/* * Copyright 2002-2017 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.test.web.reactive.server; import java.net.URI; import java.util.Optional; import java.util.function.Function; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.reactivestreams.Publisher; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.publisher.MonoProcessor; import org.springframework.core.io.buffer.DataBuffer; import org.springframework.http.HttpCookie; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.client.reactive.ClientHttpConnector; import org.springframework.http.client.reactive.ClientHttpRequest; import org.springframework.http.client.reactive.ClientHttpResponse; import org.springframework.http.server.reactive.HttpHandler; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.mock.http.client.reactive.MockClientHttpRequest; import org.springframework.mock.http.client.reactive.MockClientHttpResponse; import org.springframework.mock.http.server.reactive.MockServerHttpRequest; import org.springframework.mock.http.server.reactive.MockServerHttpResponse; import org.springframework.util.Assert; import org.springframework.util.MultiValueMap; /** * Connector that handles requests by invoking an {@link HttpHandler} rather * than making actual requests to a network socket. * * <p>Internally the connector uses and adapts<br> * {@link MockClientHttpRequest} and {@link MockClientHttpResponse} to<br> * {@link MockServerHttpRequest} and {@link MockServerHttpResponse}. * * @author Rossen Stoyanchev * @since 5.0 */ public class HttpHandlerConnector implements ClientHttpConnector { private static Log logger = LogFactory.getLog(HttpHandlerConnector.class); private final HttpHandler handler; /** * Constructor with the {@link HttpHandler} to handle requests with. */ public HttpHandlerConnector(HttpHandler handler) { Assert.notNull(handler, "HttpHandler is required"); this.handler = handler; } @Override public Mono<ClientHttpResponse> connect(HttpMethod httpMethod, URI uri, Function<? super ClientHttpRequest, Mono<Void>> requestCallback) { MonoProcessor<ClientHttpResponse> result = MonoProcessor.create(); MockClientHttpRequest mockClientRequest = new MockClientHttpRequest(httpMethod, uri); MockServerHttpResponse mockServerResponse = new MockServerHttpResponse(); mockClientRequest.setWriteHandler(requestBody -> { log("Invoking HttpHandler for ", httpMethod, uri); ServerHttpRequest mockServerRequest = adaptRequest(mockClientRequest, requestBody); this.handler.handle(mockServerRequest, mockServerResponse).subscribe(aVoid -> {}, result::onError); return Mono.empty(); }); mockServerResponse.setWriteHandler(responseBody -> { log("Creating client response for ", httpMethod, uri); result.onNext(adaptResponse(mockServerResponse, responseBody)); return Mono.empty(); }); log("Writing client request for ", httpMethod, uri); requestCallback.apply(mockClientRequest).subscribe(aVoid -> {}, result::onError); return result; } private void log(String message, HttpMethod httpMethod, URI uri) { if (logger.isDebugEnabled()) { logger.debug(String.format("%s %s \"%s\"", message, httpMethod, uri)); } } private ServerHttpRequest adaptRequest(MockClientHttpRequest request, Publisher<DataBuffer> body) { HttpMethod method = request.getMethod(); URI uri = request.getURI(); HttpHeaders headers = request.getHeaders(); MultiValueMap<String, HttpCookie> cookies = request.getCookies(); return MockServerHttpRequest.method(method, uri).headers(headers).cookies(cookies).body(body); } private ClientHttpResponse adaptResponse(MockServerHttpResponse response, Flux<DataBuffer> body) { HttpStatus status = Optional.ofNullable(response.getStatusCode()).orElse(HttpStatus.OK); MockClientHttpResponse clientResponse = new MockClientHttpResponse(status); clientResponse.getHeaders().putAll(response.getHeaders()); clientResponse.getCookies().putAll(response.getCookies()); clientResponse.setBody(body); return clientResponse; } }