Ensure client response is drained with onStatus hook

Issue: SPR-17473
This commit is contained in:
Rossen Stoyanchev
2018-11-08 13:26:41 -05:00
parent ed1d63dcc3
commit c187cb2fa1
6 changed files with 224 additions and 25 deletions

View File

@@ -0,0 +1,151 @@
/*
* Copyright 2002-2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.reactive.function.client;
import java.time.Duration;
import java.util.function.Function;
import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.ChannelOption;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import org.springframework.core.io.buffer.AbstractDataBufferAllocatingTestCase;
import org.springframework.core.io.buffer.NettyDataBufferFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.http.client.reactive.ReactorResourceFactory;
import static org.junit.Assert.*;
/**
* WebClient integration tests focusing on data buffer management.
* @author Rossen Stoyanchev
*/
public class WebClientDataBufferAllocatingTests extends AbstractDataBufferAllocatingTestCase {
private static final Duration DELAY = Duration.ofSeconds(5);
private MockWebServer server;
private WebClient webClient;
private ReactorResourceFactory factory;
@Before
public void setUp() {
this.factory = new ReactorResourceFactory();
this.factory.afterPropertiesSet();
this.server = new MockWebServer();
this.webClient = WebClient
.builder()
.clientConnector(initConnector())
.baseUrl(this.server.url("/").toString())
.build();
}
private ReactorClientHttpConnector initConnector() {
if (bufferFactory instanceof NettyDataBufferFactory) {
ByteBufAllocator allocator = ((NettyDataBufferFactory) bufferFactory).getByteBufAllocator();
return new ReactorClientHttpConnector(this.factory, httpClient ->
httpClient.tcpConfiguration(tcpClient -> tcpClient.option(ChannelOption.ALLOCATOR, allocator)));
}
else {
return new ReactorClientHttpConnector();
}
}
@After
public void shutDown() throws InterruptedException {
waitForDataBufferRelease(Duration.ofSeconds(2));
this.factory.destroy();
}
@Test
public void bodyToMonoVoid() {
this.server.enqueue(new MockResponse()
.setResponseCode(201)
.setHeader("Content-Type", "application/json")
.setChunkedBody("{\"foo\" : {\"bar\" : \"123\", \"baz\" : \"456\"}}", 5));
Mono<Void> mono = this.webClient.get()
.uri("/json").accept(MediaType.APPLICATION_JSON)
.retrieve()
.bodyToMono(Void.class);
StepVerifier.create(mono).expectComplete().verify(Duration.ofSeconds(3));
assertEquals(1, this.server.getRequestCount());
}
@Test
public void onStatusWithBodyNotConsumed() {
RuntimeException ex = new RuntimeException("response error");
testOnStatus(ex, response -> Mono.just(ex));
}
@Test
public void onStatusWithBodyConsumed() {
RuntimeException ex = new RuntimeException("response error");
testOnStatus(ex, response -> response.bodyToMono(Void.class).thenReturn(ex));
}
@Test // SPR-17473
public void onStatusWithMonoErrorAndBodyNotConsumed() {
RuntimeException ex = new RuntimeException("response error");
testOnStatus(ex, response -> Mono.error(ex));
}
@Test
public void onStatusWithMonoErrorAndBodyConsumed() {
RuntimeException ex = new RuntimeException("response error");
testOnStatus(ex, response -> response.bodyToMono(Void.class).then(Mono.error(ex)));
}
private void testOnStatus(Throwable expected,
Function<ClientResponse, Mono<? extends Throwable>> exceptionFunction) {
HttpStatus errorStatus = HttpStatus.BAD_GATEWAY;
this.server.enqueue(new MockResponse()
.setResponseCode(errorStatus.value())
.setHeader("Content-Type", "application/json")
.setChunkedBody("{\"error\" : {\"status\" : 502, \"message\" : \"Bad gateway.\"}}", 5));
Mono<String> mono = this.webClient.get()
.uri("/json").accept(MediaType.APPLICATION_JSON)
.retrieve()
.onStatus(status -> status.equals(errorStatus), exceptionFunction)
.bodyToMono(String.class);
StepVerifier.create(mono).expectErrorSatisfies(actual -> assertSame(expected, actual)).verify(DELAY);
assertEquals(1, this.server.getRequestCount());
}
}