Understanding asynchronous processing of requests in Spring MVC (below)

Understanding asynchronous processing of requests in Spring MVC (below)

The previous blog has introduced the first way of asynchronously processing requests, and the second one will be introduced below—generating HTTP responses while processing requests asynchronously.

 

Spring MVC code example

The way to generate HTTP responses while processing requests asynchronously is to split an HTTP response into multiple event returns, which is based on the chunked transfer encoding of HTTP/1.1.

package com.example.component;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;

import java.io.IOException;

@Controller
@RequestMapping("/streaming")
public class StreamingController {

    @Autowired
    AsyncHelper asyncHelper;

    @RequestMapping(method = RequestMethod.GET)
    public ResponseBodyEmitter streaming(@RequestParam(defaultValue = "1") long eventNumber, @RequestParam(defaultValue = "0") long intervalSec) throws IOException {
        Console.println("Start get.");

        ResponseBodyEmitter emitter = new ResponseBodyEmitter();
        asyncHelper.streaming(emitter, eventNumber, intervalSec);

        Console.println("End get.");
        return emitter;
    }

}

@Component
public class AsyncHelper {
    // ...
    @Async
    public void streaming(ResponseBodyEmitter emitter, long eventNumber, long intervalSec) throws IOException {
        Console.println("Start Async processing.");

        for (long i = 1; i <= eventNumber; i++) {
            sleep(intervalSec);
            emitter.send("msg" + i + "\r\n");
        }

        emitter.complete();

        Console.println("End Async processing.");
    }
    // ...
}

Use CURL or a browser to access the following parameters, and the following content is displayed at the beginning:

HTTP/1.1 200 
Transfer-Encoding: chunked
Date: Wed, 05 Oct 2016 16:00:22 GMT
  • After 1 second, add another message
msg1
  • After another 1 second, another message is added, and then the result is finally returned.
msg2

Using SseEmitter

The so-called Sse is actually Server-Sent Events, that is, server push events, which are a new feature of HTML5, and are often used by the server to actively notify the client of relevant information updates. Other alternatives are generally WebSocket and client-side polling, the former is too complicated, and the latter is too inefficient and clumsy. SseEmitter is a subclass of ResponseBodyEmitter and can generate information in text/event-stream format.

package com.example.component;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;

@Controller
@RequestMapping("/sse")
public class SseController {

    @Autowired
    AsyncHelper asyncHelper;

    @RequestMapping(method = RequestMethod.GET)
    public SseEmitter sse(@RequestParam(defaultValue = "1") long eventNumber, @RequestParam(defaultValue = "0") long intervalSec) throws IOException {
        Console.println("Start get.");

        SseEmitter emitter = new SseEmitter();
        asyncHelper.sse(emitter, eventNumber, intervalSec);

        Console.println("End get.");
        return emitter;
    }

}
@Component
public class AsyncHelper {
    // ...
    @Async
    public void sse(SseEmitter emitter, long eventNumber, long intervalSec) throws IOException {
        Console.println("Start Async processing.");

        for (long i = 1; i <= eventNumber; i++) {
            sleep(intervalSec);
            emitter.send(SseEmitter.event()
        .comment("This is test event")
        .id(UUID.randomUUID().toString())
        .name("onlog")
        .reconnectTime(3000)
        .data("msg" + i));
        }

        emitter.complete();

        Console.println("End Async processing.");
    }
    // ...
}

The returned information is as follows:

HTTP/1.1 200 
Content-Type: text/event-stream;charset=UTF-8
Transfer-Encoding: chunked
Date: Wed, 05 Oct 2016 16:31:35 GMT

:This is test event
id:c62ae77f-85fe-41ac-bfff-c03b0020a816
event:onlog
retry:3000
data:msg1

:This is test event
id:d283757e-9d67-4be5-b858-3c6b543c7b5d
event:onlog
retry:3000
data:msg2
  •  

processing timeout

ResponseBodyEmitter and SseEmitter can pass in a value when calling the constructor to specify the time limit for processing each request. Such as:

SseEmitter emitter = new SseEmitter(0L);//永不超时
  • 1

When the timeout occurs, an AsyncRequestTimeoutException exception will be reported, and a JSON string similar to the following will be sent to the client:

{"timestamp":"2016-10-05T16:35:31.060+0000","status":200,"error":"OK","exception":"org.springframework.web.context.request.async.AsyncRequestTimeoutException","message":"No message available","path":"/sse"}
  • 1

Of course, you can also override the handleAsyncRequestTimeoutException method to customize your own timeout return information, as follows:

@Controller
@RequestMapping("/sse")
public class SseController {
    private static final Logger logger = LoggerFactory.getLogger(SseController.class);
    // ...
    @ExceptionHandler
    @ResponseBody
    public String handleAsyncRequestTimeoutException(AsyncRequestTimeoutException e) {
        logger.error("timeout error is occurred.", e);
        return SseEmitter.event().data("timeout!!").build().stream()
                .map(d -> d.getData().toString())
                .collect(Collectors.joining());
    }
}

After that, the timeout information received by the client will become as follows:

HTTP/1.1 200 
Transfer-Encoding: chunked
Date: Wed, 05 Oct 2016 17:01:53 GMT

msg1
timeout!!

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326165623&siteId=291194637