Get size of serialized Request using Spring RestTemplate - java

I am using Spring and RestTemplate to send requests to a REST service. Is there a way to get the (byte)size of the actual request? Optimally it includes the size of the HTTP request including the size of the serialized myObject object for both GET and POST requests.
template.postForObject(restUrl, myObject, MyObject.class);
template.getForObject(restUrl, MyObject.class);
Basically I want to know how much data is actually transmitted.
Thanks!
[edit]:
Just to complete the answer, this is how you add the Interceptor to the RestTemplate. I also edited the LengthInterceptor to show the content length of the request instead of the response.
final List<ClientHttpRequestInterceptor> interceptors = new ArrayList<ClientHttpRequestInterceptor>();
interceptors.add( new LengthInterceptor() );
template.setInterceptors( interceptors );

You can use interceptor to intercept the request/response similarly to filter in java servlet. You have to read the response and using getHeaders().getContentLength() you'll get the body length:
public class LengthInterceptor implements ClientHttpRequestInterceptor {
#Override
public ClientHttpResponse intercept( HttpRequest request, byte[] body, ClientHttpRequestExecution execution ) throws IOException {
ClientHttpResponse response = execution.execute( request, body );
long length = response.getHeaders().getContentLength();
// do something with length
return response;
}
}

Related

How to wrap an external API inside a custom API with Spring Boot

Let's say I have a third party rest api ("https://example.com/write") that allows POST requests with the following body structure:
{
"id": "abc",
"Config": {"Comments":"text"}
}
I am completely new to Java and the Spring Framework, but I want to create a custom API with Spring that only allow users to change the text part of the body. Other parts of the JSON body should have a fixed value (for example id is always "abc"). Basically, when user input a custom text string, my api will compile the input and consume the external api and get the results from it accordingly
I understand the basics of #Getmapping / #RequestMapping after doing some research. Here is what I have so far for my custom API, and I am stuck at the post mapping section.
#RestController
#RequestMapping("/api")
public class ApiController {
#Autowired
private Environment env;
// GET
#RequestMapping(value = "/retrive", method = { RequestMethod.GET })
public ResponseEntity<?> retrive (HttpServletRequest request, HttpServletResponse response) throws IOException {
// URL
URL u = new URL("https://example.com/get");
HttpURLConnection uc = (HttpURLConnection) u.openConnection();
// Create HttpHeaders for ResponseEntity
HttpHeaders responseHeaders = new HttpHeaders();
uc.setRequestProperty ("Authentication", "Bearer "+ env.getProperty("api-key"));
try (InputStream inputStream = uc.getInputStream();
OutputStream outputStream = response.getOutputStream();
)
{IOUtils.copy(inputStream, outputStream);
}
return new ResponseEntity<>(responseHeaders, HttpStatus.OK);
}
// POST
#RequestMapping(value = "/write", method = { RequestMethod.POST },
consumes = {MediaType.APPLICATION_JSON_VALUE})
public ResponseEntity process(#RequestBody Root input) throws IOException {
// Operation goes here...
return new ResponseEntity<>(input, HttpStatus.OK);
}
public class Root{
private String Comments;
// Getters and Setters
}
Create a Custom DTO class which will represent the response from Third party API. What It means is it should have all fields which should map with corresponding required field of response payload of third party API. It will be populated on de-serializing the third party API response. You can take help of feign client here. Its very easy and declarative way to make REST API call.
Create a separate response DTO with 2 fields 1 which will have the static value and second the DTO which we created above (which will be populated with the response of third party API). This DTO will be the response from POST "/write"

Retrieving the name of the method from which HTTP call was made using an Interceptor

I am using the Spring ClientHttpRequestInterceptor to capture all outgoing HTTP calls from my applications in order to log the data. In addition to the data that I am already collecting in the interceptor, I want to somehow fetch the name of the function from which the HTTP call originated. So, as an example, if a method called getStuffFromUrl is making the HTTP call using the Spring RestTemplate as follows,
public String getStuffFromUrl() {
...
return restTemplate.exchange(url, HttpMethod.GET,entity, String.class).getBody();
}
when I capture this outbound HTTP call in my interceptor, I want to retrieve the name of the method getStuffFromUrl as well. How could I go about doing this?
If you are allowed to modify your HTTP request, one way would be to add a ad-hoc HTTP header for the method name :
public String getStuffFromUrl() {
HttpHeaders headers = new HttpHeaders();
headers.add("JavaMethod", "getStuffFromUrl");
entity = new Entity(headers)
...
return restTemplate.exchange(url, HttpMethod.GET,entity, String.class).getBody();
}
You could then get back the method name and remove the header from within the ClientHttpRequestInterceptor prior the HTTP request is actualy sent out.
ClientHttpResponse intercept(HttpRequest request, byte[] body,
ClientHttpRequestExecution execution)
throws IOException {
String javaMethodName="Unknown";
List<String> javaMethodHeader = request.getHeaders().remove("JavaMethod");
if(javaMethodHeader!=null && javaMethodHeader.size()>0) {
javaMethodName = javaMethodHeader.get(0);
}
log.info("Calling method = "+ javaMethodName);
execution.execute(request, body);
}
(provided code not tested)

Use Token in RestTemplate

I want to use this RestTemplate code to make POST requests.
#Bean(name = "simpleRestTemplate")
public RestTemplate getRestClient() {
RestTemplate restClient = new RestTemplate(getClientHttpRequestFactory());
restClient.getInterceptors().add(new BasicAuthorizationInterceptor("username", "password"));
HttpEntity<PaymentTransaction> request = new HttpEntity<>(new PaymentTransaction());
ResponseEntity<PaymentTransaction> response = restClient.exchange("http://example.com", HttpMethod.POST,
request, PaymentTransaction.class);
PaymentTransaction foo = response.getBody();
return restClient;
}
How I can add Toke authentication into the HTTP link?
Probably the easiest way is to use exchange("http://example.com" + "/" + token, HttpMethod.POST,
Is there any better way?
Check out UriComponentsBuilder:
URI uri = UriComponentsBuilder.fromUriString("http://example.com")
.pathSegment(token)
.build()
.toUri();
Then you can use exchange() that takes a URI as its first parameter.
restClient.exchange(uri, HttpMethod.POST, request, PaymentTransaction.class);
As #nickb commented, authentication is best done in HTTP headers.
If you really need to inject a token in the URL, you can implement a custom interceptor.
Pseudo code:
final String tokenValue = "something";
restClient.getInterceptors().add(new ClientHttpRequestInterceptor() {
#Override
ClientHttpResponse intercept(HttpRequest request,
byte[] body,
ClientHttpRequestExecution execution)
throws java.io.IOException {
URI modifiedUri = UriComponentsBuilder.fromUri(request.getURI())
.query("token={tokenPlaceholder}")
.buildAndExpand(tokenValue)
.toUri();
request.setURI(modifiedUri);
}
});
There are many reasons for not doing that, for instance:
Systems that intercept and logs URL would log the token too, allowing 3rd parties to impersonate your users
You need to parse the token from the URL while dealing the rest of the query in the POST body request
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/http/client/ClientHttpRequestInterceptor.html

Log Response content in spring HandlerInterceptorAdapter postHandle

I have implemented the below method to log response , but it throws an exception at IOUtils.toString(responseWrapper.getContentInputStream(), "UTF-8"); saying response has zero bytes . I am able to access headers and status. how can I get the response body?
#Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("after Request");
ObjectMapper objectMapper = new ObjectMapper();
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
HttpStatus responseStatus = HttpStatus.valueOf(responseWrapper.getStatusCode());
System.out.println(responseWrapper.getStatusCode());
HttpHeaders responseHeaders = new HttpHeaders();
for (String headerName : responseWrapper.getHeaderNames()) {
System.out.println(headerName);
responseHeaders.add(headerName, responseWrapper.getHeader(headerName));
}
String responseBody = IOUtils.toString(responseWrapper.getContentInputStream(), "UTF-8");
System.out.println(responseBody);
log.info("Response body {} ",responseBody);
responseWrapper.copyBodyToResponse();
}
You never use response.getOutputStream() or response.getWriter() so no data is added to the response. That's why it is empty.
Your created entity
ResponseEntity<JsonNode> responseEntity = new ResponseEntity<>(responseJson,responseHeaders,responseStatus);
is never used and never sent to response. Try e.g.
responseWrapper.getWriter().write("test");
and check the body after.
How to read and copy the HTTP servlet response output stream content for logging
has your answer. The reason why getting your response output stream does not work is because the output stream gets written to and flushed regularly as you generate your output. This means that when you attempt to get it at the end, you just have an empty output. Alternatively, if you capture it at the beginning, that just means that your consumption of the outputstream denies the same consumption to your client. This is why you have to copy it as in the answer above.

Send JSON body but with ContentType=application/x-www-form-urlencoded with ClientBuilder

I know the question is weird. Unfortunately I have a service that requires everything to have the header ContentType=application/x-www-form-urlencoded, eventhough the body is JSON
I am trying to use JAX-RS 2.0 ClientBuilder to call it:
String baseUrl = "http://api.example.com/";
JSONObject body = new JSONObject();
body.put("key", "value");
Client client = ClientBuilder.newClient();
client.register(new LoggingFilter());
Builder builder = client.target(baseUrl).path("something").request();
Invocation inv = builder
.header("Content-type", MediaType.APPLICATION_FORM_URLENCODED)
.buildPost(Entity.json(body));
Response response = inv.invoke();
int status = response.getStatus();
// I get 415, unsupported media type (in this case is unexpected)
I have checked my logs and I eventhough I am setting application/x-www-form-urlencoded (via the MediaType) the request appearantly has the Content-type of application/json
How can I force the request to have the Content-type I want?
BTW: This is my custom logger:
public class LoggingFilter implements ClientRequestFilter {
private static final Logger LOG = Logger.getLogger(LoggingFilter.class.getName());
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
LOG.log(Level.INFO, "body");
LOG.log(Level.INFO, requestContext.getEntity().toString());
LOG.log(Level.INFO, "headers");
LOG.log(Level.INFO, requestContext.getHeaders().toString());
}
}
And these are the logs I get:
com.acme.LoggingFilter I body
com.acme.LoggingFilter I {"key":"value"}
com.acme.LoggingFilter I headers
com.acme.LoggingFilter I {Content-type=[application/json]}
The problem with trying to use one of the static Entity helper methods is that it overrides any previous Content-Type header you may have set. In your current case, Entity.json automatically sets the header to application/json.
Instead of using the .json method, you can just use the general purpose Entity.entity(Object, MediaType) method. With your current case though, you can just do Entity.entity(body, MediaType.APPLICATION_FORM_URLENCODED_TYPE) though. The reason is that the client will look for a provider that knows how to serialize a JSONObject to application/x-www-form-urlencoded data, which there is none. So you will need to first serialize it to a String. That way the provider that handles application/x-www-form-urlencoded doesn't need to serialize anything. So just do
Entity.entity(body.toString(), MediaType.APPLICATION_FORM_URLENCODED_TYPE);

Categories