Fine-tuned concurrency and general polishing in SSE support classes
Issue: SPR-12212
This commit is contained in:
parent
4e1af7d195
commit
935ffc5827
|
|
@ -13,6 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.mvc.method.annotation;
|
||||
|
||||
import java.io.IOException;
|
||||
|
|
@ -23,7 +24,6 @@ import org.springframework.http.MediaType;
|
|||
import org.springframework.http.server.ServerHttpResponse;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
|
||||
/**
|
||||
* A controller method return value type for asynchronous request processing
|
||||
* where one or more objects are written to the response. While
|
||||
|
|
@ -53,15 +53,12 @@ import org.springframework.util.Assert;
|
|||
* emitter.complete();
|
||||
* </pre>
|
||||
*
|
||||
* <p><strong>Note:</strong> this class is not thread-safe. Callers must ensure
|
||||
* that use from multiple threads is synchronized.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 4.2
|
||||
*/
|
||||
public class ResponseBodyEmitter {
|
||||
|
||||
private Handler handler;
|
||||
private volatile Handler handler;
|
||||
|
||||
/* Cache for objects sent before handler is set. */
|
||||
private final Map<Object, MediaType> initHandlerCache = new LinkedHashMap<Object, MediaType>(10);
|
||||
|
|
@ -124,7 +121,7 @@ public class ResponseBodyEmitter {
|
|||
* @throws java.lang.IllegalStateException wraps any other errors
|
||||
*/
|
||||
public void send(Object object, MediaType mediaType) throws IOException {
|
||||
Assert.state(!this.complete, "ResponseBodyEmitter is already set complete.");
|
||||
Assert.state(!this.complete, "ResponseBodyEmitter is already set complete");
|
||||
sendInternal(object, mediaType);
|
||||
}
|
||||
|
||||
|
|
@ -132,9 +129,9 @@ public class ResponseBodyEmitter {
|
|||
if (object == null) {
|
||||
return;
|
||||
}
|
||||
if (handler == null) {
|
||||
if (this.handler == null) {
|
||||
synchronized (this) {
|
||||
if (handler == null) {
|
||||
if (this.handler == null) {
|
||||
this.initHandlerCache.put(object, mediaType);
|
||||
return;
|
||||
}
|
||||
|
|
@ -143,11 +140,11 @@ public class ResponseBodyEmitter {
|
|||
try {
|
||||
this.handler.send(object, mediaType);
|
||||
}
|
||||
catch(IOException ex){
|
||||
catch (IOException ex){
|
||||
this.handler.completeWithError(ex);
|
||||
throw ex;
|
||||
}
|
||||
catch(Throwable ex){
|
||||
catch (Throwable ex){
|
||||
this.handler.completeWithError(ex);
|
||||
throw new IllegalStateException("Failed to send " + object, ex);
|
||||
}
|
||||
|
|
@ -161,7 +158,7 @@ public class ResponseBodyEmitter {
|
|||
public void complete() {
|
||||
synchronized (this) {
|
||||
this.complete = true;
|
||||
if (handler != null) {
|
||||
if (this.handler != null) {
|
||||
this.handler.complete();
|
||||
}
|
||||
}
|
||||
|
|
@ -176,7 +173,7 @@ public class ResponseBodyEmitter {
|
|||
synchronized (this) {
|
||||
this.complete = true;
|
||||
this.failure = ex;
|
||||
if (handler != null) {
|
||||
if (this.handler != null) {
|
||||
this.handler.completeWithError(ex);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,18 +13,17 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.mvc.method.annotation;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.core.MethodParameter;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
|
|
@ -41,7 +40,6 @@ import org.springframework.web.context.request.async.WebAsyncUtils;
|
|||
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
|
||||
import org.springframework.web.method.support.ModelAndViewContainer;
|
||||
|
||||
|
||||
/**
|
||||
* Supports return values of type {@link ResponseBodyEmitter} and also
|
||||
* {@code ResponseEntity<ResponseBodyEmitter>}.
|
||||
|
|
@ -61,6 +59,7 @@ public class ResponseBodyEmitterReturnValueHandler implements HandlerMethodRetur
|
|||
this.messageConverters = messageConverters;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean supportsReturnType(MethodParameter returnType) {
|
||||
if (ResponseBodyEmitter.class.isAssignableFrom(returnType.getParameterType())) {
|
||||
|
|
@ -121,13 +120,11 @@ public class ResponseBodyEmitterReturnValueHandler implements HandlerMethodRetur
|
|||
|
||||
private final DeferredResult<?> deferredResult;
|
||||
|
||||
|
||||
public HttpMessageConvertingHandler(ServerHttpResponse outputMessage, DeferredResult<?> deferredResult) {
|
||||
this.outputMessage = outputMessage;
|
||||
this.deferredResult = deferredResult;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void send(Object data, MediaType mediaType) throws IOException {
|
||||
sendInternal(data, mediaType);
|
||||
|
|
@ -145,7 +142,7 @@ public class ResponseBodyEmitterReturnValueHandler implements HandlerMethodRetur
|
|||
return;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No suitable converter for " + data);
|
||||
throw new IllegalArgumentException("No suitable converter for " + data.getClass());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -170,13 +167,11 @@ public class ResponseBodyEmitterReturnValueHandler implements HandlerMethodRetur
|
|||
|
||||
private final HttpHeaders mutableHeaders = new HttpHeaders();
|
||||
|
||||
|
||||
public StreamingServletServerHttpResponse(ServerHttpResponse delegate) {
|
||||
this.delegate = delegate;
|
||||
this.mutableHeaders.putAll(delegate.getHeaders());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setStatusCode(HttpStatus status) {
|
||||
this.delegate.setStatusCode(status);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.web.servlet.mvc.method.annotation;
|
||||
|
||||
import java.io.IOException;
|
||||
|
|
@ -26,9 +27,7 @@ import org.springframework.http.MediaType;
|
|||
import org.springframework.http.server.ServerHttpResponse;
|
||||
|
||||
/**
|
||||
* A specialization of
|
||||
* {@link org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter
|
||||
* ResponseBodyEmitter} for sending
|
||||
* A specialization of {@link ResponseBodyEmitter} for sending
|
||||
* <a href="http://www.w3.org/TR/eventsource/">Server-Sent Events</a>.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
|
|
@ -36,7 +35,7 @@ import org.springframework.http.server.ServerHttpResponse;
|
|||
*/
|
||||
public class SseEmitter extends ResponseBodyEmitter {
|
||||
|
||||
public static final MediaType TEXT_PLAIN = new MediaType("text", "plain", Charset.forName("UTF-8"));
|
||||
static final MediaType TEXT_PLAIN = new MediaType("text", "plain", Charset.forName("UTF-8"));
|
||||
|
||||
|
||||
@Override
|
||||
|
|
@ -51,7 +50,6 @@ public class SseEmitter extends ResponseBodyEmitter {
|
|||
/**
|
||||
* Send the object formatted as a single SSE "data" line. It's equivalent to:
|
||||
* <pre>
|
||||
*
|
||||
* // static import of SseEmitter.*
|
||||
*
|
||||
* SseEmitter emitter = new SseEmitter();
|
||||
|
|
@ -69,7 +67,6 @@ public class SseEmitter extends ResponseBodyEmitter {
|
|||
/**
|
||||
* Send the object formatted as a single SSE "data" line. It's equivalent to:
|
||||
* <pre>
|
||||
*
|
||||
* // static import of SseEmitter.*
|
||||
*
|
||||
* SseEmitter emitter = new SseEmitter();
|
||||
|
|
@ -91,7 +88,6 @@ public class SseEmitter extends ResponseBodyEmitter {
|
|||
/**
|
||||
* Send an SSE event prepared with the given builder. For example:
|
||||
* <pre>
|
||||
*
|
||||
* // static import of SseEmitter
|
||||
*
|
||||
* SseEmitter emitter = new SseEmitter();
|
||||
|
|
@ -108,6 +104,7 @@ public class SseEmitter extends ResponseBodyEmitter {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public static SseEventBuilder event() {
|
||||
return new DefaultSseEventBuilder();
|
||||
}
|
||||
|
|
@ -156,6 +153,7 @@ public class SseEmitter extends ResponseBodyEmitter {
|
|||
Map<Object, MediaType> build();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Default implementation of SseEventBuilder.
|
||||
*/
|
||||
|
|
@ -165,7 +163,6 @@ public class SseEmitter extends ResponseBodyEmitter {
|
|||
|
||||
private StringBuilder sb;
|
||||
|
||||
|
||||
@Override
|
||||
public SseEventBuilder comment(String comment) {
|
||||
append(":").append(comment != null ? comment : "").append("\n");
|
||||
|
|
|
|||
Loading…
Reference in New Issue