Skip to content

SseEmitter: connection closed after first event #25987

@reta

Description

@reta

It seems like there is a regression introduced into SseEmitter in latest 5.2.10.RELEASE (apparently #25442), it now returns to the client only first SSE event.

How to reproduce

package com.example.sse.emmiter;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter.SseEventBuilder;

@SpringBootApplication
public class SseEmitterRegressionApplication {
    @RestController
    @EnableAutoConfiguration
    static class LibraryController {
        @GetMapping("/sse")
        public SseEmitter streamSseMvc() {
            final SseEmitter emitter = new SseEmitter();
            final ExecutorService sseMvcExecutor = Executors.newSingleThreadExecutor();
            
            sseMvcExecutor.execute(() -> {
                try {
                    for (int eventId = 1; eventId <= 5; ++eventId) {
                        SseEventBuilder event = SseEmitter.event()
                            .id(Integer.toString(eventId))
                            .data(new Book("New Book #" + eventId, "Author #" + eventId), MediaType.APPLICATION_JSON)
                            .name("book");
                        emitter.send(event);
                        Thread.sleep(100);
                    }
                    emitter.complete();
                } catch (Exception ex) {
                    emitter.completeWithError(ex);
                }
            });
            
            return emitter;
        }
    }
    
    static class Book {
        private String title;
        private String author;

        public Book() {
        }

        public Book(final String title, final String author) {
            this.setTitle(title);
            this.setAuthor(author);
        }

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        public String getAuthor() {
            return author;
        }

        public void setAuthor(String author) {
            this.author = author;
        }
        
        @Override
        public int hashCode() {
            return HashCodeBuilder.reflectionHashCode(this);
        }

        @Override
        public boolean equals(Object obj) {
            return EqualsBuilder.reflectionEquals(this, obj);
        }
        
        @Override
        public String toString() {
            return ToStringBuilder.reflectionToString(this);
        }
    }

    public static void main(String[] args) {
        SpringApplication.run(SseEmitterRegressionApplication.class, args);
    }
}
  • using latest Spring Boot 2.3.4.RELEASE and Spring Framework 5.2.9.RELEASE
$ curl http://localhost:8080/sse -iv

> GET /sse HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.71.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200
< Content-Type: text/event-stream
< Transfer-Encoding: chunked
< Date: Wed, 28 Oct 2020 22:09:00 GMT
<
{ [15 bytes data]
100   335    0   335    0     0    589      0 --:--:-- --:--:-- --:--:--   590HTTP/1.1 200

Content-Type: text/event-stream
Transfer-Encoding: chunked
Date: Wed, 28 Oct 2020 22:09:00 GMT

id:1
data:{"title":"New Book #1","author":"Author #1"}
event:book

id:2
data:{"title":"New Book #2","author":"Author #2"}
event:book

id:3
data:{"title":"New Book #3","author":"Author #3"}
event:book

id:4
data:{"title":"New Book #4","author":"Author #4"}
event:book

id:5
data:{"title":"New Book #5","author":"Author #5"}
event:book
  • using latest Spring Boot 2.3.4.RELEASE and Spring Framework 5.2.10.RELEASE (overriding with <spring-framework.version>5.2.10.RELEASE</spring-framework.version>)
$ curl http://localhost:8080/sse -iv

> GET /sse HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.71.1
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200
< Content-Type: text/event-stream
< Transfer-Encoding: chunked
< Date: Wed, 28 Oct 2020 22:10:33 GMT
<
{ [15 bytes data]
100    54    0    54    0     0   2250      0 --:--:-- --:--:-- --:--:--  2250HTTP/1.1 200
Content-Type: text/event-stream
Transfer-Encoding: chunked
Date: Wed, 28 Oct 2020 22:10:33 GMT

id:1
data:{"title":"New Book #1","author":"Author #1"}

Reproducible all the time. Please advice if this is a regression or SseEmitter semantics has changed (would appreciate documentation pointers) or more details are needed, thank you.

Metadata

Metadata

Assignees

Labels

in: webIssues in web modules (web, webmvc, webflux, websocket)status: backportedAn issue that has been backported to maintenance branchestype: regressionA bug that is also a regression

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions