I would like to add some http headers to requests running through a Spring Cloud Gateway. The issue is that there seems to be methods for adding headers, and methods for doing something with the body, but no method that allows me to adjust the headers while looking at the body.
The reason I need to look at the body while making headers is to create the digest for the http signature.
How I can add headers and body while being unable to look at the body while updating the headers:
.filters(f -> {
return f
.addResponseHeader("foo", "bar")
.modifyRequestBody(String.class, String.class,
(exchange, s) -> {
...
})
Is there a way to use addRequestHeader() while also seeing the body?
You're going to need to write your own routefilter for that.
See https://www.baeldung.com/spring-cloud-custom-gateway-filters for example
Related
I have a webservice which calls another WS and returns the response from the second WS. It looks like so:
// MyController
public ResponseEntity<Foo> requestFooController(#RequestBody #Valid Bar request) {
return this.myService.requestFooService(request);
}
//MyService
ResponseEntity<Foo> requestFooService(Bar request) {
Buzz improvedRequest = ...
return this.secondWS.secondRequestFoo(improvedRequest);
}
When I call the API through Postman, I receive a HTTP OK response with an empty body. Yet, when I'm in debug mode I can see that the service is returning a ResponseEntity with a body. The headers are not lost though.
I changed my code like so and it works fine:
// MyController
public ResponseEntity<Foo> requestFooController(#RequestBody #Valid Bar request) {
ResponseEntity<Foo> tmp = this.myService.requestFooService(request);
return ResponseEntity.status(tmp.getStatusCode()).body(tmp.getBody());
}
Now through Postman I do have the expected body. However, I don't understand the behaviour. I thought that maybe it's due to the fact that the body is some kind of stream that can be read once or something similar. But from reading the source code I don't see anything that could explain this behaviour.
I'm using the Netflix-stack (so HTTP calls between the two WS are made through a Feign client).
Any idea why I'm getting this result?
EDIT:
More details on my stask:
SpringBoot 1.5.3.RELEASE
Feign 2.0.5
There is a bug that causes the named body of an HTTP MultiPart POST to fail. The symptom of this is that you make a POST request with a body, and Spring-Boot can't match it up to an endoint. The exception I see is:
2019-01-23 15:22:45.046 DEBUG 1639 --- [io-8080-exec-10] .w.s.m.m.a.ServletInvocableHandlerMethod : Failed to resolve argument 3 of type 'org.springframework.web.multipart.MultipartFile'
org.springframework.web.multipart.support.MissingServletRequestPartException: Required request part 'file' is not present
Zuul is doing caching of the request in order to re-try multiple times. In this process, it fails to preserve the named field for the binary body. You may find it working if you preface the request with zuul. So instead of http://myserver.com/myservice/endpoint use zuul in the path: http://myserver.com/zuul/myservice/endpoint
That will effectively avoid the saving of the request and the retry mechanism.
More details are available on this issue in Zuul's GitHub Bug List.
I'm using the exchange headers to store any variables in the route. But, looks like these headers will be carried on to the other routes which are called from this route.
In the below sample, I'm calling a getContact route which will call a http endpoint. But, it will also send the headers, variable1 & variable2, it got from the initial route, direct:start.
from("direct:start")
.setHeader("variable1", constant("value1"))
.setHeader("variable2", constant("value2"))
.to("direct:getContact");
from("direct:getContact")
.setHeader("Content-Type", constant("application/json"))
.setHeader("Accept", constant("application/json"))
.setHeader(Exchange.HTTP_METHOD, constant("GET"))
.to("http://<host>:<port>/contact/3")
.unmarshal().json(JsonLibrary.Jackson);
Is there a way to avoid this? In contrast, a method call in java will hide all the existing variables by context switch.
I've run into the problem before when sending a webservice call using http4. Tt's rather annoying that Camel seems to send send the entire exchange when you're using the http4/http endpoint. I got around this by using a content enricher. I placed the actual call using http4 in the enrich route and had an simple aggregation strategy combine the two messages afterwards.
Alternatively, you can make the call in a bean. This way you lose some of the benefits of camel but you have complete control over the call body.
There is no direct way to avoid this. If you are setting the headers to a hard-coded value then you might be able to move the header to a URI property on your endpoint. If not then you only really have 2 other options. The first option is to remove all of the headers using a remove header call after your HTTP call so they don't go downstream. The second is to set all of the headers in the same route as the http call and have a different route call that endpoint with an enrich statement and in the aggregation back to the main route you can customize the returned exchange.
Here is an camel http reference page for all of the allowed headers to see if you can put it in the URI http://camel.apache.org/http4.html
Sample of a route removing headers
from("direct:start")
.setHeader("variable1", constant("value1"))
.setHeader("variable2", constant("value2"))
.setHeader("Content-Type", constant("application/json"))
.setHeader("Accept", constant("application/json"))
.setHeader(Exchange.HTTP_METHOD, constant("GET"))
.to("http://<host>:<port>/contact/3")
.unmarshal().json(JsonLibrary.Jackson)
.removeHeaders("variable*")
.to("Anything I call now won't have the variable headers");
enrichment call
AggregationStrategy aggregationStrategy = new ExampleAggregationStrategy();
from("direct:start")
.enrich("direct:getContact", aggregationStrategy)
.to("You can have no additional headers here");
public class ExampleAggregationStrategy implements AggregationStrategy {
public Exchange aggregate(Exchange original, Exchange resource) {
Object originalBody = original.getIn().getBody();
Object resourceResponse = resource.getIn().getBody();
Object mergeResult = //TODO implement this however you want. You can remove any headers here you like
if (original.getPattern().isOutCapable()) {
original.getOut().setBody(mergeResult);
} else {
original.getIn().setBody(mergeResult);
}
return original;
}
}
Actually 1 more option came to mind when going through the camel documentation I found an interesting property. Disclaimer I have never tried this property myself since I am still running camel 2.15 atm, but feel free to test it really quick it might just be what you need.
copyHeaders
default: true
Camel 2.16: If this option is true then IN exchange headers will be copied to OUT exchange headers according to copy strategy. Setting this to false, allows to only include the headers from the HTTP response (not propagating IN headers).
Just use:
.removeHeaders("variable*")
to remove headers of any pattern.
I am exposing a rest service by using restlet with camel.
I have exposed a rest service as one end, and at another end I have overridden process method.
The code looks like below,
from("restlet:/service/serviceName/{serviceId}?restletMethod=PUT").process(new Processor() {
#Override
public void process(Exchange exchange) throws Exception {
//Implementation goes here.
}
}
I have two issues here.
I am not able to set the content type for the request
I am not able
to achieve versioning of content type
I tried with the following options.
setHeader(Exchange.CONTENT_TYPE, simple("application/json"))
setHeader(Exchange.ACCEPT_CONTENT_TYPE, simple("application/json"))
Still the issue is not solved. Is there any other option?
So your route is a from. This means you send a request to it.
Send a request to it from fiddler, chrome or somewhere else with the headers you want.
Add logging to the route that prints out the entire exchange including the headers. Here you will find Content-Type, and all the other HTTP headers as well.
To access them you need to access the header on the Exchange like any other header.
I don't understand why you are setting headers there.
For example in your process code you can do like this:
String contentType=(String) exchange.getIn().getHeader("Content-Type");
if (contentType.equals("application/json")) {
//do something
}
Note, you need to verify that the header is called Content-Type and not content-type or something similar since RESTLET is case sensitive. That is why you need to add the logging to see the name of the header on the exchange.
I'm trying to get access to the HTTP headers that are injected by Rest Assured. Spring's Mock MVC gives you access to pretty much everything via the MvcResult, and you can use this result to log pretty much anything you would like about the request and response. The only way I can see how to do this is in RestAssured is with a Filter. However, it gives you limited access to the request (you just get the RequestSpecification). I understand that it might be tricky to get access to headers that are added by the HttpClient, but it doesn't look like you can even get access to headers that are added by Rest Assured itself. For example, I can't see any OAuth related headers, nor content-type or content-length. The only headers that appear are those that were manually added using, for example, .contentType(ContentType.XML)
Is there any other way to get access to those headers? I don't need to modify the request, I just want to be able to log all of it and the headers that are injected by Rest Assured.
I found that it's possible to register your own HttpClientFactory with RestAssured:
RestAssured.config().httpClient(
HttpClientConfig.httpClientConfig().httpClientFactory(
new CustomHttpClientFactory())
So I created a new factory that returns an HTTP client into which I inject some request and response interceptors.
public class CustomHttpClientFactory extends HttpClientConfig.HttpClientFactory {
#Override
public HttpClient createHttpClient() {
DefaultHttpClient client = new DefaultHttpClient();
client.addRequestInterceptor((request, ctx) -> {
// do what you will
});
client.addResponseInterceptor((response, ctx) -> {
// do what you will
});
return client;
}
}
This gives you almost full access to manipulate the request and response. One thing to remember is that if you're going to read from the response's entity, you should first wrap it in a BufferedHttpEntity to make it re-readable:
if (response.getEntity() != null && !response.getEntity().isRepeatable()) {
response.setEntity(new BufferedHttpEntity(response.getEntity()));
}
Another problem I ran into is when trying to see the OAuth related information. When using RestAssured's OAuth functionality, it adds its own OAuthSigner interceptor to the HTTP client right before executing the request. This means that it will always be the last interceptor to be called and any interceptor you may have already injected will be called before the request ever gets signed. Because I don't really need to see the signature for now, I didn't investigate this further and I'm leaving it as an exercise for the reader. ;)
I have a jersey server up and running. When running from browser directly I get the correct response. However when I try to access the rest service from angular.js's $resource I get the following error in the console when trying to access to the correct url. I've tried to read all materials online, like http://simplapi.wordpress.com/2013/04/10/jersey-jax-rs-implements-a-cross-domain-filter/ to setup a CORS filter, but the guide seems to be dated and cryptic. (im using the newest implementation of jersey).
Failed to load resource: Origin localhost:63342 is not allowed
by Access-Control-Allow-Origin.
method that makes the data I need available in jersey.
#Path("/id/{id}")
#GET
#Produces(MediaType.APPLICATION_JSON)
public boolean validateSSN(#PathParam("id") String id) {
IdValidator idv = new IdValidator(id);
return idv.Validate();
}
accessor method in angular.js:
services.factory('ReplyFactory', function ($resource) {
console.log("test");
return $resource(baseUrl + '/myproject/api/validate/id/:id', {id: '#id'},
{'get': { method: 'GET' }});
});
Well what you need to do is to ensure that all responses from your resources have following http headers:
Access-Control-Allow-Origin - specifies from which origins requests should be accepted (localhost:63342 in your case)
Access-Control-Allow-Headers - specifies which headers are allowed to be used via CORS
Access-Control-Allow-Methods - specifies which methods are allowed to be used via CORS
And there are other headers like Access-Control-Allow-Credentials and etc.
So you just need to add at least Access-Control-Allow-Origin header to your responses. How you can do it depends on your environment.
You can manually define those headers on each resource .
You can define Jersey filter to add CORS headers to all responses
You can use servlet filter to add CORS headers to all responses
There are also specific solutions for Tomcat and Jetty
There many ways how to do it but all of it is about the same thing - you just add an extra header to your server responses.