Forward hostname and path in proxy - spring

I have an external service:
https://domainx.com/path/a/to/my/app
Via proxies and webservices it ends-up in my Spring (Data Rest) application on a tomcat server, exposed under:
http://mytomcatserver:8080/this/is/my/app
This app returns a HAL-JSON response with in it a _links section, pointing to itself and other resources. For example:
{
"id": "a_resource_001",
"_links": {
"self": {
"href": "http://mytomcatserver:8080/this/is/my/app/res/a_resource_001"
}
}
}
On the consuming side this link won't mean anything of course.
This was solved: on the consuming side, somewhere a header is added:
X-Forwarded-Host=domainx.com
Then the result became (automatically thanks to the framework):
{
"id": "a_resource_001",
"_links": {
"self": {
"href": "https://domainx.com/this/is/my/app/res/a_resource_001"
}
}
}
The link is improved, but still the path / context-root doesn't match:
/path/a/to/my/app != /this/is/my/app.
So, I thought, is there a X-Forwarded-Path or something like that. Some header that is picked up by my application framework (Tomcat, or Spring, or Spring Data Rest) and used to build the url in the JSON response.
But no, didn't find it.
Does somebody know how to solve such issues. It seems to me I'm not the first dealing with this stuff.
O yeah, using the same path is not an option in this case. (Of course, if I would expose my application's context-root as /path/to/my/app, it would work. not an option in this case)
I imagine there is a solution in Tomcat/Spring where some headers might influence the links.
But I also think that this can be solved in the web-server (apache) or proxy settings.

I had same issues with this.
While you're using a spring application, the easiest way is to set the context path the same to the proxy.
There is also the option to get the x-forwarded-for information. Those has to be sent from proxy.
On application side you can use (in this case application.properties)
server.use-forward-headers=true
server.servlet.context-path=/my/custom/context/path

Related

Spring boot, tomcat, rest api 404

I am using Kotlin + Gradle and trying to build a war file to deploy on Tomcat. My application is from the https://start.spring.io plus a simple controller and build the war file using ./gradlew bootWar
#SpringBootApplication
class ServletInitializer : SpringBootServletInitializer() {
override fun configure(application: SpringApplicationBuilder): SpringApplicationBuilder {
return application.sources(DemoApplication::class.java)
}
}
#RestController
class TomcatController {
#GetMapping("/hello")
fun sayHello(): Collection<String> {
return IntStream.range(0, 10)
.mapToObj { i: Int -> "Hello number $i" }
.collect(Collectors.toList())
}
}
when I try to access it I get
Type Status Report
Message The requested resource [/demo-0.0.1-SNAPSHOT/hello] is not available
Description The origin server did not find a current representation for the target resource or is not willing to disclose that one exists.
I am super stuck. What am I doing wrong? If I add a html file to the src/main/webapp/index.html it shows up for some reason only the rest api can't be reached.
Spring Boot applications come with a built in Servlet. You are probably already using this feature when launching the application inside your IDE.
This basically means that you can just run your .jar file on any web server and it will be ready to go without setting up an extra tomcat instance.
However, if you want to build a Spring Boot application as a war file and deploy it to an external tomcat, you need to follow some extra steps as explained in this article.
Assuming from what you posted so far: the path that is returned shows another route before your actual controller route "/demo-0.0.1-SNAPSHOT/hello" is this "/demo-0.0.1-SNAPSHOT" the path that your application runs on ? If not it should be included in your controller (assuming you havent set it elsewhere for e.g. in your application.properties).
for e.g. http://localhost:8080/ would be the basepath and either http://localhost:8080/demo-0.0.1-SNAPSHOT/hello or http://localhost:8080/hello would point to your controller. Also your startup logs (for Tomcat and Spring) might give away more about the issue.

Spring Boot Actuator Change the format of /health

Recently I update spring-boot-starter-actuator to 2.2.2 and when I consume the /health endpoint i got:
{
"groups": [],
"status": {
"code": "UP",
"description": ""
}
}
instead of:
{
"status": "UP"
}
And i don't have any clue the reason of this. Any idea? Or how I can refomat the output json to the original format? Not overwrite the HealthIndicator, only reformat.
Thanks in advance.
Spring Actuator 2.2 Health Endpoint JSON documentation says:
The /actuator/health endpoint has changed the resulting JSON format by
renaming details to components for the first-level elements. This
helps to differentiate the actual details returned by a
HealthIndicator from the component indicators that make up composite
health.
As a result of the change, the actuator media type has been bumped
from application/vnd.spring-boot.actuator.v2+json to
application/vnd.spring-boot.actuator.v3+json. If you have tools that
need to consume the older format, you can use an HTTP Accept: header
with the V2 media type, application/vnd.spring-boot.actuator.v2+json.
In addition if you want to see all documentation related to health and what is groups ? how to custom the health indicator take a look the
Current Health Information

Accessing VCAP properties on PCF Spring Boot app

I have a simple Spring Boot app running on PCF. I am wondering if there is a better way to access VCAP environment variables from PCF. Specifically, I am trying to access service credentials for a RabbitMQ service instance running on PCF and bound to my application.
At the moment, I access the credentials using System.getenv:
JSONObject vcapServices = new JSONObject(System.getenv("VCAP_SERVICES"));
JSONArray rabbitmq = (JSONArray) vcapServices.get("p-rabbitmq");
JSONObject serviceInfo = (JSONObject) rabbitmq.get(0);
JSONObject credentials = (JSONObject) serviceInfo.get("credentials");
hostname = credentials.getString("hostname");
virtualHost = credentials.getString("vhost");
username = credentials.getString("username");
password = credentials.getString("password");
I was trying to get it working with the #Value annotation to try to access the VCAP environment variables like this:
#Value("${vcap.services.p-rabbitmq.credentials.hostname}")
private String hostname;
but I haven't been able to grab the values yet.
Is there any way I can access VCAP variables via the #Value annotation? Or is there a better way other than System.getenv to get these credentials from PCF once I deploy my application?
Is there any way I can access VCAP variables via the #Value annotation?
Yes, I think you need to change your format slightly. It's vcap.services.<service-instance-name>.credentials.hostname. You have the name of the service provider itself, not the name of your service instance. I can't see that name in the example above, but I've found the Javadoc to be helpful. It lists the following example.
VCAP_SERVICES: {
"rds-mysql-1.0": [
{
"credentials": {
"host": "mysql-service-public.clqg2e2w3ecf.us-east-1.rds.amazonaws.com",
"hostname": "mysql-service-public.clqg2e2w3ecf.us-east-1.rds.amazonaws.com",
"name": "d04fb13d27d964c62b267bbba1cffb9da",
"password": "pxLsGVpsC9A5S",
"port": 3306,
"user": "urpRuqTf8Cpe6",
"username": "urpRuqTf8Cpe6"
},
"label": "rds-mysql-1.0",
"name": "mysql",
"plan": "10mb"
}
]
}
In this example, the service provider name is "rds-mysql-1.0", which would be the same as "p-rabbitmq" in your example. It then has a service instance name of "mysql" & that is what you'd use in your property placeholder.
Ex: vcap.services.mysql.credentials.host
https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/cloud/CloudFoundryVcapEnvironmentPostProcessor.html
Having explained all that, it can still be a fair bit of work to wire up all of your properties. Especially if you're utilizing multiple services or in your case, you're using RabbitMQ which has a lot of info that is passed through VCAP_SERVICES.
UPDATE
Spring Cloud Connectors is still an option, leaving it below for historical context, but java-cfenv is the recommended way to access service properties going forward. It plays nicer with Spring Boot and is every bit as capable.
DEPRECATED
Another option to consider is Spring Cloud Connectors. This is a library that will basically handle all of this for you. You ask for a DataSource or ConnectionFactory and it makes sure one is wired into your application.
Ex:
#Bean
public RabbitConnectionFactory rabbitFactory() {
return connectionFactory().rabbitConnectionFactory();
}
See Getting Started if you're interested in checking this out.

Spring-Cloud Zuul breaks UTF-8 symbols in forwarded multipart request filename

this is first time for me on SO, so please be patient for my first question.
I think i have some kind of configuration problem, but after a day of experiments i'm stuck. Our application is based on Spring-Cloud [Brixton release]. We have configuration like this: Portal (web application serving angular-based web-ui), which has zuul proxy with single route configured to our gateway service, like so:
zuul:
ignoredServices: '*'
prefix: /api
routes:
api-proxy:
path: /**
serviceId: api-gateway
which has another Zuul configured and relays requests to inner bussiness logic services:
zuul:
ignoredServices: '*'
routes:
service1:
path: /service1/**
serviceId: service1
service2:
path: /service2/**
serviceId: service2
All this configuration is working with no problem.
The problem now that i am facing is with file upload multipart requests. To be more precise - those multipart requests, when file to be uploaded has non latin symbols (e.g. ąčęėįš) from UTF-8. When request reaches service which has to deal with #RequestPart MultipartFile file, then file.getOriginalFilename() returns questionmarks in the places of aforementioned symbols. Now, i have tried to directly upload such file to such controller, and filename comes without questionmarks, that is, not broken, which suggests, that some bad interpretation/parsing of multipart request occurs somewhere in Zuul filters, when proxy relays incomming request.
Maybe someone had similar experience with Zuul and can direct me some way to resolve this problem?
I just ran into the same issue myself, and created the following issue:
https://jira.spring.io/browse/SPR-15396
Hopefully this is getting configurable in Spring 4.3.8.
In the meantime, you have to create a bean of type FormBodyWrapperFilter (that overrides the one in ZuulConfiguration). In the constructor, you pass a copy of FormHttpMessageConverter, which extends from FormHttpMessageConverter, and you change the encoding used in FormHttpMessageConverter.MultipartHttpOutputMessage#getAsciiBytes(String) to UTF-8 (you might also want to delete any references to javax-mail, unless you have that on classpath). You need a pretty recent version of Spring Cloud Netflix to do this.
Example:
#Bean
FormBodyWrapperFilter formBodyWrapperFilter() {
return new FormBodyWrapperFilter(new MyFormHttpMessageConverter());
}
Then you create a copy of FormHttpMessageConverter, and change the following method:
private byte[] getAsciiBytes(String name) {
try {
// THIS IS THE ONLY MODIFICATION:
return name.getBytes("UTF-8");
} catch (UnsupportedEncodingException ex) {
// Should not happen - US-ASCII is always supported.
throw new IllegalStateException(ex);
}
}
Still having issues after modifying response even with newer versions
Using spring boot 2.3.8.RELEASE
Managed to fix it by forcing the following spring properties
server.servlet.encoding.force= true
server.servlet.encoding.charset= UTF-8

How do you change the hostname in Spring Data REST HATEOAS links?

I have a Spring Boot application with Spring Data Rest which I'm deploying via jar file with embedded Tomcat. The application runs on port 8080 but in front of Tomcat there's an httpd which passes the request through.
Now the application comes with HATEOAS links, which I'm using in my JS-Client.
The response looks something like this:
{
"property" : "value"
"_links" : {
"self" : {
"href" : "http://my.server:8080/resource/1"
}
}
}
My problem is that since I access the application via my.server, the application responds with an href to my.server:8080, which isn't accessible from the outside.
How can I change the href hostname to my.server (without the port) without letting tomcat run under port 80?
I tried subclassing RepositoryRestMvcConfiguration and setting the baseURI, but that does not work, since I get a 404 then.
If you can configure your httpd to not alter the HOST header you'll get what you're after as the default link builder uses the HOST header to construct URLs.
Alternatively you can have your httpd append an X-Forwarded-Host header, which overrides URL construction using the HOST header.

Resources