|
50 | 50 | */
|
51 | 51 | class WiretapConnector implements ClientHttpConnector {
|
52 | 52 |
|
53 |
| - private static final DataBufferFactory bufferFactory = new DefaultDataBufferFactory(); |
54 |
| - |
55 |
| - |
56 | 53 | private final ClientHttpConnector delegate;
|
57 | 54 |
|
58 | 55 | private final Map<String, Info> exchanges = new ConcurrentHashMap<>();
|
@@ -117,119 +114,157 @@ public Info(WiretapClientHttpRequest request, WiretapClientHttpResponse response
|
117 | 114 |
|
118 | 115 | public ExchangeResult createExchangeResult(@Nullable String uriTemplate) {
|
119 | 116 | return new ExchangeResult(this.request, this.response,
|
120 |
| - this.request.getContent(), this.response.getContent(), uriTemplate); |
| 117 | + this.request.getRecorder().getContent(), this.response.getRecorder().getContent(), uriTemplate); |
121 | 118 | }
|
122 | 119 | }
|
123 | 120 |
|
124 | 121 |
|
125 | 122 | /**
|
126 |
| - * ClientHttpRequestDecorator that intercepts and saves the request body. |
| 123 | + * Tap into a Publisher of data buffers to save the content. |
127 | 124 | */
|
128 |
| - private static class WiretapClientHttpRequest extends ClientHttpRequestDecorator { |
| 125 | + final static class WiretapRecorder { |
129 | 126 |
|
130 |
| - private final DataBuffer buffer; |
| 127 | + private static final DataBufferFactory bufferFactory = new DefaultDataBufferFactory(); |
131 | 128 |
|
132 |
| - private final MonoProcessor<byte[]> body = MonoProcessor.create(); |
| 129 | + public static final byte[] EMPTY_CONTENT = new byte[0]; |
133 | 130 |
|
134 | 131 |
|
135 |
| - public WiretapClientHttpRequest(ClientHttpRequest delegate) { |
136 |
| - super(delegate); |
137 |
| - this.buffer = bufferFactory.allocateBuffer(); |
138 |
| - } |
| 132 | + @Nullable |
| 133 | + private final Publisher<? extends DataBuffer> publisher; |
139 | 134 |
|
| 135 | + @Nullable |
| 136 | + private final Publisher<? extends Publisher<? extends DataBuffer>> publisherNested; |
140 | 137 |
|
141 |
| - /** |
142 |
| - * Return a "promise" with the request body content written to the server. |
143 |
| - */ |
144 |
| - public MonoProcessor<byte[]> getContent() { |
145 |
| - return this.body; |
146 |
| - } |
| 138 | + private final DataBuffer buffer; |
147 | 139 |
|
| 140 | + private final MonoProcessor<byte[]> content; |
148 | 141 |
|
149 |
| - @Override |
150 |
| - public Mono<Void> writeWith(Publisher<? extends DataBuffer> publisher) { |
151 |
| - return super.writeWith( |
| 142 | + |
| 143 | + private WiretapRecorder(@Nullable Publisher<? extends DataBuffer> publisher, |
| 144 | + @Nullable Publisher<? extends Publisher<? extends DataBuffer>> publisherNested) { |
| 145 | + |
| 146 | + if (publisher != null && publisherNested != null) { |
| 147 | + throw new IllegalArgumentException("At most one publisher expected"); |
| 148 | + } |
| 149 | + |
| 150 | + this.publisher = publisher != null ? |
152 | 151 | Flux.from(publisher)
|
153 | 152 | .doOnNext(this::handleOnNext)
|
154 |
| - .doOnError(this::handleError) |
| 153 | + .doOnError(this::handleOnError) |
155 | 154 | .doOnCancel(this::handleOnComplete)
|
156 |
| - .doOnComplete(this::handleOnComplete)); |
157 |
| - } |
| 155 | + .doOnComplete(this::handleOnComplete) : null; |
158 | 156 |
|
159 |
| - @Override |
160 |
| - public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> publisher) { |
161 |
| - return super.writeAndFlushWith( |
162 |
| - Flux.from(publisher) |
163 |
| - .map(p -> Flux.from(p).doOnNext(this::handleOnNext).doOnError(this::handleError)) |
164 |
| - .doOnError(this::handleError) |
| 157 | + this.publisherNested = publisherNested != null ? |
| 158 | + Flux.from(publisherNested) |
| 159 | + .map(p -> Flux.from(p).doOnNext(this::handleOnNext).doOnError(this::handleOnError)) |
| 160 | + .doOnError(this::handleOnError) |
165 | 161 | .doOnCancel(this::handleOnComplete)
|
166 |
| - .doOnComplete(this::handleOnComplete)); |
| 162 | + .doOnComplete(this::handleOnComplete) : null; |
| 163 | + |
| 164 | + this.buffer = bufferFactory.allocateBuffer(); |
| 165 | + this.content = MonoProcessor.create(); |
| 166 | + |
| 167 | + if (this.publisher == null && this.publisherNested == null) { |
| 168 | + this.content.onNext(EMPTY_CONTENT); |
| 169 | + } |
167 | 170 | }
|
168 | 171 |
|
169 |
| - @Override |
170 |
| - public Mono<Void> setComplete() { |
171 |
| - handleOnComplete(); |
172 |
| - return super.setComplete(); |
| 172 | + |
| 173 | + public Publisher<? extends DataBuffer> getPublisherToUse() { |
| 174 | + Assert.notNull(this.publisher, "Publisher not in use."); |
| 175 | + return this.publisher; |
173 | 176 | }
|
174 | 177 |
|
175 |
| - private void handleOnNext(DataBuffer buffer) { |
176 |
| - this.buffer.write(buffer); |
| 178 | + public Publisher<? extends Publisher<? extends DataBuffer>> getNestedPublisherToUse() { |
| 179 | + Assert.notNull(this.publisherNested, "Nested publisher not in use."); |
| 180 | + return this.publisherNested; |
177 | 181 | }
|
178 | 182 |
|
179 |
| - private void handleError(Throwable ex) { |
180 |
| - if (!this.body.isTerminated()) { |
181 |
| - this.body.onError(ex); |
| 183 | + public MonoProcessor<byte[]> getContent() { |
| 184 | + return this.content; |
| 185 | + } |
| 186 | + |
| 187 | + |
| 188 | + private void handleOnNext(DataBuffer nextBuffer) { |
| 189 | + this.buffer.write(nextBuffer); |
| 190 | + } |
| 191 | + |
| 192 | + private void handleOnError(Throwable ex) { |
| 193 | + if (!this.content.isTerminated()) { |
| 194 | + this.content.onError(ex); |
182 | 195 | }
|
183 | 196 | }
|
184 | 197 |
|
185 | 198 | private void handleOnComplete() {
|
186 |
| - if (!this.body.isTerminated()) { |
| 199 | + if (!this.content.isTerminated()) { |
187 | 200 | byte[] bytes = new byte[this.buffer.readableByteCount()];
|
188 | 201 | this.buffer.read(bytes);
|
189 |
| - this.body.onNext(bytes); |
| 202 | + this.content.onNext(bytes); |
190 | 203 | }
|
191 | 204 | }
|
192 | 205 | }
|
193 | 206 |
|
194 | 207 |
|
| 208 | + /** |
| 209 | + * ClientHttpRequestDecorator that intercepts and saves the request body. |
| 210 | + */ |
| 211 | + private static class WiretapClientHttpRequest extends ClientHttpRequestDecorator { |
| 212 | + |
| 213 | + @Nullable |
| 214 | + private WiretapRecorder recorder; |
| 215 | + |
| 216 | + |
| 217 | + public WiretapClientHttpRequest(ClientHttpRequest delegate) { |
| 218 | + super(delegate); |
| 219 | + } |
| 220 | + |
| 221 | + public WiretapRecorder getRecorder() { |
| 222 | + Assert.notNull(this.recorder, "No WiretapRecorder: was the client request written?"); |
| 223 | + return this.recorder; |
| 224 | + } |
| 225 | + |
| 226 | + @Override |
| 227 | + public Mono<Void> writeWith(Publisher<? extends DataBuffer> publisher) { |
| 228 | + this.recorder = new WiretapRecorder(publisher, null); |
| 229 | + return super.writeWith(this.recorder.getPublisherToUse()); |
| 230 | + } |
| 231 | + |
| 232 | + @Override |
| 233 | + public Mono<Void> writeAndFlushWith(Publisher<? extends Publisher<? extends DataBuffer>> publisher) { |
| 234 | + this.recorder = new WiretapRecorder(null, publisher); |
| 235 | + return super.writeAndFlushWith(this.recorder.getNestedPublisherToUse()); |
| 236 | + } |
| 237 | + |
| 238 | + @Override |
| 239 | + public Mono<Void> setComplete() { |
| 240 | + this.recorder = new WiretapRecorder(null, null); |
| 241 | + return super.setComplete(); |
| 242 | + } |
| 243 | + } |
| 244 | + |
| 245 | + |
195 | 246 | /**
|
196 | 247 | * ClientHttpResponseDecorator that intercepts and saves the response body.
|
197 | 248 | */
|
198 | 249 | private static class WiretapClientHttpResponse extends ClientHttpResponseDecorator {
|
199 | 250 |
|
200 |
| - private final DataBuffer buffer; |
201 |
| - |
202 |
| - private final MonoProcessor<byte[]> body = MonoProcessor.create(); |
| 251 | + private final WiretapRecorder recorder; |
203 | 252 |
|
204 | 253 |
|
205 | 254 | public WiretapClientHttpResponse(ClientHttpResponse delegate) {
|
206 | 255 | super(delegate);
|
207 |
| - this.buffer = bufferFactory.allocateBuffer(); |
| 256 | + this.recorder = new WiretapRecorder(super.getBody(), null); |
208 | 257 | }
|
209 | 258 |
|
210 | 259 |
|
211 |
| - /** |
212 |
| - * Return a "promise" with the response body content read from the server. |
213 |
| - */ |
214 |
| - public MonoProcessor<byte[]> getContent() { |
215 |
| - return this.body; |
| 260 | + public WiretapRecorder getRecorder() { |
| 261 | + return this.recorder; |
216 | 262 | }
|
217 | 263 |
|
218 | 264 | @Override
|
| 265 | + @SuppressWarnings("ConstantConditions") |
219 | 266 | public Flux<DataBuffer> getBody() {
|
220 |
| - return super.getBody() |
221 |
| - .doOnNext(this.buffer::write) |
222 |
| - .doOnError(this.body::onError) |
223 |
| - .doOnCancel(this::handleOnComplete) |
224 |
| - .doOnComplete(this::handleOnComplete); |
225 |
| - } |
226 |
| - |
227 |
| - private void handleOnComplete() { |
228 |
| - if (!this.body.isTerminated()) { |
229 |
| - byte[] bytes = new byte[this.buffer.readableByteCount()]; |
230 |
| - this.buffer.read(bytes); |
231 |
| - this.body.onNext(bytes); |
232 |
| - } |
| 267 | + return Flux.from(this.recorder.getPublisherToUse()); |
233 | 268 | }
|
234 | 269 | }
|
235 | 270 |
|
|
0 commit comments