How to disable logging for Spring RestTemplate - java

I am trying to reduce the logging of RestTemplate.
HttpHeaders headers = new HttpHeaders();
RestTemplate restTemplate = new RestTemplate();
headers.setContentType(MediaType.TEXT_PLAIN);
HttpEntity<String> entity = new HttpEntity<>(dataToBeSenttoVM, headers);
log.info("something");
ResponseEntity<String> response = restTemplate.exchange(url,HttpMethod.POST, entity, String.class);
When the statement is executed it prints a Response. I am doing a lot of API calls using this, and this is creating a lot of logging noise in my console.
Is there any way I can reduce or disable the logging for this?
I only want to avoid logging in this part of the code not the whole application.
Using Log4j's LoggerManager with following:
logging.pattern.console=%d{yyyy-MM-dd} %d{HH:mm:ss.SSS} [%thread] %5p %-70.70(%logger:%L){70} %m%n
logging.level.org.springframework.web.client.RestTemplate=ERROR
logging.level.org.springframework.web: DEBUG

Increase the minimum logging level of RestTemplate in your application.properties file, e.g., to ERROR:
logging.level.org.springframework.web.client.RestTemplate=ERROR
Or, if you're using an application.yml file:
logging:
level:
org.springframework.web.client.RestTemplate: ERROR

Related

Spring Boot multipart/form-data request file streaming to downstream service

I have a microservice architecture, where one service acts as a proxy, and must only forward the uploaded form data payload to the downstream service using restTemplate, preferably without loading anything from the request on disk or into memory.
I managed to resolve the issue taking the following steps.
Here I will describe the approaches, and the limitations used:
I have the following rest template configuration:
#Bean
public RestTemplate myRestTemplate() {
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory();
requestFactory.setBufferRequestBody(false);
RestTemplate restTemplate = new RestTemplate(requestFactory);
restTemplate.setInterceptors(new ArrayList<>()); // to avoid interceptors loading data into memory
return restTemplate;
}
in my controller I am processing the HttpServletRequest directly using Apache Commons FileUpload Streaming Api with one asterix:
Special care on the multipart form data, so first the form fields are processed in the while loop, and then only one file was I able to process, since:
FileItemStream fileItemStream = uploadItemIterator.next();
return fileItemStream.openStream();
must be returned without invoking itemIterator.hasNext(), because that will result in FileItemStream.ItemSkippedException
which works wonderfully, no data is saved on disk
c:\Users\myuser\AppData\Local\Temp\tomcat.11416588345568217859.8077\
note: I have set the following property as stated in the documentation.
spring.application.servlet.multipart.enabled: false
From here, Using the streaming api I have an inputStream, which I will pass further down to create my HttpEntity as follows (simplified in example, full inspiration to include filename in request: here):
MultiValueMap<String, Object> multiPartBody = new LinkedMultiValueMap<>();
multiPartBody.add(FILE, inputStream);
HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(multiPartBody, myHeaders);
after this, I do make the call to my rest Template:
myRestTemplate.postForEntity(url, requestEntity, MyResponse.class);
this goes all the way via the following sequence:
RestTemplate.doExecute()
HttpAccessor.createRequest()
HttpComponentsClientHttpRequestFactory.createRequest() -> which will return a **HttpComponentsStreamingClientHttpRequest** <- this one is important
RestTemplate.doWithRequest(ClientHttpRequest httpRequest) -> calls: ((HttpMessageConverter<Object>) messageConverter).write(
requestBody, requestContentType, httpRequest);
FormHttpMessageConverter.write()
FormHttpMessageConverter.writeMultipart() -> where outputMessage instanceof StreamingHttpOutputMessage is true
HttpComponentsStreamingClientHttpRequest.executeInternal -> creates a new StreamingHttpEntity(...)
after which this goes down on InternalCLientExecution, and in execChain
sooner or later it will enter in the chain:
HttpComponentsStreamingClientHttpRequest.StreamingHttpEntity.writeTo(OutputStream outputStream) throws IOException {
this.body.writeTo(outputStream);
}
where body is a FormHttpMessageConverter.lambda from above:
if (outputMessage instanceof StreamingHttpOutputMessage streamingOutputMessage) {
streamingOutputMessage.setBody(outputStream -> {
writeParts(outputStream, parts, boundary);
writeEnd(outputStream, boundary);
});
}
so we get further down, and end up in:
FormHttpMessageConverter.writeParts()
FormHttpMessageConverter.writePart()
here a multipartMessage is composed and passed further down (or invoked the superclass AbstractHttpMessageConverter method)
multipartMessage = new MultipartHttpOutputMessage(os, charset);
...
((HttpMessageConverter<Object>) messageConverter).write(partBody, partContentType, multipartMessage);
from here we get into AbstractHttpMessageConverter.write where condition
if (outputMessage instanceof StreamingHttpOutputMessage)
evaluates to false because MultipartHttpOutputMessage is not an instance of StreamingHttpOutputMessage
But this seems not to affect anything, since the whole thing is invoked in the above mentioned lambda, sooner or later, we need to write the bytes from the inputStream into the outputStream.
one impediment:
if I configure the restTemplate as follows:
#Bean
#org.springframework.cloud.client.loadbalancer.LoadBalanced
public RestTemplate myRestTemplate() {
...
}
there is an interceptor/aspect overriding the RestTemplate HttpComponentsClientHttpRequestFactory with RibbonClientHttpRequestFactory (using spring netflix stack), which does not support setBufferRequestBody(false).
That is how I managed to solve the file streaming issue, hope it helps others too:
Limitations/Constraints:
You cannot use MultipartFile in your controllers since spring by default saves data into temp files on fileSystem (can't use resolve-lazily either: because), I was able to overcome this issue only with Apache Commons FileUpload
Using Apache Commons FileUpload I managed to process only one file, and the form data need to be processed before the file data
spring.application.servlet.multipart.enabled: false -> affects other endpoints too
composing downstream form data with correct Content-Disposition: form-data; name="file"; filename="my.txt" needs some strange embedded HttpEntity constructions
#LoadBalanced overrides the whole restTemplate requestFactory
Good luck everyone, and any feedback is welcome.

Spring boot rest template - HttpMessageConverter cannot convert objects

hello everyone I have a problem that I do not understand why this is happening. I am working with spring boot and deploying with Kubernetes. after a while, the restTemplate stops working and I do not know what can be the cause. this is how I am creating the restTemplate class
#Bean
public RestTemplate vanillaRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(new RestTemplateErrorHandler());
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
return restTemplate;
}
after a while some of my APIs that is using restTemplate returns an error like this:
{
"status": 500,
"body": "{ message: Could not extract response: no suitable HttpMessageConverter found for response type [interface java.util.List] and content type [application/json;charset=UTF-8] , cause: null }",
}
this is only an example. resttemplate will have a problem with many classes such as Map, String, etc when this happens. and this is how I am sending the request:
ResponseEntity<List> adsRes = restTemplate.getForEntity(url, List.class);
the weird part is when I restart the app on the server everything starts working again and the APIs have no problem at all. even the ones that returned an error.
I have read this question but, as you can see I have not set any interceptor or anything like that to resttemplate
thanks in advance
Try replace the way how you are calling to the endpoint by the following code
ResponseEntity<YourObject[]> response = restTemplate.getForEntity("urlService",
YourObject[].class);
YourObject[] yourObjects= response.getBody();
List<YourObject> list = Arrays.asList(yourObjects);

Curl Token request to Spring RestTemplate Conversion

I am trying to convert a curl request, which is giving me a token into Spring's Resttemplate post request, but I am getting 403 - bad request
My Curl Request
curl -d "grant_type=client_credentials&client_id=nu5yzeth9tektzf5egxuntp7&client_secret=uP2Xvr6SCKYgXgxxJsv2QkUG"
-H "Content-Type: application/x-www-form-urlencoded"
-X POST https://cloudsso.example.com/as/token.oauth2
Curl Response:
{"access_token":"HVURQ845OPJqs8UpOlef5m2ZCNwR","token_type":"Bearer","expires_in":3599}
Now, Here is my Java code to implement above curl post request
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
MultiValueMap<String, String> bodyParamMap = new LinkedMultiValueMap<String, String>();
bodyParamMap.add("grant_type", "client_credentials");
bodyParamMap.add("client_id", "nu5yzeth9tektzf5egxuntp7");
bodyParamMap.add("client_secret", "uP2Xvr6SCKYgXgxxJsv2QkUG");
entity = new HttpEntity<>(reqBodyData, bodyParamMap);
restTemplate.postForEntity(url, entity, TokenDTO.class)
Resttemplate call response:
2019-12-04 11:10:16,483[0;39m [39mDEBUG[0;39m [[34mrestartedMain[0;39m] [33morg.springframework.core.log.CompositeLog[0;39m: Accept=[application/json, application/*+json]
[30m2019-12-04 11:10:16,484[0;39m [39mDEBUG[0;39m [[34mrestartedMain[0;39m] [33morg.springframework.core.log.CompositeLog[0;39m: Writing [{"grant_type":["client_credentials"],"client_id":["nu5yzeth9tektzf5egxuntp7"],"client_secret":["uP2Xvr6SCKYgXgxxJsv2QkUG"]}] with org.springframework.http.converter.StringHttpMessageConverter
[30m2019-12-04 11:10:17,976[0;39m [39mDEBUG[0;39m [[34mrestartedMain[0;39m] [33morg.springframework.core.log.CompositeLog[0;39m: Response 400 BAD_REQUEST
[30m2019-12-04 11:10:17,981[0;39m [34mINFO [0;39m [[34mrestartedMain[0;39m] [33mcom.ibm.ciscoApiIntegration.service.impl.CiscoAPIServiceImpl[0;39m: e::: 400 Bad Request
How can make the above curl request in RestTemplate working?
Note: https://cloudsso.example.com/as/token.oauth2 it's little modified for the question.
If you take a look at the documentation for HttpEntity you will see that you are using the wrong constructor.
entity = new HttpEntity<>(reqBodyData, bodyParamMap);
You are passing the arguments you want to use as the body (bodyParamMap) as headers (as the second argument is the headers to be used for the request). In fact you aren't even using the HttpHeaders for the request as you aren't passing them to the HttpEntity. I'm not sure what the reqBodyData is but probably a toString of the bodyParamMap.
What you should have been doing is the following
entity = new HttpEntity<>(bodyParamMap, headers);
As HttpHeaders impements MultiValueMap you can directly use them in the constructor. You also want to pass the bodyParamMap as is.
NOTE: I would also suggest to explictly set the Content-Type on the HttpHeaders so you are sure what is being send.
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
Thus the total resulting code should look like
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> bodyParamMap = new LinkedMultiValueMap<>();
bodyParamMap.add("grant_type", "client_credentials");
bodyParamMap.add("client_id", "nu5yzeth9tektzf5egxuntp7");
bodyParamMap.add("client_secret", "uP2Xvr6SCKYgXgxxJsv2QkUG");
entity = new HttpEntity<>(bodyParamMap, headers);
restTemplate.postForEntity(url, entity, TokenDTO.class)
NOTE: You shouldn't be constructing a single use RestTemplate but you rather want to configure that once and inject that into your class.
I have just solved the issue:
I had this in this
// wrong
//private HttpEntity<String> entity;
I changed it to this:
private MultiValueMap<String, String> parametersMap;
and changed it
// not working
entity = new HttpEntity<>(reqBodyData, bodyParamMap);
to this
// working
entity = new HttpEntity<>(bodyParamMap, headers);
Now it's working perfectly fine.
response:
2019-12-04 11:52:46,503 DEBUG [restartedMain] org.springframework.core.log.CompositeLog: HTTP POST https://cloudsso.example.com/as/token.oauth2
2019-12-04 11:52:46,521 DEBUG [restartedMain] org.springframework.core.log.CompositeLog: Accept=[application/json, application/*+json]
2019-12-04 11:52:46,522 DEBUG [restartedMain] org.springframework.core.log.CompositeLog: Writing [{grant_type=[client_credentials], client_id=[nu5yzeth9tektzf5egxuntp7], client_secret=[uP2Xvr6SCKYgXgxxJsv2QkUG]}] with org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter
2019-12-04 11:52:47,831 DEBUG [restartedMain] org.springframework.core.log.CompositeLog: Response 200 OK
Note: in my case, adding or disabling this value, isn't making any difference, i am getting the auth token anyway.
//headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
As suggested by post https://stackoverflow.com/a/49127760/11226302
You should set the request content type in header as;
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
Hope this helps!

Spring RestTemplate message converter priority when posting

What is the most convenient way to influence the priority of the message converters Spring applies when POSTing with RestTemplate?
Use case: I want to ensure a given entity is POSTed as JSON rather than e.g. XML when I do restTemplate.postForEntity(url, entity, Void.class).
Default
By default the entity is converted to XML because the MappingJackson2XmlHttpMessageConverter takes precedence over the MappingJackson2HttpMessageConverter. The default list of converters for my app appears to be (Spring scans the classpath to see what's available):
Option 1
You can configure the message converters explicitly for a given RestTemplate instance like so restTemplate.setMessageConverters(Lists.newArrayList(new MappingJackson2HttpMessageConverter())). This is inconvenient if the instance is shared (as a Spring bean for example) as you might need converter X in one case and converter Y in a different one.
Option 2
You can set Accept and Content-Type HTTP headers explicitly in which case Spring will use a matching message converter. The downside is that you have to resort to RestTemplate.exchange instead of RestTemplate.postForEntity which means: extra code, less convenience.
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.setContentType(MediaType.APPLICATION_JSON);
requestHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
HttpEntity requestEntity = new HttpEntity(entity, requestHeaders);
restTemplate.exchange(url, HttpMethod.POST, requestEntity, Void.class);
Option 3
This might be what I'm looking for :)
This issue is answered in detail here.
Basically, when you add the below-mentioned library, it adds MappingJackson2XmlHttpMessageConverter before MappingJackson2HttpMessageConverter. As a result, Spring boot assumes every request accepts application/XML.
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
To avoid this behaviour, you might want to swap the two message converters.
Example:
#Bean
RestTemplate restTemplate() {
RestTemplate restTemplate = new RestTemplate();
// move XML converter to the end of list
List<HttpMessageConverter<?>> messageConverters = restTemplate.getMessageConverters();
for (int i = 0; i < messageConverters.size() -1 ; i++) {
if (messageConverters.get(i) instanceof MappingJackson2XmlHttpMessageConverter) {
Collections.swap(messageConverters, i,messageConverters.size() - 1);
}
}
restTemplate.setMessageConverters(messageConverters);
// add interceptors if necessary
restTemplate.setInterceptors(Collections.singletonList(catalogInterceptior()));
return restTemplate;
}
According to the Spring javadoc (https://docs.spring.io/spring-framework/docs/current/javadoc-api/index.html?org/springframework/web/client/RestTemplate.html) you can still use postForEntity,
public <T> ResponseEntity<T> postForEntity(java.lang.String url,
#Nullable
java.lang.Object request,
java.lang.Class<T> responseType,
java.util.Map<java.lang.String,?> uriVariables)
throws RestClientException
....
The request parameter can be a HttpEntity in order to add additional HTTP headers to the request.

SessionId lost when I make a request between backend of microservices

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'.

Categories

Resources