Fine-tuned concurrency and general polishing in SSE support classes

Issue: SPR-12212
This commit is contained in:
Juergen Hoeller 2015-03-25 00:39:26 +01:00
parent 4e1af7d195
commit 935ffc5827
3 changed files with 18 additions and 29 deletions

View File

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

View File

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

View File

@ -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");