|
16 | 16 |
|
17 | 17 | package org.springframework.web.servlet.function;
|
18 | 18 |
|
19 |
| -import java.io.IOException; |
20 | 19 | import java.time.Duration;
|
21 | 20 | import java.util.concurrent.CompletableFuture;
|
22 |
| -import java.util.concurrent.CompletionException; |
23 |
| -import java.util.function.Function; |
24 |
| - |
25 |
| -import javax.servlet.ServletException; |
26 |
| -import javax.servlet.http.Cookie; |
27 |
| -import javax.servlet.http.HttpServletRequest; |
28 |
| -import javax.servlet.http.HttpServletResponse; |
29 | 21 |
|
30 | 22 | import org.reactivestreams.Publisher;
|
31 | 23 |
|
32 |
| -import org.springframework.core.ReactiveAdapter; |
33 | 24 | import org.springframework.core.ReactiveAdapterRegistry;
|
34 |
| -import org.springframework.http.HttpHeaders; |
35 |
| -import org.springframework.http.HttpStatus; |
36 |
| -import org.springframework.lang.Nullable; |
37 |
| -import org.springframework.util.Assert; |
38 |
| -import org.springframework.util.ClassUtils; |
39 |
| -import org.springframework.util.MultiValueMap; |
40 |
| -import org.springframework.web.context.request.async.AsyncWebRequest; |
41 |
| -import org.springframework.web.context.request.async.DeferredResult; |
42 |
| -import org.springframework.web.context.request.async.WebAsyncManager; |
43 |
| -import org.springframework.web.context.request.async.WebAsyncUtils; |
44 |
| -import org.springframework.web.servlet.ModelAndView; |
45 | 25 |
|
46 | 26 | /**
|
47 |
| - * Implementation of {@link ServerResponse} based on a {@link CompletableFuture}. |
| 27 | + * Asynchronous subtype of {@link ServerResponse} that exposes the future |
| 28 | + * response. |
48 | 29 | *
|
49 | 30 | * @author Arjen Poutsma
|
50 |
| - * @since 5.3 |
| 31 | + * @since 5.3.2 |
51 | 32 | * @see ServerResponse#async(Object)
|
52 | 33 | */
|
53 |
| -final class AsyncServerResponse extends ErrorHandlingServerResponse { |
54 |
| - |
55 |
| - static final boolean reactiveStreamsPresent = ClassUtils.isPresent( |
56 |
| - "org.reactivestreams.Publisher", AsyncServerResponse.class.getClassLoader()); |
57 |
| - |
58 |
| - |
59 |
| - private final CompletableFuture<ServerResponse> futureResponse; |
60 |
| - |
61 |
| - @Nullable |
62 |
| - private final Duration timeout; |
63 |
| - |
64 |
| - |
65 |
| - private AsyncServerResponse(CompletableFuture<ServerResponse> futureResponse, @Nullable Duration timeout) { |
66 |
| - this.futureResponse = futureResponse; |
67 |
| - this.timeout = timeout; |
68 |
| - } |
69 |
| - |
70 |
| - @Override |
71 |
| - public HttpStatus statusCode() { |
72 |
| - return delegate(ServerResponse::statusCode); |
73 |
| - } |
74 |
| - |
75 |
| - @Override |
76 |
| - public int rawStatusCode() { |
77 |
| - return delegate(ServerResponse::rawStatusCode); |
78 |
| - } |
79 |
| - |
80 |
| - @Override |
81 |
| - public HttpHeaders headers() { |
82 |
| - return delegate(ServerResponse::headers); |
83 |
| - } |
84 |
| - |
85 |
| - @Override |
86 |
| - public MultiValueMap<String, Cookie> cookies() { |
87 |
| - return delegate(ServerResponse::cookies); |
| 34 | +public interface AsyncServerResponse extends ServerResponse { |
| 35 | + |
| 36 | + /** |
| 37 | + * Blocks indefinitely until the future response is obtained. |
| 38 | + */ |
| 39 | + ServerResponse block(); |
| 40 | + |
| 41 | + |
| 42 | + // Static creation methods |
| 43 | + |
| 44 | + /** |
| 45 | + * Create a {@code AsyncServerResponse} with the given asynchronous response. |
| 46 | + * Parameter {@code asyncResponse} can be a |
| 47 | + * {@link CompletableFuture CompletableFuture<ServerResponse>} or |
| 48 | + * {@link Publisher Publisher<ServerResponse>} (or any |
| 49 | + * asynchronous producer of a single {@code ServerResponse} that can be |
| 50 | + * adapted via the {@link ReactiveAdapterRegistry}). |
| 51 | + * @param asyncResponse a {@code CompletableFuture<ServerResponse>} or |
| 52 | + * {@code Publisher<ServerResponse>} |
| 53 | + * @return the asynchronous response |
| 54 | + */ |
| 55 | + static AsyncServerResponse create(Object asyncResponse) { |
| 56 | + return DefaultAsyncServerResponse.create(asyncResponse, null); |
88 | 57 | }
|
89 | 58 |
|
90 |
| - private <R> R delegate(Function<ServerResponse, R> function) { |
91 |
| - ServerResponse response = this.futureResponse.getNow(null); |
92 |
| - if (response != null) { |
93 |
| - return function.apply(response); |
94 |
| - } |
95 |
| - else { |
96 |
| - throw new IllegalStateException("Future ServerResponse has not yet completed"); |
97 |
| - } |
| 59 | + /** |
| 60 | + * Create a (built) response with the given asynchronous response. |
| 61 | + * Parameter {@code asyncResponse} can be a |
| 62 | + * {@link CompletableFuture CompletableFuture<ServerResponse>} or |
| 63 | + * {@link Publisher Publisher<ServerResponse>} (or any |
| 64 | + * asynchronous producer of a single {@code ServerResponse} that can be |
| 65 | + * adapted via the {@link ReactiveAdapterRegistry}). |
| 66 | + * @param asyncResponse a {@code CompletableFuture<ServerResponse>} or |
| 67 | + * {@code Publisher<ServerResponse>} |
| 68 | + * @param timeout maximum time period to wait for before timing out |
| 69 | + * @return the asynchronous response |
| 70 | + */ |
| 71 | + static AsyncServerResponse create(Object asyncResponse, Duration timeout) { |
| 72 | + return DefaultAsyncServerResponse.create(asyncResponse, timeout); |
98 | 73 | }
|
99 | 74 |
|
100 |
| - @Nullable |
101 |
| - @Override |
102 |
| - public ModelAndView writeTo(HttpServletRequest request, HttpServletResponse response, Context context) |
103 |
| - throws ServletException, IOException { |
104 |
| - |
105 |
| - writeAsync(request, response, createDeferredResult()); |
106 |
| - return null; |
107 |
| - } |
108 |
| - |
109 |
| - static void writeAsync(HttpServletRequest request, HttpServletResponse response, DeferredResult<?> deferredResult) |
110 |
| - throws ServletException, IOException { |
111 |
| - |
112 |
| - WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); |
113 |
| - AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response); |
114 |
| - asyncManager.setAsyncWebRequest(asyncWebRequest); |
115 |
| - try { |
116 |
| - asyncManager.startDeferredResultProcessing(deferredResult); |
117 |
| - } |
118 |
| - catch (IOException | ServletException ex) { |
119 |
| - throw ex; |
120 |
| - } |
121 |
| - catch (Exception ex) { |
122 |
| - throw new ServletException("Async processing failed", ex); |
123 |
| - } |
124 |
| - |
125 |
| - } |
126 |
| - |
127 |
| - private DeferredResult<ServerResponse> createDeferredResult() { |
128 |
| - DeferredResult<ServerResponse> result; |
129 |
| - if (this.timeout != null) { |
130 |
| - result = new DeferredResult<>(this.timeout.toMillis()); |
131 |
| - } |
132 |
| - else { |
133 |
| - result = new DeferredResult<>(); |
134 |
| - } |
135 |
| - this.futureResponse.handle((value, ex) -> { |
136 |
| - if (ex != null) { |
137 |
| - if (ex instanceof CompletionException && ex.getCause() != null) { |
138 |
| - ex = ex.getCause(); |
139 |
| - } |
140 |
| - result.setErrorResult(ex); |
141 |
| - } |
142 |
| - else { |
143 |
| - result.setResult(value); |
144 |
| - } |
145 |
| - return null; |
146 |
| - }); |
147 |
| - return result; |
148 |
| - } |
149 |
| - |
150 |
| - |
151 |
| - @SuppressWarnings({"unchecked"}) |
152 |
| - public static ServerResponse create(Object o, @Nullable Duration timeout) { |
153 |
| - Assert.notNull(o, "Argument to async must not be null"); |
154 |
| - |
155 |
| - if (o instanceof CompletableFuture) { |
156 |
| - CompletableFuture<ServerResponse> futureResponse = (CompletableFuture<ServerResponse>) o; |
157 |
| - return new AsyncServerResponse(futureResponse, timeout); |
158 |
| - } |
159 |
| - else if (reactiveStreamsPresent) { |
160 |
| - ReactiveAdapterRegistry registry = ReactiveAdapterRegistry.getSharedInstance(); |
161 |
| - ReactiveAdapter publisherAdapter = registry.getAdapter(o.getClass()); |
162 |
| - if (publisherAdapter != null) { |
163 |
| - Publisher<ServerResponse> publisher = publisherAdapter.toPublisher(o); |
164 |
| - ReactiveAdapter futureAdapter = registry.getAdapter(CompletableFuture.class); |
165 |
| - if (futureAdapter != null) { |
166 |
| - CompletableFuture<ServerResponse> futureResponse = |
167 |
| - (CompletableFuture<ServerResponse>) futureAdapter.fromPublisher(publisher); |
168 |
| - return new AsyncServerResponse(futureResponse, timeout); |
169 |
| - } |
170 |
| - } |
171 |
| - } |
172 |
| - throw new IllegalArgumentException("Asynchronous type not supported: " + o.getClass()); |
173 |
| - } |
174 |
| - |
175 |
| - |
176 | 75 | }
|
| 76 | + |
0 commit comments