I have a controller method that simply streams bytes for media (images, css, js, etc.) to the client. I first tried something like this:
#RequestMapping(value="/path/to/media/**", method=RequestMethod.GET)
#ResponseBody
public byte[] getMedia(HttpServletRequest request) throws IOException
{
//logic for getting path to media on server
return Files.readAllBytes(Paths.get(serverPathToMedia));
}
I originally tested this in Firefox, and it all seemed to work fine. However, I then tried it in Chrome, and then found that none of the images work. So, I then changed it to something like this:
#RequestMapping(value="/path/to/media/**", method=RequestMethod.GET)
public ResponseEntity<byte[]> getMedia(HttpServletRequest request) throws IOException
{
//logic for getting path to media on server
byte[] bytes = Files.readAllBytes(Paths.get(serverPathToMedia));
//logic for setting some header values like Content-Type and Content-Length
return new ResponseEntity<byte[]>(bytes, headers, HttpStatus.OK);
}
This gave the same results as before. I saw in the developer tools that my response headers were coming down as expected, but still no image bytes
Next I tried something like this:
#RequestMapping(value="/path/to/media/**", method=RequestMethod.GET)
public void getMedia(HttpServletRequest request, HttpServletResponse response) throws IOException
{
//logic for getting path to media on server
byte[] bytes = Files.readAllBytes(Paths.get(serverPathToMedia));
response.getOutputStream().write(bytes);
}
Without even setting any response headers, this works in Firefox and Chrome. Now, while I can just do it this last way since it works, this doesn't seem like the correct Spring MVC way. I want to know why the first two things I tried didn't work, as they seem more correct. Also, is there something I didn't try that would actually be the right way to do this?
Your last approach is pretty much the way to go about it. The only change that I can suggest is to not keep the entire content file to be streamed in memory, instead to stream out the content with buffering - IOUtils from Apache commons can do this for you.
Related
I'm currently trying to use wiremock to mock the result of http calls in my unit tests, and when I try to get the response body, I got some encoding issues.
I write one methode to stub a post methode
public static void setupMockExecutionResponse(WireMockServer mockService) throws IOException {
mockService.stubFor(WireMock.post(WireMock.urlEqualTo("/reportExecutions"))
.willReturn(WireMock.aResponse()
.withStatus(HttpStatus.OK.value())
.withHeader("Content-Type", MediaType.APPLICATION_JSON_VALUE+"; charset=utf-8")
.withHeader("set-cookie", "JSESSIONID=1111111111111; SERVERID=jasper")
.withBody(
copyToString(
JasperClientMock.class.getClassLoader().getResourceAsStream("payload/execution-response.json"),
StandardCharsets.UTF_8))));
}
As you can see I specified that the charset of the response body is UTF-8, and in the header I add it too.
The json file used as the response is also encoded in UTF-8
{
"status":"ready",
"totalPages":1,
"requestId":"217f7dc9-47c4-4c44-bada-7e29b653887b",
"reportURI":"/test/test/test/test/export_test",
"exports":[
{
"status":"ready",
"outputResource":{
"contentType":"application/pdf",
"fileName":"export_test.pdf",
"outputFinal":true,
"outputTimestamp":0
},
"id":"6ca0038f-94ff-4bd9-bdf4-6a35259fd05e"
}
]
}
I expect when I make the post request to get the header specified in the setUp of the mock and the json string corresponding at my file.
feign.Response r = jasperFeignClient.executeReport(headerMap, requestBody);
checkResponseStatut(r.status(), EXECUTION_STEP, documentJasperRequest.getUrlReport(), requestBody);
getCookie(r, cookies);
String execResponse = IOUtils.toString(r.body().asInputStream(), String.valueOf(StandardCharsets.UTF_8));
I have the good return code (200), the cookies that I set, but my response body is messed up and look like this
So when I try to convert it to a java object I have parsing error, because it can't find the begining character of the json.
I even tryed to hard code the json string directly in the body of the stub, but that didn't change a things.
EDIT
When I make a direct call to the stubbed endpoint in my test, the encoding is good
#Test
public void testGetPDF(){
Response response = given().when().post("http://localhost:9561/reportExecutions");
String status = response.jsonPath().get("status");
System.out.println(status);
assertEquals(status, "ready");
}
It's only when I go into the class where the call is made that i got encoding issues.
It looks like your call via the Jasper client is by default setting accept-encodig:gzip (or something similar) and WireMock is therefore returning a gzipped body.
You should be able to resolve the issue by doing one of: a) disabling gzip in WireMock, b) disabling gzip in your client config, or c) ungzipping the body before attempting to parse it.
After updating all my pom dependencies to their latest version solved this encoding issue.
I have a large file download that is served by a RestController on one server, that I need to stream through a RestController on another server. When calling the end server directly the result streams fine. However when using RestTemplate to call this server and then write the response to an OutputStream, the response is buffered on the front server until the whole file is ready, and then streamed. Is there a way I can write the file to an OutputStream as it comes in?
At the moment my code on the front server looks similar to this
#ResponseBody
public void downloadResults(HttpServletRequest request, HttpServletResponse response, #RequestParam("id") String jobId, OutputStream stream)
throws IOException
{
byte[] data = restTemplate.exchange("http://localhost/getFile", HttpMethod.POST, requestEntity, byte[].class, parameters).getBody();
stream.write(data);
}
I've set my RestTemplate to not buffer and I've verified that this is working by checking the Request type that is used, (SimpleStreamingClientHttpRequest).
The data all comes back correct, its just only written to the stream all at once, rather than as it comes in
RestTemplate is not meant for streaming the response body, as pointed out in this JIRA issue.
You can use restTemplate.execute. See https://www.baeldung.com/spring-resttemplate-download-large-file
I'm trying to create an application that download and uploads large files, so I don't want the file contents to be stored in memory.
On the mvc controller side I'm using an http message converter that converts to / from InputStream
#Override
public InputStream read(Class<? extends InputStream> clazz, HttpInputMessage inputMessage) throws IOException,
HttpMessageNotReadableException {
return inputMessage.getBody();
}
#Override
public void write(InputStream t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException,
HttpMessageNotWritableException {
try {
IOUtils.copy(t, outputMessage.getBody());
} finally {
IOUtils.closeQuietly(t);
}
}
This works well on the server side.
On the client (RestTemplate) side I tried to use the same converter, but I got an exception that the stream has been closed (probably closed when the request was completed).
Client side code:
ResponseEntity<InputStream> res = rest.getForEntity(url, InputStream.class);
// res.getBody() is closed
I've also tried to copy the input stream into a buffer and create a new ByteArrayInputStream and return it to the RestTemplate client and it worked well, however it does require that the data will be read into memory which doesn't suite my demands.
My question is how to keep the stream open until I process it without having to read it all into memory / file?
Any idea will be appreciated.
Regards, Shay
As far as I am aware, RestTemplate's getForEntity() is not an appropriate way to get an InputStream. It's a convenience for converting to and from entity classes, so presumably that's where your problem lies.
Since you are used to HttpInputMessage, why don't you use HttpInputMessage.getBody() on the client side as well? It gets you a nice InputStream, which would be ready for passing straight to an OutputStream such as HttpServletResponse.getOutputStream().
Check how Spring MVC handles large files upload with org.springframework.web.multipart.commons.CommonsMultipartResolver. It has a 'maxInMemorySize' that can help control the memory requirements. See this thread for using a multipart resolver with the REST template Sending Multipart File as POST parameters with RestTemplate requests
I am a newbie with Spring MVC but I'm quite impressed with its capabilities.
I am using 3.1.0-RELEASE and I have to show a PDF in response to form:form submission.
Here is the (small) code I wrote in the controller:
#RequestMapping(value = "new_product", method = RequestMethod.POST, params = "print")
#ResponseBody
public void saveAndShowPDF(ModelMap map, ShippingRequestInfo requestInfo, HttpServletRequest request, HttpServletResponse httpServletResponse) throws IOException {
saveProductChanges(map, requestInfo, request, httpServletResponse);
httpServletResponse.setContentType("application/pdf");
byte[] pdfImage = productService.getPDFImage(requestInfo.getRequestId());
httpServletResponse.getOutputStream().write(pdfImage);
}
This code sends the PDF byte[] back to the original window.
How do I get the PDF to be shown in a separate window so that I can still have the original browser window to show some other content? The best way would be to have the PDF shown using the client PDF view program (Adobe Reader, FoxIt etc.) but I would be fine with the PDF showing up in a separate browser window.
EDIT:
I decided to set the Content-Disposition so that the browser brings up a save/open box where the user can open Adobe (with losing the main browser page).
httpServletResponse.setHeader("Content-Disposition","attachment;filename=cool.pdf");
Thanks everyone!
Specify target="_blank" in the form:form tag that submits your form.
You would do that on the client side with some javascript e.g.,:
My Super Awesome Docment
(substitute with pretty jQuery-ness as desired) If you want something else to happen in the main window just don't return false from the onClick event, and let the regular click do whatever thing it is that you want to happen in the main window
It's not up to you if PDFs open in a browser window or in Adobe, that's configuration on the user's computer.
--
Also, just as a Spring thing, #ResponseBody on a void method doesn't make any sense. #ResponseBody is telling spring to use the method's return type as the Response. That is, you would return the byte[] from the method and let spring deal with turning it into a servlet response. Rather than writing the response directly yourself inside the method
Use the #RequestMapping produces to define the reponse type.
#RequestMapping(value = doc/{docId}, method = RequestMethod.GET, produces = "application/pdf")
#ResponseBody
public byte[] getDocument(#PathVariable("docId") String docId) throws Exception
{
return service.getDocument(docId);
}
My Servlet just won't use UTF-8 for JSON responses.
MyServlet.java:
public class MyServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse res) throws Exception {
PrintWriter writer = res.getWriter();
res.setCharacterEncoding("UTF-8");
res.setContentType("application/json; charset=UTF-8");
writer.print(getSomeJson());
}
}
But special characters aren't showing up, and when I check the headers that I'm getting back in Firebug, I see Content-Type: application/json;charset=ISO-8859-1.
I did a grep -ri iso . in my Servlet directory, and came up with nothing, so nowhere am I explicitly setting the type to ISO-8859-1.
I should also specify that I'm running this on Tomcat 7 in Eclipse with a J2EE target as a development environment, with Solaris 10 and whatever they call their web server environment (somebody else admins this) as the production environment, and the behavior is the same.
I've also confirmed that the request submitted is UTF-8, and only the response is ISO-8859-1.
Update
I have amended the code to reflect that I am calling PrintWriter before I set the character encoding. I omitted this from my original example, and now I realize that this was the source of my problem. I read here that you have to set character encoding before you call HttpServletResponse.getWriter(), or getWriter will set it to ISO-8859-1 for you.
This was my problem. So the above example should be adjusted to
public class MyServlet extends HttpServlet {
protected void doPost(HttpServletRequest req, HttpServletResponse res) throws Exception {
res.setCharacterEncoding("UTF-8");
res.setContentType("application/json");
PrintWriter writer = res.getWriter();
writer.print(getSomeJson());
}
}
Once the encoding is set for a response, it cannot be changed.
The easiest way to force UTF-8 is to create your own filter which is the first to peek at the response and set the encoding.
Take a look at how Spring 3.0 does this. Even if you can't use Spring in your project, maybe you can get some inspiration (make sure your company policy allows you to get inspiration from open source licenses).
The code looks fine. Either you're not running the code you think you're running, or there's some Filter or proxy somewhere in the request-response chain which modifies the content type like that.
Aside from specific problem, you really should consider getting output stream, using JSON library to write contents directly as UTF-8 encoded JSON; there is no benefit to using writers.
Some JSON packages only work with strings, which is unfortunate, but most allow using more efficient streams (safer and more efficient as parser/generator can handle escaping and encoding aspects together).