How to serve huge file over streaming rest? - java

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));
}

Related

Handling of various exception cases in rest template call

I am making rest template call to get the data from other microservice for this I am using the exchange method. This I am doing when a particular function gets called and below is the sample code for the same.
#Service
public void findUserById()
{
String username = "chathuranga";
String password = "123";
Integer userId = 1;
String url = "http://localhost:8080/users/" + userId;
//setting up the HTTP Basic Authentication header value
String authorizationHeader = "Basic " + DatatypeConverter.printBase64Binary((username + ":" + password).getBytes());
HttpHeaders requestHeaders = new HttpHeaders();
//set up HTTP Basic Authentication Header
requestHeaders.add("Authorization", authorizationHeader);
requestHeaders.add("Accept", MediaType.APPLICATION_JSON_VALUE);
//request entity is created with request headers
HttpEntity<AddUserRequest> requestEntity = new HttpEntity<>(requestHeaders);
ResponseEntity<FindUserResponse> responseEntity = restTemplate.exchange(
url,
HttpMethod.GET,
requestEntity,
FindUserResponse.class
);
// if (responseEntity.getStatusCode() == HttpStatus.OK) {
// System.out.println("response received");
System.out.println(responseEntity.getBody());
//} else {
// System.out.println("error occurred");
// System.out.println(responseEntity.getStatusCode());
//}
}
To handle the various exceptions code for example 500, 404 I want to made resttemplate builder class, (not the commented code) Which must be coded in different class for this I am referring this (custom hadler part)
I am not using try catch as it is not good approach when multiple calls happen in production environment.
I am also getting resource access exception while using exchange function which also needs to handle.
Now I am not getting how this class of custom handler should be called for handling response like 500.
If someone can help me with the sample code that would be very helpfull as I cannot test my code because it is not deployed for testing purpose till now
here is a sample
#ControllerAdvice
public class ErrorHandler {
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ExceptionHandler(ResourceAccessException.class)
public #ResponseBody
String handleResourceAccessException(
ResourceAccessException ex) {
return "internal server error";
}
}
When you use #ControllerAdvice , it will catch the exception you mention in #ExceptionHandler and here you can handle it the way you want.
If you don't want to return the response to the client right away, (for example, ignore ResourceAccessException and continue), you can override the handleError method of DefaultResponseErrorHandler, which is used by RestTemplate to handle the non 2xx codes.
public class ErrorHandler extends DefaultResponseErrorHandler {
#Override
public void handleError(ClientHttpResponse response, HttpStatus statusCode) {
// write your code here
}
}

SpringBoot how to Send response to other URL

I have the following code:
#RequestMapping(
consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE},
path = "api/api1",
method = RequestMethod.POST,
produces = MediaType.ALL_VALUE
)
public ResponseEntity<?> api1CallBack(#RequestBody String requestBody, HttpServletRequest request) throws IOException, GeneralSecurityException, URISyntaxException {
String response="{SOME_JSON}";
URI callbackURL = new URI("http://otherAPIEnv/api2");
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setLocation(callbackURL);
return new ResponseEntity<String>(response,httpHeaders, HttpStatus.OK);
}
I tried the above code, but when I hit the api1 through my curl I get the response on the same machine, but I want the response to be redirected to api2 at otherAPIEnv machine.
Could someone please suggest how to achieve this kind of request and response?
When you send a request to a URL it should respond to the same otherwise client will be in waiting for it until it times out.
So, the approach should be different in this scenario.
First, in your main rest API you have to send a response code to release the client.
Then, in the API method you have to call another method asynchronously which calls api2 and performs the desired operation.
Here is a simple example.
#Autowired
API2Caller api2Caller;
#RequestMapping(
consumes = {MediaType.APPLICATION_FORM_URLENCODED_VALUE},
path = "api/api1",
method = RequestMethod.POST,
produces = MediaType.ALL_VALUE
)
#ResponseStatus(HttpStatus.ACCEPTED)
public void api1CallBack(#RequestBody String requestBody, HttpServletRequest request) throws IOException, GeneralSecurityException, URISyntaxException {
api2Caller.callApi2(requestBody);
}
and the APICaller should look like following
#Component
public class API2Caller {
#Async
public SomeResultPojo callApi2() {
// use RestTemplate to call the api2
return restTemplate.postForObject("http://otherAPIEnv/api2", request, SomeResultPojo.class);
}
}
But you can choose your most comfortable way to perform asynchronous operation.
Look like a job for redirect.
String redirectMe() {
return "redirect:http://otherAPIEnv/api2"
}
As for the curl. You have POST mapping of the method so be sure to try it with curl -X POST... or change it to GET.
This the more modular and more generic way to do such kind of things:
public #ResponseBody ClientResponse updateDocStatus(MyRequest myRequest) {
ClientResponse clientResponse = new ClientResponse(CTConstants.FAILURE);
try {
HttpHeaders headers = prepareHeaders();
ClientRequest request = prepareRequestData(myRequest);
logger.info("cpa request is " + new Gson().toJson(request));
HttpEntity<ClientRequest> entity = new HttpEntity<ClientRequest>(request, headers);
String uri = cpaBaseUrl + updateDocUrl ;
ClientResponse serviceResponse = Utilities.sendHTTPRequest(uri, entity);
clientResponse = serviceResponse;
if (serviceResponse != null) {
if (CTConstants.SUCCESS.equalsIgnoreCase(serviceResponse.getStatus())) {
clientResponse.setStatus(CTConstants.SUCCESS);
clientResponse.setMessage(" update success.");
}
}
} catch (Exception e) {
logger.error("exception occurred ", e);
clientResponse.setStatus(CTConstants.ERROR);
clientResponse.setMessage(e.getMessage());
}
return clientResponse;
}
public static ClientResponse sendHTTPRequest(String uri, HttpEntity<ClientRequest> entity) {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new SimpleClientHttpRequestFactory());
SimpleClientHttpRequestFactory rf = (SimpleClientHttpRequestFactory) restTemplate.getRequestFactory();
rf.setReadTimeout(CTConstants.SERVICE_TIMEOUT);
rf.setConnectTimeout(CTConstants.SERVICE_TIMEOUT);
ParameterizedTypeReference<ClientResponse> ptr = new ParameterizedTypeReference<ClientResponse>() {
};
ResponseEntity<ClientResponse> postForObject = restTemplate.exchange(uri, HttpMethod.POST, entity, ptr);
return postForObject.getBody();
}
You need to use redirect and modify the return type of your method
public String api1CallBack(#RequestBody String requestBody, HttpServletRequest request) throws IOException {
return "redirect:http://otherAPIEnv/api2";
}

Calling remote method in spring with parameters

I have a class annoted with Service Annotation on server 1 .
#Service
public class MainHandler implements AbstractHandler {
#Autowired
private ServiceLocal defaultService;
#Override
public boolean execute(HttpServletRequest request, HttpServletResponse response) throws MsisdnServiceException {
System.out.println("The default Request" + request);
}
}
I want to call this method from other remote server after passing the request and get the response from this , what is the way to do in spring .
Invoking methods remotely would be using a technology called RMI, which you can google easily.
However, since you want to use HttpServletRequest and HttpServletResponse, you probably should write an Http Controller using Spring MVC. For that you can also google and very easily find excellent tutorials and guides.
You can use spring's RestTemplate to make communication with the servers.
First you need to create a controller on server 1 backend to get data from server 2:
#RestController
public class MyController {
#RequestMapping(value = "/endpoint", method = RequestMethod.POST)
String execute(#RequestBody MyClass object) {
System.out.println("Your data" + object);
}
}
On server 2 backend create a method that make a REST call to server 1's endpoint with RestTemplate:
void request() {
String url = "http://localhost:8080/endpoint";
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("Content-Type", "application/json");
JSONObject json = new JSONObject();
json.put("name", "yourName");
json.put("email", "name#gmail.com");
HttpEntity < String > httpEntity = new HttpEntity < String > (json.toString(), httpHeaders);
RestTemplate restTemplate = new RestTemplate();
String response = restTemplate.postForObject(url, httpEntity, String.class);
}

How to send response before actions in spring mvc

Say that my spring controller function receives a large amount of data.
I want to return 200 OK, given that the data is structured right, and after that I want to perform the processing, which might take a while.
To my understanding the only way to send response is by return command. But I don't want to end the function on response send.
Are there other ways to send response to client at the middle of the function?
Creating a new thread run is obvious but other languages (JS) let you handle it more elegantly.
#RequestMapping(value = Connectors.CONNECTOR_HEARTBEAT, method = RequestMethod.POST)
public ResponseEntity<String> doSomething(#RequestBody List<Message> messages) {
HttpStatus code = (messages!=null && !messages.isEmpty()) ? HttpStatus.OK
: HttpStatus.NOT_FOUND;
return new ResponseEntity<String>(res, code);
// how do I add code here??
}
You can of course do processing after sending the response. The more general way would be to use the afterCompletion method of a HandlerInterceptor. By construction, it will be executed after the response have been sent to client, but it forces you to split you logic in 2 components the before part in controller, and the after part in the interceptor.
The alternative way is to forget Spring MVC machinery and manually commit the response in the controller:
#RequestMapping(value = Connectors.CONNECTOR_HEARTBEAT, method = RequestMethod.POST)
public void doSomething(#RequestBody List<Message> messages, HttpServletResponse response) {
int code = (messages!=null && !messages.isEmpty()) ? HttpServletResponse.SC_OK
: HttpServletResponse.SC_NOT_FOUND;
if (code != HttpServletResponse.SC_OK) {
response.sendError(code, res);
return;
}
java.io.PrintWriter wr = response.getWriter();
response.setStatus(code);
wr.print(res);
wr.flush();
wr.close();
// Now it it time to do the long processing
...
}
Note the void return code to notify Spring that the response have been committed in the controller.
As a side advantage, the processing still occurs in the same thread, so you have full access to session scoped attributes or any other thread local variables used by Spring MVC or Spring Security...
You can use #Async
#RequestMapping(value = Connectors.CONNECTOR_HEARTBEAT, method =
RequestMethod.POST)
public ResponseEntity<String> doSomething(#RequestBody List<Message>
messages) {
do();
HttpStatus code = (messages!=null && !messages.isEmpty()) ? HttpStatus.OK
: HttpStatus.NOT_FOUND;
return new ResponseEntity<String>(res, code);
}
#Async
void do(){
//your code
}
this work in java 8
I guess you mau use the async mechanism of spring
Async methods have been introduced in servlet 3.0 and Spring offers some support to them
Basically... you make a request; the request is handled by the server and then, in background, a new thread manages the requesta data
Here a useful link (at least i hope :) ) http://spring.io/blog/2012/05/10/spring-mvc-3-2-preview-making-a-controller-method-asynchronous/
You should use the HandlerInterceptor. But the code get a little bit more complex than expected. So, here's a code suggestion to make it simpler by putting the whole solution in a single class:
#RequestMapping(value = Connectors.CONNECTOR_HEARTBEAT, method = RequestMethod.POST)
public ResponseEntity<String> doSomething(#RequestBody List<Message> messages) {
HttpStatus code = (messages!=null && !messages.isEmpty()) ? HttpStatus.OK
: HttpStatus.NOT_FOUND;
result.set(res); // Save the object to be used after response
return new ResponseEntity<String>(res, code);
}
private static final ThreadLocal<String> result = new ThreadLocal<String>();
#Bean
public HandlerInterceptor interceptor() {
return new HandlerInterceptor() {
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// Get the saved object and clean for the next request
String res = result.get();
result.set(null);
// TODO Your code to be executed after response.
}
};
}

How can I return Apache's HttpResponse within Spring's HttpServletResponse?

I have an endpoint using Spring:
#RequestMapping(value = {"/hello"}, method = RequestMethod.GET)
#ResponseBody
public String getContent(#RequestParam(value = "url", required = true) String url)
I would like this to return the exact same response I would get if I send a GET to url. I'm using the Apache library to do my GET, which returns me a CloseableHttpResponse. How do I return this response as my endpoint's response? My current code copies this response, which is not what I want. I would directly like to return the CloseableHttpResponse. The reason I want to do this is because some websites have really huge data, and I would like to avoid having to copy those in place.
#RequestMapping(value = {"/hello"}, method = RequestMethod.GET)
#ResponseBody
public String getContent(#RequestParam(value = "url", required = true) String url, HttpServletResponse response)
CloseableHttpResponse httpResponse = useApacheLibraryAndSendGetToUrl(url);
for (Header header : httpResponse.getAllHeaders()) {
response.addHeader(header.getName(), header.getValue());
}
response.setStatus(httpResponse.getStatusLine().getStatusCode());
return EntityUtils.toString(entity, "UTF-8");
}
You could write a custom HttpMessageConverter for the CloseableHttpResponse type, which would allow you to simply return #ResponseBody CloseableHttpResponse.
See Mapping the response body with the #ResponseBody annotation for details.

Categories

Resources