理解Spring MVC中的异步处理请求(下)

理解Spring MVC中的异步处理请求(下)

前篇博客介绍完异步处理请求的第一种方式,下面介绍第二种——一边异步处理请求一边生成HTTP响应。

Spring MVC代码示例

一边异步处理请求一边生成HTTP响应的方式为将一个HTTP响应分割成多个事件返回,这种方式是基于HTTP/1.1的分块传输编码(Chunked transfer encoding)。

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.");
    }
    // ...
}

使用CURL或者浏览器按照下面的参数进行访问,一开始便显示如下内容:

HTTP/1.1 200 
Transfer-Encoding: chunked
Date: Wed, 05 Oct 2016 16:00:22 GMT
  • 1秒后,再增多一条信息
msg1
  • 再1秒后,又增多一条信息,然后最终返回结果。
msg2

使用SseEmitter

所谓的Sse其实就是Server-Sent Events,即服务器推送事件,属于HTML5的一项新功能,常用于服务器主动通知客户端有相关信息的更新。其他替代方法一般有WebSocket和客户端定时轮询,前者过于复杂,后者又过于低效而笨拙。SseEmitter属于ResponseBodyEmitter的子类,可以生成text/event-stream格式的信息。

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.");
    }
    // ...
}

返回信息如下:

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
  •  

处理超时

ResponseBodyEmitter和SseEmitter可以在调用构造函数时传入一个数值,用来指定每次请求处理的时间上限。如:

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

当超时发生时,会报AsyncRequestTimeoutException异常,这时就会向客户端发送类似于如下的JSON字符串:

{"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

当然,你也可以重写handleAsyncRequestTimeoutException方法定制自己的超时返回信息,做法如下:

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

此后,客户端接受到的超时信息就会变成如下:

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

msg1
timeout!!

猜你喜欢

转载自blog.csdn.net/zjttlance/article/details/80022268
今日推荐