Refactor ResponseBodySubscriber to Processor

This commit changes the AbstractResponseBodySubscriber into a
AbstractResponseBodyProcessor<DataBuffer, Void>, so that the processor
can be used as a return value for writeWith.

Additional, this commit no longer closes the response after an eror
occurred.

This fixes #59.
This commit is contained in:
Arjen Poutsma
2016-07-05 11:20:18 +02:00
parent c85d1dc126
commit b0de99bc8c
8 changed files with 638 additions and 425 deletions

View File

@@ -0,0 +1,108 @@
/*
* Copyright 2002-2016 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.http.server.reactive;
import java.io.IOException;
import java.net.URI;
import org.junit.Test;
import reactor.core.publisher.Mono;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.http.server.reactive.boot.ReactorHttpServer;
import org.springframework.web.client.ResponseErrorHandler;
import org.springframework.web.client.RestTemplate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeFalse;
/**
* @author Arjen Poutsma
*/
public class ErrorHandlerIntegrationTests extends AbstractHttpHandlerIntegrationTests {
private ErrorHandler handler = new ErrorHandler();
@Override
protected HttpHandler createHttpHandler() {
return handler;
}
@Test
public void response() throws Exception {
// TODO: fix Reactor
assumeFalse(server instanceof ReactorHttpServer);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(NO_OP_ERROR_HANDLER);
ResponseEntity<String> response = restTemplate
.getForEntity(new URI("http://localhost:" + port + "/response"),
String.class);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
}
@Test
public void returnValue() throws Exception {
// TODO: fix Reactor
assumeFalse(server instanceof ReactorHttpServer);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setErrorHandler(NO_OP_ERROR_HANDLER);
ResponseEntity<String> response = restTemplate
.getForEntity(new URI("http://localhost:" + port + "/returnValue"),
String.class);
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, response.getStatusCode());
}
private static class ErrorHandler implements HttpHandler {
@Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
Exception error = new UnsupportedOperationException();
String path = request.getURI().getPath();
if (path.endsWith("response")) {
return response.writeWith(Mono.error(error));
}
else if (path.endsWith("returnValue")) {
return Mono.error(error);
}
else {
return Mono.empty();
}
}
}
private static final ResponseErrorHandler NO_OP_ERROR_HANDLER =
new ResponseErrorHandler() {
@Override
public boolean hasError(ClientHttpResponse response) throws IOException {
return false;
}
@Override
public void handleError(ClientHttpResponse response) throws IOException {
}
};
}

View File

@@ -21,8 +21,6 @@ import java.util.Random;
import org.junit.Test;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -31,12 +29,9 @@ import org.springframework.core.io.buffer.DataBufferFactory;
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.http.server.reactive.boot.ReactorHttpServer;
import org.springframework.web.client.RestTemplate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assert.*;
public class RandomHandlerIntegrationTests extends AbstractHttpHandlerIntegrationTests {
@@ -60,7 +55,6 @@ public class RandomHandlerIntegrationTests extends AbstractHttpHandlerIntegratio
@Test
public void random() throws Throwable {
// TODO: fix Reactor support
assumeFalse(server instanceof ReactorHttpServer);
RestTemplate restTemplate = new RestTemplate();
@@ -72,14 +66,6 @@ public class RandomHandlerIntegrationTests extends AbstractHttpHandlerIntegratio
assertEquals(RESPONSE_SIZE,
response.getHeaders().getContentLength());
assertEquals(RESPONSE_SIZE, response.getBody().length);
while (!handler.requestComplete) {
Thread.sleep(100);
}
if (handler.requestError != null) {
throw handler.requestError;
}
assertEquals(REQUEST_SIZE, handler.requestSize);
}
@@ -93,45 +79,21 @@ public class RandomHandlerIntegrationTests extends AbstractHttpHandlerIntegratio
public static final int CHUNKS = 16;
private volatile boolean requestComplete;
private int requestSize;
private Throwable requestError;
@Override
public Mono<Void> handle(ServerHttpRequest request, ServerHttpResponse response) {
requestError = null;
Mono<Integer> requestSizeMono = request.getBody().
reduce(0, (integer, dataBuffer) -> integer +
dataBuffer.readableByteCount()).
doAfterTerminate((size, throwable) -> {
assertNull(throwable);
assertEquals(REQUEST_SIZE, (long) size);
});
request.getBody().subscribe(new Subscriber<DataBuffer>() {
@Override
public void onSubscribe(Subscription s) {
requestComplete = false;
requestSize = 0;
requestError = null;
s.request(Long.MAX_VALUE);
}
@Override
public void onNext(DataBuffer bytes) {
requestSize += bytes.readableByteCount();
}
@Override
public void onError(Throwable t) {
requestComplete = true;
requestError = t;
}
@Override
public void onComplete() {
requestComplete = true;
}
});
response.getHeaders().setContentLength(RESPONSE_SIZE);
return response.writeWith(multipleChunks());
return requestSizeMono.then(response.writeWith(multipleChunks()));
}
private Publisher<DataBuffer> singleChunk() {