REST Response - logging payload via readEntity(String.class) fails - java

Consider this code for a REST proxy running under Jersey 2.22.2:
#POST
#Produces(MediaType.APPLICATION_JSON)
#Path("/foo")
public Response foo(String request) {
logger.info("Request to /foo :\n" + request);
WebTarget target = ClientBuilder.newClient().target(esbWsUrl).path("/foo");
Response response = target.request(MediaType.APPLICATION_JSON_TYPE).post(Entity.entity(request, MediaType.APPLICATION_JSON_TYPE));
// problem is in the 2 lines below
logger.info("Buffered: " + response.bufferEntity());
logger.info("Response from /foo :\n" + response.readEntity(String.class));
return response;
}
First I tried this without the response.bufferedEntity() and got IllegalStateException since response.readEntity(String.class) consumes from the underlying stream in the Response.
When I use response.bufferEntity(), I am able to call response.readEntity(String.class) multiple times (the buffering seems to work), yet the response returned by Jersey gives a zero bytes response.
$ curl -m 5 -i -X POST -d '{"input_values": "abc"}' http://localhost:9000/services/rest/foo
HTTP/1.1 200 OK
Date: Thu, 27 Oct 2016 08:18:09 GMT
Keep-Alive: timeout=20
X-Type: default
Server: nginx
Content-Type: application/json; charset=UTF-8
Content-Length: 364
curl: (28) Operation timed out after 5000 milliseconds with 0 out of 364 bytes received
Some relevent documentation is here: Response.readEntity
Can anyone please show me how to log the contents of the Response, before returning a valid Response ?
Maybe there is some stream handling issues I havent thought of, or perhaps another way to turn the Response entity to a string (without consuming the stream).

I ended up making a completely new Response, based on the status code and entity of the previous one.

Related

How to get response body of a post request without escape sequences in Java?

I am trying to convert the HttpResponse's response body to a String using the following piece of code:
HttpGet request = new HttpGet(url);
request.setHeader("Accept", "application/json");
response = client.execute(request);
EntityUtils.toString(response.getEntity(), standardCharsets.UTF_8.displayName());
The expected response after converting it to a String is as follows:
{"errors": ["permission denied"]}
This code was working fine until recently. But now the response body is somehow being modified to this:
{"errors":["1 error occurred:\n\t* permission denied\n\n"]}
I do not understand how the "1 error occurred" message is being inserted into the response body as the response from the same call in Postman gives me the appropriate response i.e.:
{"errors": ["permission denied"]}
And I've checked the code thoroughly and am certain that the "1 error occurred" message is not being inserted manually.
EDIT:
These were the response headers from Postman:
cache-control →no-store
content-length →33
content-type →application/json
date →Thu, 30 May 2019 06:52:33 GMT
status →403
And these were the response headers from the HttpClient call:
Cache-Control: no-store
Content-Type: application/json
Date: Thu, 30 May 2019 09:06:21 GMT
Content-Length: 60
The only difference is that the status code isn't being printed. But checking the status code through the response.getStatusLine().getStatusCode() yields 403 as well.
Looks like you are using Apache HttpClient, which does not modify responses this way. It's definitely the response generated by server. Check your configuration of request in Postman to see if there are any additional headers, that may alter server behavior.

Spring Boot: How to resolve Content-Type when incorrectly received from server

I have the following class in Java. I'm expecting it to issue a GET request to the url, get back a JSON payload, and transform that payload to List<LocationData>.
package ...
import ...
#Repository
public class ProxiedLocationRepo {
public List<LocationData> findAll() throws Exception {
RestTemplate restTemplate = new RestTemplate();
String url = UriComponentsBuilder
.fromUriString("https://my-host/path")
.queryParams("some", "queryParams")
.toUriString();
ResponseEntity<List<LocationData>> res = restTemplate.exchange(
url,
HttpMethod.GET,
null,
new ParameterizedTypeReference<List<LocationData>>(){});
if (res.getStatusCode() == HttpStatus.ACCEPTED) {
return res.getBody();
} else {
throw new ResponseStatusException(res.getStatusCode(), "Did not receive a 200 response from Server.");
}
}
}
However, I'm getting back this error:
org.springframework.http.InvalidMediaTypeException: Invalid mime type "charset=UTF-8": does not contain '/'
Which is expected, because if I do the same request from curl, and check the headers I get this (notice Content-Type line):
$ curl -sfi 'https://my-host/path?some=queryParams'
HTTP/1.1 200 OK
Server: nginx
Date: Wed, 06 Mar 2019 13:58:58 GMT
Content-Type: charset=UTF-8
Content-Length: 1821
Connection: keep-alive
Vary: Accept-Encoding
Strict-Transport-Security: max-age=31536000; includeSubDomains
X-Frame-Options: SAMEORIGIN
... # perfectly formatted JSON payload here
I know that the Content-Type returned from this server is going to be application/json, but it is not providing it to me.
Is there anyway to inform RestTemplate#exchange of what the Content-Type of the response will be? If not, is there any other methodology I could use to resolve this issue besides getting the owners of the server to set the Content-Type correctly?
EDIT:
I have also tried adding the "Accept" header but got the same results:
$ curl -sfi 'https://my-host/path?some=queryParams' \
-H 'Accept: application/json'
Unfortunately I don't think there's any way to fix this while leveraging the Spring framework. Even if you were to create a custom JsonbHttpMessageConverter that accepts a MIME type of ANY, Spring would still fail to parse the incorrect Content-Type received from the request (because it can't find "/" in the Content-Type string).
So the resolution here was to do use java.net.HttpURLConnection to do the networking instead, and then use com.fasterxml.jackson.databind.ObjectMapper to map from the JSON to a POJO.
It works, but at the cost of no longer being able to leverage any of Spring's HTTP handling, which is likely much much more robust than anything I can implement alone.

Jersey: how to set MediaType to javax.ws.rs.core.Response, if real "physical" response header does not have any information about it

When I send request to server
...
Response response = builder.method(req.getMethod(), Entity.entity(req, req.getMediaType())); // req.getMediaType() return MediaType.APPLICATION_XML
if(response.getStatus() != 200)
throw new CoreErrorException("core resulted error with status = " + response.getStatus());
T resp = response.readEntity(respType);
...
Jersey throw exception on the last line:
org.glassfish.jersey.message.internal.MessageBodyProviderNotFoundException: MessageBodyReader not found for media type=application/octet-stream
I did some investigation. First of all, I catch that response:
Content-Length: 93
Date: Thu, 21 Nov 2013 12:53:46 GMT
Server: APP
<root>
<returncode>XXX</returncode>
<desc>some description</desc>
</root>
Header does not contain any information about MediaType.
Indeed, when I try to call response.getMediaType(), it returns null.
I think, that is the problem. Jersey cann't detect MediaType of response and set it by default ("application/octet-stream"). But in really the body of my response is XML. Is there some way to tell Jersey about it?
Try to add Accept header into your request by calling WebTarget#request(MediaType),
ClientBuilder.newClient()
.target("mytarget")
.request("application/xml")
.method(req.getMethod(), Entity.entity(req, req.getMediaType()));
whether the server doesn't add Content-Type header to the response. If not you can try to change the headers on the client site using WriterInterceptor or simply by calling
Response response = ...;
response.getStringHeaders().putSingle(HttpHeaders.CONTENT_TYPE, "text/plain");
T resp = response.readEntity(respType);

Jersey Restful Testing with Junit

I am testing a DELETE verb in the following way which seems to work just fine.
#Test
public void testDelete() throws Exception {
WebResource webResourceTest = webResource.path("/deletesomestuff/delete").queryParam("FT","From Test");
final String responseString = webResourceTest.type(MediaType.TEXT_PLAIN).delete(String.class);
Assert.assertEquals("Request fulfilled.", responseString);
}
This returns what I am after in a String. Here is a snippet from the actual DELETE API call.
#DELETE
#Path("/delete")
#Produces(MediaType.TEXT_PLAIN)
public Response delete(#PathParam("/delete") String deleteString) {
<snip>
return Response.status(204).entity("Request fulfilled.").build();
}
The DELETE calls works fine as well and returns the correct String but my question is this. How can I get the response status returned via a WebResource? I just can't seem to find a way to retrieve that. The test works as-is but I am just curious to know if pulling the response from a WebResource is possible. Now when I use ClientResponse from my junit test I always receive a 200.
I have also tested the DELETE API call with a curl:
curl -i -X DELETE /webapi/deletesomestuff/delete?FT=From+Test
HTTP/1.1 204 No Content
Server: Apache-Coyote/1.1
Content-Type: text/plain
Date: Mon, 08 Jul 2013 18:11:13 GMT
A 204 was returned.
Thanks!
I guess this should work:
ClientResponse response = webResourceTest.type(MediaType.TEXT_PLAIN).delete(ClientResponse.class);
Assert.assertEquals(204, response.getStatus());

How to set/format HTTP headers by simply writing Strings to a socket

Just using the core Java API I am trying to send a response with correctly formatted HTTP headers. With my current code, I can see the client recieves my response, but I think I am not including all the information because when I a return a 404 error to a web browser client the web browser just displays a blank page. If I try with curl, I also do not see a curl letting me know about the error. Here is what I am currently doing with the response:
outputStream.write("HTTP/1.1 404 Not Found".getBytes());
An HTTP response is made up of three parts
The initial status line, which is what you are sending
The Response Header (with response headers like Content-Type, Content-Length, Last-Modified, Cookie, etc)
The Response Body - (The actual HTML with user-friendly content that tells the user what happened.
You are only sending the first.
An example of what you should be sending is
HTTP/1.1 404 Not Found
Date: Fri, 18 Aug 2012 13:24:45 GMT
Content-Type: text/HTML
Content-Length: 90
<html><head><title>Page not found</title></head><body>The page was not found.</body></html>
Also, don't forget to close the socket at the end.
This page describes the HTTP response in an easy way to understand.
EDIT
It's important to note that each line in the response must be separated by both the CR and LF characters. So you should also be writing \r\n to represent each newline before your call to getBytes().
When you have are giving a 404 error, you should include content for the browser to display. Include a Content-Length and Content-Type property, and then some html content for the browser to display. Also remember to flush the stream after writing to it. A proper response might look like this:
String response = "HTTP/1.1 404 Not Found\r\n" +
"Content-Length: 22\r\n" +
"Content-Type: text/html\r\n\r\n" +
"<h1>404 Not Found</h1>";
outputStream.write(response.getBytes());
outputStream.flush();
There should be a carriage return and a line feed(\r\n) after each header, and two \r\n's after the headers are done so you can indicate that the content is coming up.
Adding a newline and closing the connection helps my browser and curl to understand the response:
outputStream.write("HTTP/1.1 404 Not Found\n".getBytes());
outputStream.close();
...and a minimal "working" response:
outputStream.write("HTTP/1.1 200 OK\n\nHello, world!".getBytes())
outputStream.close()

Categories