I'm having multiple microservices
1. MangerApp
2. ProcessApp
3. DoingStuffApp
4. .....
the "MangerApp Microservices" get an Http-Request
I'm looking for a way to transfer automatically some of the HTTP headers
in the call, while I don't want to go over each place and do - add Headers, my HTTP headers are stored as a thread-local Map.
since I call to other microservices, with RestTemplate I have many different calls some get/post/put/etc...
changing each one them and passing the header manually is not that efficient.
I'm looking for a way to manage it, other than extending the RestTemplate Class now.
You can use a ClientHttpRequestInterceptor to achieve what you need.
1) Create a HeaderInterceptor implementing ClientHttpRequestInterceptor. In this example it gets the Authorization and Accept headers from a ThreadLocal and propagates them:
public class HeaderInterceptor implements ClientHttpRequestInterceptor{
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
HttpHeaders headers = request.getHeaders();
List<String> authorization = HeaderThreadLocal.getAuthorization()
List<String> accept = HeaderThreadLocal.getAuthorization();
headers.addAll("Authorization", authorization);
headers.addAll("Accept", accept);
return execution.execute(request, body);
}
}
2) Configure your RestTemplate bean adding the header interceptor:
restTemplate.getInterceptors().add(new HeaderInterceptor());
Related
I am trying to make request between microservices in order to retrieve a list of users with the same roles. For this, first I make a request between FrontEnd and Backend inside the microservice 1. Following, I call an endpoint in the microservice 2 from Microservice 1 backend, but the session Id is lost in it, and I can retrieve the context.
I am using spring security and Redis for the session Control.
Manually, I retrieve the session Id from the microservice 1 and I add it as an attribute of the header of the second call, to the microservice 2. But it does not work.
String sessionID= RequestContextHolder.currentRequestAttributes().getSessionId();
RestTemplate rest = new RestTemplate();
HttpHeaders headers= new HttpHeaders();
headers.set("Session",sessionID);
HttpEntity<ResponseData> entity = new HttpEntity<ResponseData>(headers);
ResponseEntity<ResponseData> responseEntity =rest.exchange(targetApi, HttpMethod.GET, entity,ResponseData.class);
Finally, I resolved the problem adding an interceptor as a component:
#Component
public class SpringSessionClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
throws IOException {
request.getHeaders().add("Cookie", "SESSION=" + sessionId);
return execution.execute(request, body);
}
}
And I created a #Bean to configure the rest template:
#Bean
public RestTemplate restTemplate(){
RestTemplate rest = new RestTemplate();
ClientHttpRequestInterceptor interceptor= new SpringSessionClientHttpRequestInterceptor();
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();
interceptors.add(interceptor);
rest.setInterceptors(interceptors);
return rest;
}
I don't know the exact answer to your question but in terms of your design I'd question if you really want to make your microservice1 depending on microsevice2. A microservice should be autonomous in the way it works and being able to be deployed on it's own (in theory anyway!). May be you could have an orchestrating microservice that receives your session information and then calls the 2 other microservices to pass that information on via 'standard' attributes.
headers.set("Session",sessionID);
I assume that the problem is that you are using the wrong identifier. As far as I know, it is JSESSIONID by default.
Another problem that I can see here is that JSESSIONID expected to be in cookies. Try to put it in cookies when sending a request to your 'microservice2'.
I'm building REST services using spring-mvc and what I'm looking for now is a way to proxy HTTP request to external REST service from inside Spring MVC controller.
I'm getting HttpServletRequest object and want to proxy it making as few changes as possible. What is essential for me is keeping all the headers and attributes of incoming request as they are.
#RequestMapping('/gateway/**')
def proxy(HttpServletRequest httpRequest) {
...
}
I was trying simply to send another HTTP request to external resource using RestTemplate but I failed to find a way to copy REQUEST ATTRIBUTES (which is very important in my case).
Thanks in advance!
I wrote this ProxyController method in Kotlin to forward all incoming requests to remote service (defined by host and port) as follows:
#RequestMapping("/**")
fun proxy(requestEntity: RequestEntity<Any>, #RequestParam params: HashMap<String, String>): ResponseEntity<Any> {
val remoteService = URI.create("http://remote.service")
val uri = requestEntity.url.run {
URI(scheme, userInfo, remoteService.host, remoteService.port, path, query, fragment)
}
val forward = RequestEntity(
requestEntity.body, requestEntity.headers,
requestEntity.method, uri
)
return restTemplate.exchange(forward)
}
Note that the API of the remote service should be exactly same as this service.
You can use the spring rest template method exchange to proxy the request to a third party service.
#RequestMapping("/proxy")
#ResponseBody
public String proxy(#RequestBody String body, HttpMethod method, HttpServletRequest request, HttpServletResponse response) throws URISyntaxException {
URI thirdPartyApi = new URI("http", null, "http://example.co", 8081, request.getRequestURI(), request.getQueryString(), null);
ResponseEntity<String> resp =
restTemplate.exchange(thirdPartyApi, method, new HttpEntity<String>(body), String.class);
return resp.getBody();
}
What is the restTemplate.exchange() method for?
if you think of applying the API gateway pattern for microservices,
have a look at Netflix zuul which is a good alternative in the spring boot ecosystem. A good example is provided here.
I want to know if there is a way of removing those headers from my current response. I am using the #ResponseBody annotation and already have tried using a Filter to try to not add those headers, based on the following How do delete a HTTP response header?.
Ideally, the HTTP response should be like the one from this link: https://api.github.com/users/mralexgray/repos with no Content-Length and Transfer-Encoding headers.
You could write directly to the HttpServletResponse's OutputStream. Spring will give you the HttpServletResponse (and the HttpServletRequest) if you want it, simply by adding it to your method signature.
This way you have (mostly) full control of headers. You would probably need to create the JSON yourself, but it's usually quite simple. For example...
private ObjectMapper mapper = new ObjectMapper();
#RequestMapping(value = "/getStuff", method = RequestMethod.GET)
public void getStuff(HttpServletResponse httpServletResponse) throws Exception {
try {
httpServletResponse.setHeader("Pragma","public");
httpServletResponse.setHeader("Expires","0");
httpServletResponse.setHeader("Cache-Control","must-revalidate, post-check=0, pre-check=0");
httpServletResponse.setHeader("Cache-Control","public");
OutputStream outputStream = httpServletResponse.getOutputStream();
try {
mapper.writeValue(outputStream, myObject);
} finally {
outputStream.close();
}
This might not seem elegant, but by using #ResponseBody you are using that as a convenience to do all the hard work in creating the response. But if it is not creating the response as you would like it, you can take a step back and do it "manually" using HttpServletResponse.
RestTemplate restTemplate = new RestTemplate();
final MappingJackson2XmlHttpMessageConverter converter = new MappingJackson2XmlHttpMessageConverter();
final List<MediaType> supportedMediaTypes = new LinkedList<MediaType>(converter.getSupportedMediaTypes());
supportedMediaTypes.add(MediaType.ALL);
converter.setSupportedMediaTypes(supportedMediaTypes);
restTemplate.getMessageConverters().add(converter);
ResponseEntity<MyDTO[]> response = restTemplate.getForEntity(urlBase, MyDTO[].class);
HttpHeaders headers = response.getHeaders();
URI location = headers.getLocation(); // Has my redirect URI
response.getBody(); //Always null
I was under the impression that a 302 would automatically be followed. Am I incorrect in this assumption? I now need to pick off this location and re-request?
Using the default ClientHttpRequestFactory implementation - which is the SimpleClientHttpRequestFactory - the default behaviour is to follow the URL of the location header (for responses with status codes 3xx) - but only if the initial request was a GETrequest.
Details can be found in this class - searching for the following method:
protected void prepareConnection(HttpURLConnection connection, String httpMethod) throws IOException {
...
if ("GET".equals(httpMethod)) {
connection.setInstanceFollowRedirects(true);
}
Here the relevant doc comment of HttpURLConnection.setInstanceFollowRedirects method:
Sets whether HTTP redirects (requests with response code 3xx) should
be automatically followed by this {#code HttpURLConnection}
instance.
The default value comes from followRedirects, which defaults to true.
Are you trying to redirect from one protocol to another, e.g. from http to https or vise versa? If so the automatic redirect won't work. See this comment: URLConnection Doesn't Follow Redirect
After discussion among Java Networking engineers, it is felt that we
shouldn't automatically follow redirect from one protocol to another,
for instance, from http to https and vise versa, doing so may have
serious security consequences
from https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4620571
Otherwise if you debug the RestTemplate code you will see that by default HttpURLConnection is set properly with getInstanceFollowRedirects() == true.
When using the CommonsClientHttpRequestFactory (which uses HttpClient v3 underneath) you can override the postProcessCommonsHttpMethod method and set to follow redirects.
public class FollowRedirectsCommonsClientHttpRequestFactory extends CommonsClientHttpRequestFactory {
#Override
protected void postProcessCommonsHttpMethod(HttpMethodBase httpMethod) {
httpMethod.setFollowRedirects(true);
}
}
You can then use it like this (with optional, possibly preconfigured, HttpClient instance) and requests will follow the location headers in response:
RestTemplate restTemplate = new RestTemplate(
new FollowRedirectsCommonsClientHttpRequestFactory());
Try create RestTemplate like this (Spring config):
#Bean("smartRestTemplate")
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder
.setConnectTimeout(Duration.ofSeconds(..))
.setReadTimeout(Duration.ofSeconds(..))
.build();
}
I'm currently writing a REST API that uses a CXF interceptor to add certain headers to each request.
The code of this interceptor is:
public class TestHeaderInterceptor extends AbstractOutDatabindingInterceptor {
public TestHeaderInterceptor() {
super(Phase.SEND);
}
#SuppressWarnings("unchecked")
#Override
public void handleMessage(Message message) {
MultivaluedMap<String, Object> headers = (MetadataMap<String, Object>) message.get(Message.PROTOCOL_HEADERS);
if (headers == null) {
headers = new MetadataMap<String, Object>();
}
headers.add("X-Test", "test");
message.put(Message.PROTOCOL_HEADERS, headers);
}
}
So, as you can see I'm adding a header called X-Test with value test. When I use a CXF REST client (proxy based), I'm using the following code to add the interceptor to the client:
Client client = WebClient.client(clientObj);
ClientConfiguration config = WebClient.getConfig(client);
List<Interceptor<? extends Message>> interceptors = new ArrayList<Interceptor<? extends Message>>();
interceptors.add(new TestHeaderInterceptor());
config.setOutInterceptors(interceptors);
My REST API only has 2 actions:
#GET
#Produces(JSON_UTF8)
#Path("test/{id}")
Test test(#PathParam("id") String id);
#POST
#Produces(JSON_UTF8)
#Path("test2/{type}")
#Consumes(JSON_UTF8)
Test test2(Test obj, #PathParam("type") Type type);
The test/{id} method works successfully and it adds the header (checked with Wireshark). However, the test2/{type} call does not add the header to the request.
The weirdest thing is that, while using debug, the interceptor code is clearly invoked, which leaves me to think that somehow Apache CXF is ommitting the headers I add.
That's also the reason why I'm using the Phase.SEND phase in stead of Phase.MARSHALL, just because I thought my headers are ommitted somewhere in the process of running through all these phases. But even now the headers are still missing.
After some debugging I found out that the CXF interceptor chain is different when I'm using the test2 call, for example:
Interceptor chain with test:
[2014-06-13 10:49:48,535] - [DEBUG] - [Default Executor-thread-1] - [PhaseInterceptorChain.java:682] - Chain org.apache.cxf.phase.PhaseInterceptorChain#1384d8d1 was modified. Current flow:
pre-logical [ClientRequestFilterInterceptor]
prepare-send [MessageSenderInterceptor]
marshal [TestHeaderInterceptor]
prepare-send-ending [MessageSenderEndingInterceptor]
Interceptor chain with test2:
[2014-06-13 10:50:02,205] - [DEBUG] - [Default Executor-thread-1] - [PhaseInterceptorChain.java:682] - Chain org.apache.cxf.phase.PhaseInterceptorChain#69a33de5 was modified. Current flow:
pre-logical [ClientRequestFilterInterceptor]
prepare-send [MessageSenderInterceptor]
write [BodyWriter]
marshal [TestHeaderInterceptor]
prepare-send-ending [MessageSenderEndingInterceptor]
As you can see there is an additional BodyWriter interceptor at the WRITE phase. I suppose that when writing the request body, you can no longer access the headers (because the body comes after the headers).
So, the fix was to actually move the TestHeaderInterceptor to a phase before the BodyWriter, so in my code I'm now using the following code in my constructor:
public TestHeaderInterceptor() {
super(Phase.SETUP);
}