Spring MVC Streaming and SSE Request Processing – Spring MVC流和SSE请求处理

最后修改: 2018年 8月 1日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.介绍

This simple tutorial demonstrates the use of several asynchronous and streaming objects in Spring MVC 5.x.x.

这个简单的教程演示了Spring MVC 5.x.x中几个异步和流式对象的使用。

Specifically, we’ll review three key classes:

具体而言,我们将审查三个关键类别。

  • ResponseBodyEmitter
  • SseEmitter
  • StreamingResponseBody

Also, we’ll discuss how to interact with them using a JavaScript client.

此外,我们还将讨论如何使用JavaScript客户端与它们互动。

2. ResponseBodyEmitter

2.ResponseBodyEmitter

ResponseBodyEmitter handles async responses.

ResponseBodyEmitter处理异步响应。

Also, it represents a parent for a number of subclasses – one of which we’ll take a closer look at below.

此外,它还代表了一些子类的父类–下面我们将仔细看看其中的一个子类。

2.1. Server Side

2.1. 服务器端

It’s better to use a ResponseBodyEmitter along with its own dedicated asynchronous thread and wrapped with a ResponseEntity (which we can inject the emitter into directly):

最好是使用一个ResponseBodyEmitter以及它自己的专用异步线程,并且用ResponseEntity包裹(我们可以直接将emitter注入其中)。

@Controller
public class ResponseBodyEmitterController {
 
    private ExecutorService executor 
      = Executors.newCachedThreadPool();

    @GetMapping("/rbe")
    public ResponseEntity<ResponseBodyEmitter> handleRbe() {
        ResponseBodyEmitter emitter = new ResponseBodyEmitter();
        executor.execute(() -> {
            try {
                emitter.send(
                  "/rbe" + " @ " + new Date(), MediaType.TEXT_PLAIN);
                emitter.complete();
            } catch (Exception ex) {
                emitter.completeWithError(ex);
            }
        });
        return new ResponseEntity(emitter, HttpStatus.OK);
    }
}

So, in the example above, we can sidestep needing to use CompleteableFutures, more complicated asynchronous promises, or use of the @Async annotation.

因此,在上面的例子中,我们可以避免使用CompleteableFutures、更复杂的异步承诺,或者使用@Async 注。

Instead, we simply declare our asynchronous entity and wrap it in a new Thread provided by the ExecutorService.

相反,我们只需声明我们的异步实体,并将其包裹在一个由ExecutorService提供的新Thread中。

2.2. Client Side

2.2 客户端

For client-side use, we can use a simple XHR method and call our API endpoints just like in a usual AJAX operation:

对于客户端的使用,我们可以使用一个简单的XHR方法,就像在通常的AJAX操作中一样调用我们的API端点。

var xhr = function(url) {
    return new Promise(function(resolve, reject) {
        var xmhr = new XMLHttpRequest();
        //...
        xmhr.open("GET", url, true);
        xmhr.send();
       //...
    });
};

xhr('http://localhost:8080/javamvcasync/rbe')
  .then(function(success){ //... });

3. SseEmitter

3.SseEmitter

SseEmitter is actually a subclass of ResponseBodyEmitter and provides additional Server-Sent Event (SSE) support out-of-the-box.

SseEmitter实际上是ResponseBodyEmitter的子类,并提供额外的Server-Sent Event(SSE)支持,即开即用。

3.1. Server Side

3.1. 服务器端

So, let’s take a quick look at an example controller leveraging this powerful entity:

因此,让我们快速看看一个利用这一强大实体的控制器的例子。

@Controller
public class SseEmitterController {
    private ExecutorService nonBlockingService = Executors
      .newCachedThreadPool();
    
    @GetMapping("/sse")
    public SseEmitter handleSse() {
         SseEmitter emitter = new SseEmitter();
         nonBlockingService.execute(() -> {
             try {
                 emitter.send("/sse" + " @ " + new Date());
                 // we could send more events
                 emitter.complete();
             } catch (Exception ex) {
                 emitter.completeWithError(ex);
             }
         });
         return emitter;
    }   
}

Pretty standard fare, but we’ll notice a few differences between this and our usual REST controller:

很标准,但我们会注意到这和我们通常的REST控制器有一些不同。

  • First, we return a SseEmitter
  • Also, we wrap the core response information in its own Thread
  • Finally, we send response information using emitter.send()

3.2. Client Side

3.2 客户端

Our client works a little bit differently this time since we can leverage the continuously connected Server-Sent Event Library:

我们的客户端这次的工作方式有点不同,因为我们可以利用连续连接的服务器发送事件库。

var sse = new EventSource('http://localhost:8080/javamvcasync/sse');
sse.onmessage = function (evt) {
    var el = document.getElementById('sse');
    el.appendChild(document.createTextNode(evt.data));
    el.appendChild(document.createElement('br'));
};

4. StreamingResponseBody

4.StreamingResponseBody

Lastly, we can use StreamingResponseBody to write directly to an OutputStream before passing that written information back to the client using a ResponseEntity.

最后,我们可以使用StreamingResponseBody直接写入OutputStream,然后使用ResponseEntity将写入的信息传回给客户端。

4.1. Server Side

4.1. 服务器端

@Controller
public class StreamingResponseBodyController {
 
    @GetMapping("/srb")
    public ResponseEntity<StreamingResponseBody> handleRbe() {
        StreamingResponseBody stream = out -> {
            String msg = "/srb" + " @ " + new Date();
            out.write(msg.getBytes());
        };
        return new ResponseEntity(stream, HttpStatus.OK);
    }
}

4.2. Client Side

4.2 客户端

Just like before, we’ll use a regular XHR method to access the controller above:

就像以前一样,我们将使用一个普通的XHR方法来访问上面的控制器。

var xhr = function(url) {
    return new Promise(function(resolve, reject) {
        var xmhr = new XMLHttpRequest();
        //...
        xmhr.open("GET", url, true);
        xmhr.send();
        //...
    });
};

xhr('http://localhost:8080/javamvcasync/srb')
  .then(function(success){ //... });

Next, let’s take a look at some successful uses of these examples.

接下来,让我们看看这些例子的一些成功运用。

5. Bringing It All Together

5.把所有的东西集中起来

After we’ve successfully compiled our server and run our client above (accessing the supplied index.jsp), we should see the following in our browser:

在我们成功地编译了我们的服务器并运行了上面的客户端(访问提供的index.jsp)之后,我们应该在浏览器中看到以下内容。

SpringMVCAsyncResponseReques.v3
And the following in our terminal:

SpringMVCAsyncResponseReques.v3br/>
并在我们的终端中进行如下操作。

 

Terminal

We can also call the endpoints directly and see them streaming responses appear in our browser.

我们也可以直接调用端点,看到它们的流式响应出现在我们的浏览器中。

6. Conclusion

6.结论

While Future and CompleteableFuture have proven robust additions to Java and Spring, we now have several resources at our disposal to more adequately handle asynchronous and streaming data for highly-concurrent web applications.

虽然FutureCompleteableFuture已被证明是对Java和Spring的有力补充,但我们现在有几种资源可供支配,以更充分地处理高并发Web应用程序的异步和流式数据。

Finally, check out the complete code examples over on GitHub.

最后,请查看GitHub上的完整代码示例