|
|
|
@ -17,8 +17,8 @@
@@ -17,8 +17,8 @@
|
|
|
|
|
package org.springframework.web.servlet.mvc.method.annotation; |
|
|
|
|
|
|
|
|
|
import java.io.IOException; |
|
|
|
|
import java.util.LinkedHashMap; |
|
|
|
|
import java.util.Map; |
|
|
|
|
import java.util.LinkedHashSet; |
|
|
|
|
import java.util.Set; |
|
|
|
|
|
|
|
|
|
import org.springframework.http.MediaType; |
|
|
|
|
import org.springframework.http.server.ServerHttpResponse; |
|
|
|
@ -54,18 +54,18 @@ import org.springframework.util.Assert;
@@ -54,18 +54,18 @@ import org.springframework.util.Assert;
|
|
|
|
|
* </pre> |
|
|
|
|
* |
|
|
|
|
* @author Rossen Stoyanchev |
|
|
|
|
* @author Juergen Hoeller |
|
|
|
|
* @since 4.2 |
|
|
|
|
*/ |
|
|
|
|
public class ResponseBodyEmitter { |
|
|
|
|
|
|
|
|
|
private final Long timeout; |
|
|
|
|
|
|
|
|
|
private volatile Handler handler; |
|
|
|
|
private final Set<DataWithMediaType> earlySendAttempts = new LinkedHashSet<DataWithMediaType>(8); |
|
|
|
|
|
|
|
|
|
/* Cache for objects sent before handler is set. */ |
|
|
|
|
private final Map<Object, MediaType> initHandlerCache = new LinkedHashMap<Object, MediaType>(10); |
|
|
|
|
private Handler handler; |
|
|
|
|
|
|
|
|
|
private volatile boolean complete; |
|
|
|
|
private boolean complete; |
|
|
|
|
|
|
|
|
|
private Throwable failure; |
|
|
|
|
|
|
|
|
@ -110,32 +110,29 @@ public class ResponseBodyEmitter {
@@ -110,32 +110,29 @@ public class ResponseBodyEmitter {
|
|
|
|
|
protected void extendResponse(ServerHttpResponse outputMessage) { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
void initialize(Handler handler) throws IOException { |
|
|
|
|
synchronized (this) { |
|
|
|
|
this.handler = handler; |
|
|
|
|
for (Map.Entry<Object, MediaType> entry : this.initHandlerCache.entrySet()) { |
|
|
|
|
try { |
|
|
|
|
sendInternal(entry.getKey(), entry.getValue()); |
|
|
|
|
} |
|
|
|
|
catch (Throwable ex) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (this.complete) { |
|
|
|
|
if (this.failure != null) { |
|
|
|
|
this.handler.completeWithError(this.failure); |
|
|
|
|
} |
|
|
|
|
else { |
|
|
|
|
this.handler.complete(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (this.timeoutCallback != null) { |
|
|
|
|
this.handler.onTimeout(this.timeoutCallback); |
|
|
|
|
synchronized void initialize(Handler handler) throws IOException { |
|
|
|
|
this.handler = handler; |
|
|
|
|
|
|
|
|
|
for (DataWithMediaType sendAttempt : this.earlySendAttempts) { |
|
|
|
|
sendInternal(sendAttempt.getData(), sendAttempt.getMediaType()); |
|
|
|
|
} |
|
|
|
|
this.earlySendAttempts.clear(); |
|
|
|
|
|
|
|
|
|
if (this.complete) { |
|
|
|
|
if (this.failure != null) { |
|
|
|
|
this.handler.completeWithError(this.failure); |
|
|
|
|
} |
|
|
|
|
if (this.completionCallback != null) { |
|
|
|
|
this.handler.onCompletion(this.completionCallback); |
|
|
|
|
else { |
|
|
|
|
this.handler.complete(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (this.timeoutCallback != null) { |
|
|
|
|
this.handler.onTimeout(this.timeoutCallback); |
|
|
|
|
} |
|
|
|
|
if (this.completionCallback != null) { |
|
|
|
|
this.handler.onCompletion(this.completionCallback); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
@ -159,33 +156,29 @@ public class ResponseBodyEmitter {
@@ -159,33 +156,29 @@ public class ResponseBodyEmitter {
|
|
|
|
|
* @throws IOException raised when an I/O error occurs |
|
|
|
|
* @throws java.lang.IllegalStateException wraps any other errors |
|
|
|
|
*/ |
|
|
|
|
public void send(Object object, MediaType mediaType) throws IOException { |
|
|
|
|
public synchronized void send(Object object, MediaType mediaType) throws IOException { |
|
|
|
|
Assert.state(!this.complete, "ResponseBodyEmitter is already set complete"); |
|
|
|
|
sendInternal(object, mediaType); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private void sendInternal(Object object, MediaType mediaType) throws IOException { |
|
|
|
|
if (object == null) { |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
if (this.handler == null) { |
|
|
|
|
synchronized (this) { |
|
|
|
|
if (this.handler == null) { |
|
|
|
|
this.initHandlerCache.put(object, mediaType); |
|
|
|
|
return; |
|
|
|
|
if (object != null) { |
|
|
|
|
if (this.handler != null) { |
|
|
|
|
try { |
|
|
|
|
this.handler.send(object, mediaType); |
|
|
|
|
} |
|
|
|
|
catch (IOException ex) { |
|
|
|
|
this.handler.completeWithError(ex); |
|
|
|
|
throw ex; |
|
|
|
|
} |
|
|
|
|
catch (Throwable ex) { |
|
|
|
|
this.handler.completeWithError(ex); |
|
|
|
|
throw new IllegalStateException("Failed to send " + object, ex); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
try { |
|
|
|
|
this.handler.send(object, mediaType); |
|
|
|
|
} |
|
|
|
|
catch (IOException ex){ |
|
|
|
|
this.handler.completeWithError(ex); |
|
|
|
|
throw ex; |
|
|
|
|
} |
|
|
|
|
catch (Throwable ex){ |
|
|
|
|
this.handler.completeWithError(ex); |
|
|
|
|
throw new IllegalStateException("Failed to send " + object, ex); |
|
|
|
|
else { |
|
|
|
|
this.earlySendAttempts.add(new DataWithMediaType(object, mediaType)); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -194,12 +187,10 @@ public class ResponseBodyEmitter {
@@ -194,12 +187,10 @@ public class ResponseBodyEmitter {
|
|
|
|
|
* <p>A dispatch is made into the app server where Spring MVC completes |
|
|
|
|
* asynchronous request processing. |
|
|
|
|
*/ |
|
|
|
|
public void complete() { |
|
|
|
|
synchronized (this) { |
|
|
|
|
this.complete = true; |
|
|
|
|
if (this.handler != null) { |
|
|
|
|
this.handler.complete(); |
|
|
|
|
} |
|
|
|
|
public synchronized void complete() { |
|
|
|
|
this.complete = true; |
|
|
|
|
if (this.handler != null) { |
|
|
|
|
this.handler.complete(); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -208,13 +199,11 @@ public class ResponseBodyEmitter {
@@ -208,13 +199,11 @@ public class ResponseBodyEmitter {
|
|
|
|
|
* <p>A dispatch is made into the app server where Spring MVC will pass the |
|
|
|
|
* exception through its exception handling mechanism. |
|
|
|
|
*/ |
|
|
|
|
public void completeWithError(Throwable ex) { |
|
|
|
|
synchronized (this) { |
|
|
|
|
this.complete = true; |
|
|
|
|
this.failure = ex; |
|
|
|
|
if (this.handler != null) { |
|
|
|
|
this.handler.completeWithError(ex); |
|
|
|
|
} |
|
|
|
|
public synchronized void completeWithError(Throwable ex) { |
|
|
|
|
this.complete = true; |
|
|
|
|
this.failure = ex; |
|
|
|
|
if (this.handler != null) { |
|
|
|
|
this.handler.completeWithError(ex); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -222,12 +211,10 @@ public class ResponseBodyEmitter {
@@ -222,12 +211,10 @@ public class ResponseBodyEmitter {
|
|
|
|
|
* Register code to invoke when the async request times out. This method is |
|
|
|
|
* called from a container thread when an async request times out. |
|
|
|
|
*/ |
|
|
|
|
public void onTimeout(Runnable callback) { |
|
|
|
|
synchronized (this) { |
|
|
|
|
this.timeoutCallback = callback; |
|
|
|
|
if (this.handler != null) { |
|
|
|
|
this.handler.onTimeout(callback); |
|
|
|
|
} |
|
|
|
|
public synchronized void onTimeout(Runnable callback) { |
|
|
|
|
this.timeoutCallback = callback; |
|
|
|
|
if (this.handler != null) { |
|
|
|
|
this.handler.onTimeout(callback); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -237,12 +224,10 @@ public class ResponseBodyEmitter {
@@ -237,12 +224,10 @@ public class ResponseBodyEmitter {
|
|
|
|
|
* reason including timeout and network error. This method is useful for |
|
|
|
|
* detecting that a {@code ResponseBodyEmitter} instance is no longer usable. |
|
|
|
|
*/ |
|
|
|
|
public void onCompletion(Runnable callback) { |
|
|
|
|
synchronized (this) { |
|
|
|
|
this.completionCallback = callback; |
|
|
|
|
if (this.handler != null) { |
|
|
|
|
this.handler.onCompletion(callback); |
|
|
|
|
} |
|
|
|
|
public synchronized void onCompletion(Runnable callback) { |
|
|
|
|
this.completionCallback = callback; |
|
|
|
|
if (this.handler != null) { |
|
|
|
|
this.handler.onCompletion(callback); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -263,4 +248,28 @@ public class ResponseBodyEmitter {
@@ -263,4 +248,28 @@ public class ResponseBodyEmitter {
|
|
|
|
|
void onCompletion(Runnable callback); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Simple struct for a data entry. |
|
|
|
|
*/ |
|
|
|
|
static class DataWithMediaType { |
|
|
|
|
|
|
|
|
|
private final Object data; |
|
|
|
|
|
|
|
|
|
private final MediaType mediaType; |
|
|
|
|
|
|
|
|
|
public DataWithMediaType(Object data, MediaType mediaType) { |
|
|
|
|
this.data = data; |
|
|
|
|
this.mediaType = mediaType; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public Object getData() { |
|
|
|
|
return this.data; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public MediaType getMediaType() { |
|
|
|
|
return this.mediaType; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|