Streaming data with REST - java

Using REST with RESTEasy and Swagger, is there any way to stream data back to the caller with a GET endpoint? I've seen a couple of examples where the entire stream can be returned, but I haven't seen any examples where the data can actually be streamed back. I also did have a look at this example(followed from this link-Return File From Resteasy Server) however, this example looks like it is returning a stream and expecting the caller to utilize the stream? Is this a correct assumption?:
#GET
#Produces(MediaType.APPLICATION_OCTET_STREAM)
#Path("/stream/test")
public Response getTestStream() {
String myName = "name";
InputStream stream = new ByteArrayInputStream(myName.getBytes(StandardCharsets.UTF_8));
return Response.ok().entity(stream).build();
}
But this does not seem to work for me. I get an exception: javax.ws.rs.NotAcceptableException: RESTEASY003635: No match for accept header.
Any help would be greatly appreciated!

You can return Object of inputstream object in Response.
For e.g.
#GetMapping(value = "/stream/test")
#ResponseBody
public ResponseEntity<?> getTestStream() {
String myName = "name";
InputStream stream = new ByteArrayInputStream(myName.getBytes(StandardCharsets.UTF_8));
HttpHeaders headers = new HttpHeaders();
headers.add("Cache-Control", "no-cache, no-store, must-revalidate");
headers.add("Pragma", "no-cache");
headers.add("Expires", "0");
return ResponseEntity
.ok()
.headers(headers)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(new InputStreamResource(stream));
}

Related

How to return binary data instead of base64 encoded byte[] in spring mvc rest controller

I want to return a generated pdf file via spring-mvc-rest controller. This is a shortened version of the code I'm currently using:
#RestController
#RequestMapping("/x")
public class XController {
#RequestMapping(value = "/", method = RequestMethod.GET)
public ResponseEntity<byte[]> find() throws IOException {
byte[] pdf = createPdf();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(new MediaType("application", "pdf"));
headers.setContentDispositionFormData("attachment", "x.pdf");
headers.setContentLength(pdf.length);
return new ResponseEntity<byte[]>(pdf, headers, HttpStatus.OK);
}
}
This works almost fine, it just to return the actual byte array as base64 encoded :(
curl -i 'http://127.0.0.1:8080/app/x'
Server: Apache-Coyote/1.1
Content-Disposition: form-data; name="attachment"; filename=x.pdf"
Content-Type: application/pdf
Content-Length: 138654
Date: Fri, 08 Jan 2016 11:25:38 GMT
"JVBERi0xLjYNJeLjz9MNCjMyNCAwIG9iag [...]
(btw. the response doesn't even contain a closing " :)
Any hints appreciated!
The problem is caused by Spring trying to encode the response as Json.
Your request probably specifies Accepts = "*/*" and since Spring ignores the ResponseEntity's ContentType, the best encoding is found to be application/json.
The simplest fix to this is to add a produces to your request mapping, so your code becomes:
#RestController
#RequestMapping(value = "/x",
produces = "application/pdf") // <-- Add this
public class XController {
#RequestMapping(value = "/", method = RequestMethod.GET)
public ResponseEntity<byte[]> find() throws IOException {
byte[] pdf = createPdf();
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_PDF);
headers.setContentDispositionFormData("attachment", "x.pdf");
headers.setContentLength(pdf.length);
return new ResponseEntity<byte[]>(pdf, headers, HttpStatus.OK);
}
}
I created the example using your code, but a very similar method is doing his job in my web application:
#RequestMapping(value = "/", method = RequestMethod.GET)
public void downloadFile(HttpServletResponse response,
HttpServletRequest request) throws IOException
{
byte[] pdf = createPdf();
response.setContentType("application/x-download");
response.setHeader("Content-Disposition", "attachment; filename=foo.pdf");
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.getOutputStream().write(pdf);
}
Else you can try this answer Open ResponseEntity PDF in new browser tab
This is my code and work fine, maybe this would can help you.
#RequestMapping(value = "/createReport", method = RequestMethod.POST,produces="application/pdf")
#ResponseStatus(value = HttpStatus.OK)
public ResponseEntity<byte[]> createReport(#RequestBody ReporteDTO reporteDTO) {
byte[] outputReport = null;
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType("application/pdf"));
headers.setContentDispositionFormData("inline", "archivo.pdf");
headers.setCacheControl("must-revalidate, post-check=0, pre-check=0");
outputReport = getFilePdf();
ResponseEntity<byte[]> response = new ResponseEntity<byte[]>(outputReport, headers, HttpStatus.OK);
return response;
}
Add produces property to RequestMapping:
#RequestMapping(path = "/download", produces = "application/pdf")
The answer from Rasmus Faber is very close.
In my case: I want to view a PDF generated by spring. I don't want to download it.
But I get every time the base64 encoded string, instead of my byte[].
I found out that I get the base64-string only in Firefox. So I switched to chrome, where the answer from Rasmus working fine.
Now, it works in Firefox too. But I can't say why (I changed nothing). I guess that something was cached.
For more information, this might help Incorrect content type for PDF file with Firefox
My working code with streams:
#GetMapping(path = "/demo/pdf", produces = "application/pdf")
public StreamingResponseBody getPdf(final HttpServletResponse response) {
response.setContentType("application/pdf");
response.setHeader("cache-control","must-revalidate, post-check=0, pre-check=0");
response.setHeader("Content-Disposition", "inline; filename=aCleverFileName.pdf");
return outputStream -> this.pdfService.createPdf(outputStream);
}

How to serve huge file over streaming rest?

I have 2 Spring Web applications: Application1 and Application2. In Application1, I have an endpoint at "http://application1/getbigcsv" that uses streaming in order to serve a gigantic 150MB CSV file back to the user if they hit that URL.
I dont want users to hit Application1 directly, but hit Application2 instead.
If I have the following method in my controller in Application2
#RequestMapping(value = "/large.csv", method = GET, produces = "text/csv")
#ResponseStatus(value = HttpStatus.OK)
public String streamLargeCSV() {
// Make an HTTP Request to http://application1/getbigcsv
// Return its response
}
My worry is the above is not doing "streaming" whereas Application1 is doing streaming. Is there some way I can make sure that the application2 will be serving back the same data from application1's rest endpoint in a streaming fashion? Or is the method above actually returning things in a "Streaming" method already because Application1 is serving its endpoint as streaming?
First of all: you can but not with that method signature.
Unfortunately, you have not shown how you produce that CSV file in app1, whether this is truly streaming. Let's assume it is.
You signature will look like this:
#RequestMapping(value = "/large.csv", method = GET, produces = "text/csv")
#ResponseStatus(value = HttpStatus.OK)
public void streamLargeCSV(OutputStream out) {
// Make an HTTP Request to http://application1/getbigcsv
// Return its response
}
Now we have to grab the input stream from app1 first. Use Apache HttpClient to get your HttpEntity. This entity has a writeTo(OutputStream) method which will receive your out parameter. It will block until all bytes are consumed/streamed. When you are done, free all HttpClient resources.
Complete code:
#RequestMapping(value = "/large.csv", method = GET, produces = "text/csv")
#ResponseStatus(value = HttpStatus.OK)
public void streamLargeCSV(OutputStream out) {
// Make an HTTP Request to http://application1/getbigcsv
CloseableHttpClient httpclient = HttpClients.createDefault();
HttpGet httpGet = new HttpGet("http://application1/getbigcsv");
CloseableHttpResponse response = httpclient.execute(httpGet);
try {
HttpEntity entity = response.getEntity();
// Return its response
entity.writeTo(out);
} finally {
response.close();
}
}
Here is my real world example. Start reading from "Interesting to say what I have achieved in particular with this:"
In java.ws.rs.core package you have classes: StreamingOutput and ResponseBuilder.
Not sure if it will help you, but you may try.
Example:
#Produces("application/octet-stream")
public Response doThings () {
...
StreamingOutput so;
try {
so = new StreamingOutput() {
public void write(OutputStream output) {
…
}
};
} catch (Exception e) {
...
}
ResponseBuilder response = Response.ok(so);
response.header("Content-Type", ... + ";charset=utf-8");
return response.build();
}
Change your methods return type to ResponseEntity<?> and return as following:
#GetMapping("/download")
public ResponseEntity<?> fetchActivities(
#RequestParam("filename") String filename) {
String string = "some large text"
InputStream is = new ByteArrayInputStream(string.getBytest());
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=large.txt");
headers.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_OCTET_STREAM_VALUE);
return ResponseEntity.ok().headers(headers).body(new InputStreamResource(is));
}

Why RestTemplate GET response is in JSON when should be in XML?

I struggled with an extrange spring behavior using RestTemplate (org.springframework.web.client.RestTemplate) without success.
I use in my hole application below code and always receive an XML response, which I parse and evaluate its result.
String apiResponse = getRestTemplate().postForObject(url, body, String.class);
But can't figure out why a server response is in JSON format after executing:
String apiResponse = getRestTemplate().getForObject(url, String.class);
I've debugged at low level RestTemplate and the content type is XML, but have no idea why the result is in JSON.
When I access from a browser the response is also in XML, but in apiResponse I got JSON.
I tried many options after reading Spring documentation
http://docs.spring.io/spring/docs/3.0.x/api/org/springframework/web/client/RestTemplate.html
Also tried to modify explicitly the headers but still can't figure it out.
I debugged RestTemplate class and noticed that this method is always setting application/json:
public void doWithRequest(ClientHttpRequest request) throws IOException {
if (responseType != null) {
List<MediaType> allSupportedMediaTypes = new ArrayList<MediaType>();
for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {
if (messageConverter.canRead(responseType, null)) {
List<MediaType> supportedMediaTypes = messageConverter.getSupportedMediaTypes();
for (MediaType supportedMediaType : supportedMediaTypes) {
if (supportedMediaType.getCharSet() != null) {
supportedMediaType =
new MediaType(supportedMediaType.getType(), supportedMediaType.getSubtype());
}
allSupportedMediaTypes.add(supportedMediaType);
}
}
}
if (!allSupportedMediaTypes.isEmpty()) {
MediaType.sortBySpecificity(allSupportedMediaTypes);
if (logger.isDebugEnabled()) {
logger.debug("Setting request Accept header to " + allSupportedMediaTypes);
}
request.getHeaders().setAccept(allSupportedMediaTypes);
}
}
}
Could you give an idea?
I could solve my issue with RC.'s help. I'll post the answer to help other people.
The problem was that Accept header is automatically set to APPLICATION/JSON so I had to change the way to invoke the service in order to provide the Accept header I want.
I changed this:
String response = getRestTemplate().getForObject(url, String.class);
To this in order to make the application work:
// Set XML content type explicitly to force response in XML (If not spring gets response in JSON)
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_XML));
HttpEntity<String> entity = new HttpEntity<String>("parameters", headers);
ResponseEntity<String> response = getRestTemplate().exchange(url, HttpMethod.GET, entity, String.class);
String responseBody = response.getBody();

spring mvc jquery ajax response as json encoding issue

Recenlty I have big problem with Polish Characters in JSON response from the server. I have simple Ajax request for this:
jQuery.ajax( "/GetSimpleRuleList",
{
type:"GET",
responseType:"application/json;charset=utf-8",
contentType:"application/json;charset=utf-8",
cache:false
} ).done( function ( data )
{
console.log( data );
//nevermind here
} );
And appropriate Controller at server end:
#RequestMapping(value = "/GetSimpleRuleList", method = RequestMethod.GET)
public
#ResponseBody
String getRuleList( ServletResponse response )
{
//magically getting my list here
response.setCharacterEncoding( "UTF-8" );
return //Using JACKSON ObjectWriter here
}
Now I'm 100% sure that encoidng on server side and database from where I take data from is OK, no problem with that.
But when It comes to reading response from server it is:
???
instead of Polish char like:
ąćź
Moreover it only fails when receiving response from server, while sending a request with data is encoded correctly.
In my web.xml I have filter for character encoding.
Any help with this? I'm out of ideas.
Now I'm 100% sure that encoidng on server side and database from where I take data from is OK
try adding the Content-Type header if it's not already present int your response:
response.setHeader("Content-Type", "application/json;charset=UTF-8")
Get sure to use UTF-8 charset when reading from database. Jackson's encoding defaults to UTF-8, so your data might not be encoded using UTF-8?!?
what encoding do you use when reading from database? maybe ISO-8859-2?
Try changing your response type to org.springframework.http.ResponseEntity
public ResponseEntity<String> getRuleList(){
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.add("Content-Type", "application/json; charset=utf-8");
responseHeaders.setCacheControl("no-cache, max-age=0");
String allyourjson = "yourjsongoeshere";
return new ResponseEntity<String>(allyourjson, responseHeaders, HttpStatus.OK);
}
You can use spring annotation RequestMapping above controller class for receveing application/json;utf-8 in all responses
#Controller
#RequestMapping(produces = {"application/json; charset=UTF-8","*/*;charset=UTF-8"})
public class MyController{
...
#RequestMapping(value = "/GetSimpleRuleList", method = RequestMethod.GET)
public
#ResponseBody
String getRuleList( ServletResponse response )
{
//magically getting my list here
response.setCharacterEncoding( "UTF-8" );
return //Using JACKSON ObjectWriter here
}
...
}

How do I read the response header from RestTemplate?

I am posting information to a web service using RestTemplate.postForObject. Besides the result string I need the information in the response header. Is there any way to get this?
RestTemplate template = new RestTemplate();
String result = template.postForObject(url, request, String.class);
Ok, I finally figured it out. The exchange method is exactly what i need. It returns an HttpEntity which contains the full headers.
RestTemplate template = new RestTemplate();
HttpEntity<String> response = template.exchange(url, HttpMethod.POST, request, String.class);
String resultString = response.getBody();
HttpHeaders headers = response.getHeaders();
Best thing to do whould be to use the execute method and pass in a ResponseExtractor which will have access to the headers.
private static class StringFromHeadersExtractor implements ResponseExtractor<String> {
public String extractData(ClientHttpResponse response) throws
{
return doSomthingWithHeader(response.getHeaders());
}
}
Another option (less clean) is to extend RestTemplate and override the call to doExecute and add any special header handling logic there.
HttpEntity<?> entity = new HttpEntity<>( postObject, headers ); // for request
HttpEntity<String> response = template.exchange(url, HttpMethod.POST, entity, String.class);
String result= response.getBody();
HttpHeaders headers = response.getHeaders();
I don't know if this is the recommended method, but it looks like you could extract information from the response headers if you configure the template to use a custom HttpMessageConverter.

Categories

Resources