Sending requests to a spring servers without waiting for a response - spring

I am writing two services that do some calculations. One of them must only send a lot of requests to second service without waiting for a response:
...
for (String url: oneService.getUrlList()) {
try {
sendRequests(restTemplate, url);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
...
#Async
void sendRequests(RestTemplate restTemplate, String url) throws InterruptedException {
restTemplate.postForLocation("http://second-servive/calculation/" + url, String.class);
}
I have in controller of second service:
#RequestMapping(value = "calculation/{url}", method = RequestMethod.POST)
public void getShortCircuitCalculation(#PathVariable String url) {
secondService.getCalculation(url);
}
I created many instances of the second service with eureka server to make calculations faster. But with #Async annotation and instances of second service it takes a lot of time, just like with a single instance. The first service is still waiting for responses from the second service. I tried to send responses in separate threads, but got the same.
Are there any ways to not wait for the response from the second service and continue sending responses so instances make calculations?

Related

Detect closed communication in Spring Boot Web

I have 2 spring-boot-starter-web services. Service A sends a request via Retrofit to service B. I have configured it to timeout after 10 seconds. Service A detects the timeout (SocketTimeoutException), but I have no way for service B to detect it. How can I verify that the socket is closed? I send a file via outputstream of httpServletResponse and it does not detect that it is closed. It looks like it sends the file to service A, when service A has already timed out.
try (FileInputStream in = new FileInputStream(file)){
OutputStream out = httpServletResponse.getOutputStream();
IOUtils.copy(in,out); // copy from in to out
out.close();
} catch (IOException e) {
// In AWS I get a "broken pipe" IOException. But, locally, I don't get any exception.
}
I don't know if there is a way to check if a Socket is closed until the server tries to read/write from it. I workaround is handle this IOException's like this:
#ExceptionHandler(IOException.class)
#ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE) //(1)
public Object exceptionHandler(IOException e, HttpServletRequest request) {
if (StringUtils.containsIgnoreCase(ExceptionUtils.getRootCauseMessage(e), "Broken pipe")) { //(2)
return null; //(2) socket is closed, cannot return any response
} else {
return new HttpEntity<>(e.getMessage()); //(3)
}
}
This blog post Spring - How to handle "IOException: Broken pipe" can help.

Consume RequestBody in Rest Controller Lazily in Spring Boot

I own a Spring boot Rest API which is consumed by many external Applications. This API receives a List of documents(base64 String) in one request. Each document is of around 100MB and most of the time there are 6,7 documents in Request payload. Let's say if 7 documents are in list it will consume 700MB in memory which is too much. I want to make it memory efficient.
I Can't ask Consumer of API to send document one by one, I have to receive them at once in a request but i want load documents one by one in memory instead of loading at once.
Here is example.
#PostMapping("/documents")
Employee newEmployee(#RequestBody List<String> DocuemntsInBase64) {
}
Also let me know if JsonSurfer can help in rescue.
Hey i have solve this mystery simply by using HttpServletRequest and JsonSurfer Library.
Here a sample code:
#RequestMapping(value = "/postDocuments",
method = RequestMethod.POST,
consumes = {"application/json", "application/xml"},
produces = {"application/json", "application/xml"})
#ResponseStatus(HttpStatus.CREATED)
public void createHotel(HttpServletRequest request, HttpServletResponse response) {
JsonSurfer surfer = JsonSurferJackson.INSTANCE;
try {
surfer.configBuilder()
.bind("Your JsonPath of list", new JsonPathListener() {
#Override
public void onValue(Object value, ParsingContext context) {
logger.info("Document of size: "+value.toString().getBytes().length);
}
})
.buildAndSurf(request.getReader());
} catch (IOException e) {
e.printStackTrace();
}
}

issue with Spring and asynchronous controller + HandlerInterceptor + IE/Edge

I am working on a Spring application that serves up REST endpoints. One of the endpoints essentially acts as a proxy between the HTML client and a third party cloud storage provider. This endpoint retrieves files from the storage provider and proxies them back to the client. Something like the following (note there is a synchronous and asynchronous version of the same endpoint):
#Controller
public class CloudStorageController {
...
#RequestMapping(value = "/fetch-image/{id}", method = RequestMethod.GET, produces = MediaType.IMAGE_JPEG_VALUE)
public ResponseEntity<byte[]> fetchImageSynchronous(#PathVariable final Long id) {
final byte[] imageFileContents = this.fetchImage(id);
return ResponseEntity.ok().body(imageFileContents);
}
#RequestMapping(value = "/fetch-image-async/{id}", method = RequestMethod.GET, produces = MediaType.IMAGE_JPEG_VALUE)
public Callable<ResponseEntity<byte[]>> fetchImageAsynchronous(#PathVariable final Long id) {
return () -> {
final byte[] imageFileContents = this.fetchImage(id);
return ResponseEntity.ok().body(imageFileContents);
};
}
private byte[] fetchImage(final long id) {
// fetch the file from cloud storage and return as byte array
...
}
...
}
Due to the nature of the client app (HTML5 + ajax) and how this endpoint is used, user authentication is supplied to this endpoint differently that the other endpoints. To handle this, a HandlerInterceptor was developed to deal with authentication for this endpoint:
#Component("cloudStorageAuthenticationInterceptor")
public class CloudStorageAuthenticationInterceptor extends HandlerInterceptorAdapter {
#Override
public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) {
// examine the request for the authentication information and verify it
final Authentication authenticated = ...
if (authenticated == null) {
try {
pResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
} catch (IOException e) {
throw new RuntimeException(e);
}
return false;
}
else {
try {
request.login(authenticated.getName(), (String) authenticated.getCredentials());
} catch (final ServletException e) {
throw new BadCredentialsException("Bad credentials");
}
}
return true;
}
}
The interceptor is registered like this:
#Configuration
#EnableWebMvc
public class ApiConfig extends WebMvcConfigurerAdapter {
#Autowired
#Qualifier("cloudStorageAuthenticationInterceptor")
private HandlerInterceptor cloudStorageAuthenticationInterceptor;
#Override
public void addInterceptors(final InterceptorRegistry registry) {
registry.addInterceptor(this.cloudStorageAuthenticationInterceptor)
.addPathPatterns(
"/fetch-image/**",
"/fetch-image-async/**"
);
}
#Override
public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {
final ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(this.asyncThreadPoolCoreSize);
executor.setMaxPoolSize(this.asyncThreadPoolMaxSize);
executor.setQueueCapacity(this.asyncThreadPoolQueueCapacity);
executor.setThreadNamePrefix(this.asyncThreadPoolPrefix);
executor.initialize();
configurer.setTaskExecutor(executor);
super.configureAsyncSupport(configurer);
}
}
Ideally, the image fetching would be done asynchronously (using the /fetch-image-asyc/{id} endpoint) because it has to call a third party web service which could have some latency.
The synchronous endpoint (/fetch-image/{id}) works correctly for all browsers. However, if using the asynchronous endpoint (/fetch-image-async/{id}), Chrome and Firefox work as expect.
However, if the client is Microsoft IE or Microsoft Edge, we seem some strange behavior. The endpoint is called correctly and the response sent successfully (at least from the server's viewpoint). However, it seems that the browser is waiting for something additional. In the IE/Edge DevTools window, the network request for the image shows as pending for 30 seconds, then seems to timeout, updates to successful and the image is successfully display. It also seems the connection to the server is still open, as the server side resources like database connections are not released. In the other browsers, the async response is received and processed in a second or less.
If I remove the HandlerInterceptor and just hard-wire some credentials for debugging, the behavior goes away. So this seems to have something to with the interaction between the HandlerInterceptor and the asynchronous controller method, and is only exhibited for some clients.
Anyone have a suggestion on why the semantics of IE/Edge are causing this behavior?
Based on your description, there are some different behaviors when using IE or Edge
it seems that the browser is waiting for something additional
the connection seems still open
it works fine if remove HandlerInterceptor and use hard code in auth logic
For the first behavior, I would suggest you use fiddler to trace all http requests. It is better if you could compare two different actions via fiddler (1) run on chrome, 2) run on edge ). Check all http headers in requests and responses carefully to see whether there is some different part. For the other behaviors, I would suggest you write logs to find which part spend the most time. It will provide you useful information to troubleshot.
After much tracing on the server and reading through the JavaDocs comments for AsyncHandlerInterceptor, I was able to resolve the issue. For requests to asynchronous controller methods, the preHandle method of any interceptor is called twice. It is called before the request is handed off to the servlet handling the request and again after the servlet has handled the request. In my case, the interceptor was attempting to authenticate the request for both scenarios (pre and post request handling). The application's authentication provider checks credentials in a database. For some reason if the client is IE or Edge, the authentication provider was unable to get a database connection when called from preHandle in the interceptor after the servlet handled the request. The following exception would be thrown:
ERROR o.a.c.c.C.[.[.[.[dispatcherServlet] - Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.DataAccessResourceFailureException: Could not open connection; nested exception is org.hibernate.exception.JDBCConnectionException: Could not open connection] with root cause
java.sql.SQLTransientConnectionException: HikariPool-0 - Connection is not available, request timed out after 30001ms.
So the servlet would successfully handle the request and send a response, but the filter would get hung up for 30 seconds waiting for the database connection to timeout on the post processing called to preHandle.
So for me, the simple solution was to add a check in preHandle if it is being called after the servlet has already handled the request. I updated the preHandle method as follows:
#Override
public boolean preHandle(final HttpServletRequest pRequest, final HttpServletResponse pResponse, final Object pHandler) {
if (pRequest.getDispatcherType().equals(DispatcherType.REQUEST)) {
... perform authentication ...
}
return true;
}
That solved the issue for me. It doesn't explain everything (i.e., why only IE/Edge would cause the issue), but it seems that preHandle should only do work before the servlet handles the request anyways.

Spring controller, custom response via HttpServletResponse

I'm trying to write a custom response using HttpServletResponse.getOutputStream() and HttpServletResponse.setStatus(int).
But anything that is an status different from 200 doesn't consideres the response body that I wrote.
I have 2 web applications running on different ports, the application "A" must request data from application "B". For this I created a controller to tunnel all requests on application "A" to application "B".
Example:
#RequestMapping("/tunnel/**")
public void exchange(HttpServletRequest request, HttpServletResponse response) throws IOException {
// my service tunnel the request to another server
// and the response of the server must be replied
ResponseDescriptor tunnelResponse = tunnelService.request(request);
response.setStatus(tunnelResponse.getStatus());
// if the status was different them 200, the next line will not work
response.getOutputStream().write(tunnelResponse.getResponseBodyAsByteArray());
}
Note, I need to response from application A the exact response that come from application B.
You need to catch HttpStatusCodeException to get responses other than 200.
try {
ResponseDescriptor tunnelResponse = tunnelService.request(request);
response.setStatus(tunnelResponse.getStatus());
response.getOutputStream().write(tunnelResponse.getResponseBodyAsByteArray());
} catch (HttpStatusCodeException e) {
response.setStatus(e.getStatusCode().value());
response.getOutputStream().write(e.getResponseBodyAsByteArray());
}
Solved!
I created a #ExceptionHandler and a custom exception TunnelException extends RuntimeException.
So, on my exchange(...) method, I catch RestClientResponseException and throw my own exception encapsulating (in the exception) the Headers, HttpStatus and the ResponseBody byte array.
This is the exception handler:
#ExceptionHandler(TunnelException.class)
public ResponseEntity<?> handleTunnelException(TunnelException ex) throws IOException {
return ResponseEntity
.status(ex.getStatus())
.contentType(ex.getHeaders().getContentType())
.body(ex.getBody());
}

Request asynchronous processing does not work

I would like to make my controller (Spring MVC) handle request in parallel. I use #Callable below and it does not work, because next request is handled since first one finished (returns view).
#RequestMapping(method = RequestMethod.GET)
public Callable<String> helloWorld(final Model model) throws InterruptedException {
return new Callable<String>() {
#Override
public String call() throws Exception {
Thread.sleep(5000);
return "helloWorld";
}
};
}
Do I need any special code?
The documentation for Spring MVC states the following about the Callable<?> return type for handler methods
A Callable can be returned when the application wants to produce
the return value asynchronously in a thread managed by Spring MVC.
Spring will take the returned Callable instance, pass it to an ExecutorService (actually does more processing then than that, but for simplicity) and execute it in a different Thread then your initial request. After waiting 5 seconds, it will return the view associated with the view name "helloWorld".

Resources