Notify browser download has started immediately with DeferredResult - spring-boot

I have a Spring boot app with #RestController returning DeferredResult, holding "application/pdf" byte[] (from a service I don't control). I want to notify the browser immediately that the download has started, to do this I flush the output stream with response header information.
My code shown below, works with Tomcat (locally & WebLogic) but not Tomcat on Azure Web App. The former shows pdf download in-progress in Chrome's status bar, the latter gives nothing until DeferredResult completes.
Is there a more reliable way to notify the browser of a download than flushing the response header?
Could it be teh code is fine and its Azure preventing the flush being sent to the client? Any help gratefully received.
#RequestMapping(value= DOWNLOAD_BASE_LINK + "/{documentType}/{correlationId}/*", method= RequestMethod.GET, produces=MediaType.APPLICATION_PDF_VALUE)
public DeferredResult<ResponseEntity<?>> download(#PathVariable final PaymentDocumentService.DocumentType documentType,
#PathVariable final String correlationId,
final HttpServletResponse resp,
final HttpServletRequest req) {
LOGGER.debug("Is asyncSupported: [{}] - must be true for document download functionality", req.isAsyncSupported());
LOGGER.info("Searching for documentType [{}] with correlationId [{}]", documentType, correlationId);
final ResponseEntity timeoutResponseEntity = new ResponseEntity(HttpStatus.NOT_FOUND);
final long startTime = System.currentTimeMillis();
final DeferredResult<ResponseEntity<?>> deferredResult = new DeferredResult<>(documentServiceTimeout, timeoutResponseEntity);
final String documentFileName = paymentDocumentService.getDocumentFileName(documentType);
resp.setHeader(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + documentFileName + "\"");
resp.setContentType(MediaType.APPLICATION_PDF_VALUE);
//Give the browser the header data so it can show the download as started
final OutputStream outputStream;
try {
outputStream = resp.getOutputStream();
outputStream.flush();
} catch (IOException e) {
LOGGER.error("Could not get or flush outputStream");
deferredResult.setResult(new ResponseEntity(HttpStatus.SERVICE_UNAVAILABLE));
return deferredResult;
}
ListenableFuture<DocumentData> futureDocumentData = paymentDocumentService.getDocument(documentType, correlationId);
futureDocumentData.addCallback(new ListenableFutureCallback<DocumentData>() {
#Override
public void onSuccess(DocumentData documentData) {
LOGGER.debug("Start onSuccess: documentData received");
deferredResult.setResult(new ResponseEntity<>(documentData.getBytes(), HttpStatus.OK));
LOGGER.info("End onSuccess: returning deferredResult [{}] (File download time={}ms", deferredResult, System.currentTimeMillis() - startTime);
}
#Override
public void onFailure(Throwable ex) {
LOGGER.error("Start onFailure: documentData type[{}] correlationId[{}] failed", documentType, correlationId, ex);
deferredResult.setResult(new ResponseEntity(HttpStatus.SERVICE_UNAVAILABLE));
LOGGER.error("End onFailure: returning deferredResult [{}]", deferredResult);
}
});
return deferredResult;
}

The question above "Could it be the code is fine and its Azure preventing the flush being sent to the client?" is partly correct. Though not Azure, its actually the IIS web server buffering the response before sending it.
Here is the configuration to not buffer (responseBufferLimit zero) which solved my problem:
<handlers>
<add name="httpPlatformHandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified" responseBufferLimit="0"/>
</handlers>
Reference: https://learn.microsoft.com/en-us/iis/configuration/system.webserver/handlers/add

Related

How to retry RestAPI connection if it fails for first time in restTemplate?

I am calling a third party rest API, some times it sends response with status code 500, 504.
I want to make a another hit to the API if it gives above status code.
My current logic of retry is:
public <T> ResponseEntity<T> sendGetRequest(String url,
Class<T> responseClazz,
HttpHeaders headers) {
ResponseEntity<T> response = null;
int count = 0;
int maxTries = 2;
while(true) {
try {
HttpEntity request = new HttpEntity(headers);
response = restTemplate.exchange(url, HttpMethod.GET, request, responseClazz);
if(response.getStatusCode() != HttpStatus.OK) {
log.error("null or Error response from server for ", url);
}
log.info("Response received {}", response.toString());
return response;
}catch (ResourceAccessException rae){
log.warn("retry count {} {}", count, rae);
if (++count == maxTries) throw new ServerErrorException("API timeout");
}
}
}
I have also used apache http where I use CloseableHttpClient to retry for status code 500 and 504.
I have also looks to the solution of spring-retry. Is there any other method to do this?
When calling HTTP request with RestTemplate, there are 2 main cases to retry:
Specific response HTTP statuses. For example:
503 Service Unavailable status can be retried.
404 Not Found can be proceeded without a retry attempt.
ResourceAccessException which can represent some IO exception received without getting the HTTP server response, like SocketException: Broken pipe.
For solution based on RestTemplate/HttpClient, while it exposes options to retry based on the HTTP response, combining it with IOException handling can be tricky.
Solution based on Spring RetryTemplate
#Bean(name = "restTemplateRetryTemplate")
public RetryTemplate restTemplateRetryTemplate() {
return createRestTemplateRetryTemplate();
}
private RetryTemplate createRestTemplateRetryTemplate(boolean retryOnServerErrors) {
Map<Class<? extends Throwable>, Boolean> retryableExceptions = new HashMap<>();
retryableExceptions.put(ResourceAccessException.class, true);
retryableExceptions.put(HttpServerErrorException.ServiceUnavailable.class, true);
retryableExceptions.put(HttpServerErrorException.BadGateway.class, true);
retryableExceptions.put(HttpServerErrorException.GatewayTimeout.class, true);
retryableExceptions.put(HttpClientErrorException.TooManyRequests.class, true);
return createRetryTemplate(retryableExceptions);
}
private RetryTemplate createRetryTemplate(Map<Class<? extends Throwable>, Boolean> retryableExceptions) {
RetryTemplate retryTemplate = new RetryTemplate();
ExponentialRandomBackOffPolicy exponentialRandomBackOffPolicy = new ExponentialRandomBackOffPolicy();
exponentialRandomBackOffPolicy.setInitialInterval(INITIAL_INTERVAL);
exponentialRandomBackOffPolicy.setMaxInterval(MAX_INTERVAL);
exponentialRandomBackOffPolicy.setMultiplier(MULTIPLIER);
retryTemplate.setBackOffPolicy(exponentialRandomBackOffPolicy);
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(MAX_ATTEMPTS, retryableExceptions));
// Optional, for additional logging on failures.
retryTemplate.registerListener(retryTemplateLogListener);
return retryTemplate;
}
Usage example
#Autowired
#Qualifier("restTemplateRetryTemplate")
private RetryTemplate retryTemplate;
...
String result = retryTemplate.execute(arg -> {
return longRestTemplate.getForObject(url, String.class);
});

How to handle the http response from an api call efficiently using spring boot

When we fire an api call to a 3rd party service, we can get different HTTP responses (200, 404 etc.). How can we handle them in a standard way?
private ResponseEntity<ResultHolder> responseEntity;
public ResponseEntity<ResultHolder> serviceTest(String searchText, String countryCode) {
logger.info("Service started");
String url = prepareUrl(searchText,countryCode); //custom method
HttpHeaders header = new HttpHeaders();
prepareHeader(header); //custom method
HttpEntity<String> requestEntity = new HttpEntity<String>(header);
try {
logger.info("Calling the API");
responseEntity = restClient.exchange(url,
HttpMethod.GET,
requestEntity,
ResultHolder.class);
}catch (Exception e) {
logger.error("Exception while calling the API "+ e);
//Here I am trying to get the value of response code and handle based on that
//Is this the right way to solve the problem?
if(responseEntity.getStatusCodeValue() != 200) {
responseEntity = new ResponseEntity<ResultHolder>(
HttpStatus.BAD_REQUEST);
}
}
logger.info("Service Ended");
return responseEntity;
}
What if I want to display distinct custom messages for server side errors and for user errors like 'No Internet Connection'.
Kindly help me to understand the good practises in this area.
Thank you

Receive HTTP Stream in Spring Boot

i want to receive a HTTP Stream in SpringBoot but the InputStream of HttpServletRequest seems not to be an endless HTTP Stream and only contains the Content of first HTTP Body.
I want to process a chuncked HTTP Stream in SpringBoot on which is puhed some Value String from time to time.
Currently I tried something like this in a controller:
#Override
public void test(HttpServletRequest request,
HttpServletResponse response) {
System.out.println("StreamStart");
try {
byte[] buffer = new byte[1024];
while(true){
int len = request.getInputStream().read(buffer);
if(len!=-1) {
System.out.println("Len: " + len);
System.out.println(new String(buffer));
}
Thread.sleep(500);
}
}
catch(Exception x){
x.printStackTrace();
}
System.out.println("StreamEnd");
}
However the first Request Body after the header works, but the second does not appear in my Controller.
Does SpringBoot cancles the connection or the stream?
Can I have access to the complete HTTP Input stream to get my values from it?
Maybe Multipart request would be usefull for you?
That way you can recieve multiple parts of data
https://www.w3.org/Protocols/rfc1341/7_2_Multipart.html
Example:
#PostMapping("/upload")
public void uploadStream(#RequestParam MultipartFile[] multipartFiles){
for(MultipartFile multipartFile:multipartFiles){
try {
InputStream inputStream = multipartFile.getInputStream();
} catch (IOException e) {
e.printStackTrace();
}
}
}

Why this externa web service call go into error only when the call is performed using Spring RestTemplate?

I am working on a Spring project implementing a simple console application that have to call an external REST web service passing to it a parameter and obtaining a response from it.
The call to this webservice is:
http://5.249.148.180:8280/GLIS_Registration/6
where 6 is the specified ID. If you open this address in the browser (or by cURL tool) you will obtain the expected error message:
<?xml version="1.0" encoding="UTF-8"?>
<response>
<sampleid>IRGC 100000</sampleid>
<genus>Oryza</genus>
<error>PGRFA sampleid [IRGC 100000], genus [Oryza] already registered for this owner</error>
</response>
This error message is the expected response for this request and I correctly obtain it also using cURL tool to perform the request.
So I have to perform this GET request from my Spring application.
To do it I create this getResponse() method into a RestClient class:
#Service
#Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RestClient {
RestTemplate restTemplate;
String uriResourceRegistrationApi;
public RestClient() {
super();
restTemplate = new RestTemplate();
uriResourceRegistrationApi = "http://5.249.148.180:8280/GLIS_Registration/7";
}
public ResponseEntity<String> getResponse() {
ResponseEntity<String> response = restTemplate.getForEntity(uriResourceRegistrationApi, String.class);
return response;
}
}
Then I call this method from this test method:
#Test
public void singleResourceRestTest() {
System.out.println("singleResourceRestTest() START");
ResponseEntity<String> result = restClient.getResponse();
System.out.println("singleResourceRestTest() END");
}
But I am experiencing a very strange behavior, what it happens is:
1)The call to my external web service seems that happens (I saw it from the web services log).
2) The web service retrieve the parameter having value 7 but then it seems that can't use it as done without problem performing the request from the browser or by the shell statment:
curl -v http://5.249.148.180:8280/GLIS_Registration/7
But now, calling in this way, my webservice (I can't post the code because it is a WSO2 ESB flow) give me this error message:
<200 OK,<?xml version="1.0" encoding="UTF-8"?>
<response>
<error>Location information not correct</error>
<error>At least one between <genus> and <cropname> is required</error>
<error>Sample ID is required</error>
<error>Date is required</error>
<error>Creation method is required</error>
</response>,{Vary=[Accept-Encoding], Content-Type=[text/html; charset=UTF-8], Date=[Fri, 05 May 2017 14:07:09 GMT], Transfer-Encoding=[chunked], Connection=[keep-alive]}>
Looking the web service log it seems that performing the call using RestTemplate it have some problem to use the retrieved ID=7 to perform a database query.
I know it looks terribly strange and you can see: "The problem is of your web service and not of the Spring RestTemplate". This is only partially true because I implemented this custom method that perform a low level Http GET call, this callWsOldStyle() (putted into the previous RestClient class):
public void callWsOldStyle() {
HttpURLConnection connection = null;
BufferedReader reader = null;
try {
URL restAPIUrl = new URL("http://5.249.148.180:8280/GLIS_Registration/7");
connection = (HttpURLConnection) restAPIUrl.openConnection();
connection.setRequestMethod("GET");
// Read the response
reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder jsonData = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
jsonData.append(line);
}
System.out.println(jsonData.toString());
}catch(Exception e) {
e.printStackTrace();
}
finally {
// Clean up
IOUtils.closeQuietly(reader);
if(connection != null)
connection.disconnect();
}
}
Using this method instead the RestTemplate one it works fine and this line:
System.out.println(jsonData.toString());
print the expected result:
<?xml version="1.0" encoding="UTF-8"?><response><sampleid>IRGC 100005</sampleid><genus>Oryza</genus><error>PGRFA sampleid [IRGC 100005], genus [Oryza] already registered for this owner</error></response>
To summarize:
Calling my WS from the browser it works.
Calling my WS using cURL it works.
Calling my WS using my callWsOldStyle() method it works.
Calling my WS using the method that use RestTemplate it go into error when my WS receive and try to handle the request.
So, what can be the cause of this issue? What am I missing? Maybe can depend by some wrong header or something like this?
As Pete said you are receiving an internal server error (status code 500) so you should check the server side of this rest service.
In any case you can do the following for the resttemplate
create an org.springframework.web.client.RequestCallback object if
you need to do something in the request
create an org.springframework.web.client.ResponseExtractor<String>
object in order to extract your data
use the resttemplate
org.springframework.web.client.RequestCallback
public class SampleRequestCallBack implements RequestCallback
{
#Override
public void doWithRequest(ClientHttpRequest request) throws IOException
{
}
}
org.springframework.web.client.ResponseExtractor
public class CustomResponseExtractor implements ResponseExtractor<String>
{
private static final Logger logger = LoggerFactory.getLogger(CustomResponseExtractor.class.getName());
#Override
public String extractData(ClientHttpResponse response) throws IOException
{
try
{
String result = org.apache.commons.io.IOUtils.toString(response.getBody(), Charset.forName("UTF8"));
if( logger.isInfoEnabled() )
{
logger.info("Response received.\nStatus code: {}\n Result: {}",response.getStatusCode().value(), result);
}
return result;
}
catch (Exception e)
{
throw new IOException(e);
}
}
}
REST TEMPLATE CALL
#Test
public void testStack()
{
try
{
String url = "http://5.249.148.180:8280/GLIS_Registration/6";
String response = restTemplate.execute(url, HttpMethod.GET, new SampleRequestCallBack(), new CustomResponseExtractor());;
logger.info(response);
}
catch (Exception e)
{
logger.error("Errore", e);
}
}
Angelo

unable to call a REST webservice..Full authentication required

I am currently working on spring application and REST webservices.
I have created a REST webservice in one application and want to access that service from other applications.
Below is the error its showing when trying to access the webservice.
RestClientException : org.springframework.web.client.HttpClientErrorException: 401 Full authentication is required to access this resource
Below is my webservice code:
#RequestMapping(value = MyRequestMapping.GET_ACC_DATA, method = RequestMethod.GET)
#ResponseBody
public MyResponseDTO getSigDataValues(#PathVariable final String acc, final HttpServletResponse response) throws Exception {
MyResponseDTO responseDTO = null;
try {
//logic goes here
//responseDTO = ..
} catch (Exception e) {
LOG.error("Exception" + e);
}
return responseDTO;
}
I am calling above webservice from another application.In the below mentioned method I am calling the webservice and its throwing me the exception org.springframework.web.client.HttpClientErrorException.
public MyResponseDTO getAccData(String acc){
try{
list= (List<String>)restTemplate.postForObject(MyDataURL.GET_ACC_DATA.value(), MyResponseDTO.class, acc);
}
catch (final RestClientException e)
{
LOG.info("RestClientException :" + e);
}
Please suggest, what am I missing.
You would need to authenticate against the REST service. One of the most common ways is Basic Authentication. If this is what the service is using you would need to create an AUTHORIZATION header with Base 64 encoded usernamen + password.
RestTemplate allow to set customer headers before the request gets sent.
The process of creating the Authorization header is relatively straightforward for Basic Authentication, so it can pretty much be done manually with a few lines of code:
private HttpHeaders createHeaders(String username, String password) {
return new HttpHeaders() {
private static final long serialVersionUID = -1704024310885506847L;
{
String auth = username + ":" + password;
byte[] encodedAuth = Base64.encodeBase64(auth.getBytes(Charset.forName("US-ASCII")));
String authHeader = "Basic " + new String(encodedAuth);
set("Authorization", authHeader);
}
};
}
Then, sending a request becomes just as simple:
ResponseEntity<Dados> response = restTemplate.exchange(uriComponents.toUriString(), HttpMethod.GET,
new HttpEntity<Dados>(createHeaders(usuario, senha)), Dados.class);

Resources