Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle client abort during request upload?

Given the following application, based on a Spring Initializr template

@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    public static class Payload {
        public String field1;
        public String field2;
    }

    @RestController
    public static class MyController {
        @RequestMapping("/echo")
        public ResponseEntity<Payload> echo(@RequestBody Payload payload) {
            return new ResponseEntity<>(payload, HttpStatus.OK);
        }
    }
}

If I abort the connection after sending the headers

$ nc 192.168.56.1 8080
POST /echo HTTP/1.1
Host: 192.168.56.1:8080
Content-Type: application/json
Content-Length: 42

^C

Then the server attempts to respond with a 400 and the following is logged

2016-05-03 16:56:28.916  WARN 5776 --- [nio-8080-exec-2] .w.s.m.s.DefaultHandlerExceptionResolver : Failed to read HTTP message: org.springframework.http.converter.HttpMessageNotReadableException: Could not read document: Unexpected EOF read on the socket; nested exception is java.io.EOFException: Unexpected EOF read on the socket
2016-05-03 16:56:28.997 ERROR 5776 --- [nio-8080-exec-2] o.a.c.c.C.[Tomcat].[localhost]           : Exception Processing ErrorPage[errorCode=0, location=/error] org.apache.catalina.connector.ClientAbortException: java.io.IOException: An established connection was aborted by the software in your host machine

If instead the client aborts after the request has been sent, nothing is logged by default and the resulting ClientAbortException is available in filters via ErrorAttributes#getError.

How can I handle the during-request abort in the same way? That is, not having any warn/error logged by default, and to enable custom filters to tell that the client has aborted.

like image 410
OrangeDog Avatar asked May 03 '16 16:05

OrangeDog


1 Answers

I've found a solution, and have yet to find a case where it does the wrong thing.

@Autowired
private DefaultErrorAttributes defaultExceptionResolver;
    
@ExceptionHandler(HttpMessageNotReadableException.class)
public void checkForAbort(HttpServletRequest request, HttpServletResponse response, RuntimeException exception) {
    if (exception.getCause() instanceof EOFException) {
        ClientAbortException abortException = new ClientAbortException(exception.getCause());
        abortException.addSuppressed(exception);
        try {
            response.getOutputStream().close();
        }
        catch (IOException ex) {
            abortException.addSuppressed(ex);
        }

        defaultExceptionResolver.resolveException(request, response, null, abortException);
    }
    else {
        throw exception;
    }
}

This could would go in, for example, a @ControllerAdvice.

like image 71
OrangeDog Avatar answered Sep 20 '22 12:09

OrangeDog