Browse Source
This commit refactors the `ClientHttpRequestFactory` into an `ClientHttpConnector` abstraction, in order to reflect that `ClientHttpRequest`s only "exist" once the client is connected to the origin server. This is why the HTTP client is now callback-based, containing all interactions with the request within a `Function<ClientHttpRequest,Mono<Void>>` that signals when it's done writing to the request. The `ClientHttpRequest` contract also adopts `setComplete()` and promotes that method to the `ReactiveHttpOutputMessage` contract. This commit also adapts all other APIs to that change and fixes a few issues, including: * use `HttpMessageConverter`s instead of `Encoders`/`Decoders` * better handle type information about request content publishers * support client cookies in HTTP requests * temporarily remove the RxNetty client supportpull/1111/head
30 changed files with 941 additions and 1220 deletions
@ -0,0 +1,52 @@
@@ -0,0 +1,52 @@
|
||||
/* |
||||
* 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.client.reactive; |
||||
|
||||
import java.net.URI; |
||||
import java.util.function.Function; |
||||
|
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import org.springframework.http.HttpMethod; |
||||
|
||||
/** |
||||
* Client abstraction for HTTP client runtimes. |
||||
* {@link ClientHttpConnector} drives the underlying HTTP client implementation |
||||
* so as to connect to the origin server and provide all the necessary infrastructure |
||||
* to send the actual {@link ClientHttpRequest} and receive the {@link ClientHttpResponse} |
||||
* |
||||
* @author Brian Clozel |
||||
*/ |
||||
public interface ClientHttpConnector { |
||||
|
||||
/** |
||||
* Connect to the origin server using the given {@code HttpMethod} and {@code URI}, |
||||
* then apply the given {@code requestCallback} on the {@link ClientHttpRequest} |
||||
* once the connection has been established. |
||||
* <p>Return a publisher of the {@link ClientHttpResponse}. |
||||
* |
||||
* @param method the HTTP request method |
||||
* @param uri the HTTP request URI |
||||
* @param requestCallback a function that prepares and writes the request, |
||||
* returning a publisher that signals when it's done interacting with the request. |
||||
* Implementations should return a {@code Mono<Void>} by calling |
||||
* {@link ClientHttpRequest#writeWith} or {@link ClientHttpRequest#setComplete}. |
||||
* @return a publisher of the {@link ClientHttpResponse} |
||||
*/ |
||||
Mono<ClientHttpResponse> connect(HttpMethod method, URI uri, |
||||
Function<? super ClientHttpRequest, Mono<Void>> requestCallback); |
||||
|
||||
} |
||||
@ -1,53 +0,0 @@
@@ -1,53 +0,0 @@
|
||||
/* |
||||
* 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.client.reactive; |
||||
|
||||
import java.net.URI; |
||||
|
||||
import reactor.io.netty.http.HttpClient; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Create a {@link ClientHttpRequest} for the Reactor Net HTTP client |
||||
* |
||||
* @author Brian Clozel |
||||
*/ |
||||
public class ReactorHttpClientRequestFactory implements ClientHttpRequestFactory { |
||||
|
||||
private final HttpClient httpClient; |
||||
|
||||
public ReactorHttpClientRequestFactory() { |
||||
this(reactor.io.netty.http.HttpClient.create()); |
||||
} |
||||
|
||||
protected ReactorHttpClientRequestFactory(HttpClient httpClient) { |
||||
this.httpClient = httpClient; |
||||
} |
||||
|
||||
@Override |
||||
public ClientHttpRequest createRequest(HttpMethod httpMethod, URI uri, HttpHeaders headers) { |
||||
Assert.notNull(httpMethod, "HTTP method is required"); |
||||
Assert.notNull(uri, "request URI is required"); |
||||
Assert.notNull(headers, "request headers are required"); |
||||
|
||||
return new ReactorClientHttpRequest(httpMethod, uri, this.httpClient, headers); |
||||
} |
||||
|
||||
} |
||||
@ -1,137 +0,0 @@
@@ -1,137 +0,0 @@
|
||||
/* |
||||
* 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.client.reactive; |
||||
|
||||
import java.net.URI; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import io.netty.buffer.ByteBuf; |
||||
import io.netty.handler.codec.http.cookie.DefaultCookie; |
||||
import io.reactivex.netty.protocol.http.client.HttpClient; |
||||
import io.reactivex.netty.protocol.http.client.HttpClientRequest; |
||||
import org.reactivestreams.Publisher; |
||||
import reactor.core.converter.RxJava1ObservableConverter; |
||||
import reactor.core.publisher.Flux; |
||||
import reactor.core.publisher.Mono; |
||||
import rx.Observable; |
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer; |
||||
import org.springframework.core.io.buffer.DataBufferFactory; |
||||
import org.springframework.core.io.buffer.NettyDataBufferFactory; |
||||
import org.springframework.http.HttpCookie; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpMethod; |
||||
|
||||
/** |
||||
* {@link ClientHttpRequest} implementation for the RxNetty HTTP client |
||||
* |
||||
* @author Brian Clozel |
||||
*/ |
||||
public class RxNettyClientHttpRequest extends AbstractClientHttpRequest { |
||||
|
||||
private final NettyDataBufferFactory dataBufferFactory; |
||||
|
||||
private final HttpMethod httpMethod; |
||||
|
||||
private final URI uri; |
||||
|
||||
private Observable<ByteBuf> body; |
||||
|
||||
public RxNettyClientHttpRequest(HttpMethod httpMethod, URI uri, HttpHeaders headers, |
||||
NettyDataBufferFactory dataBufferFactory) { |
||||
super(headers); |
||||
this.httpMethod = httpMethod; |
||||
this.uri = uri; |
||||
this.dataBufferFactory = dataBufferFactory; |
||||
} |
||||
|
||||
@Override |
||||
public DataBufferFactory bufferFactory() { |
||||
return this.dataBufferFactory; |
||||
} |
||||
|
||||
/** |
||||
* Set the body of the message to the given {@link Publisher}. |
||||
* |
||||
* <p>Since the HTTP channel is not yet created when this method |
||||
* is called, the {@code Mono<Void>} return value completes immediately. |
||||
* For an event that signals that we're done writing the request, check the |
||||
* {@link #execute()} method. |
||||
* |
||||
* @return a publisher that completes immediately. |
||||
* @see #execute() |
||||
*/ |
||||
@Override |
||||
public Mono<Void> writeWith(Publisher<DataBuffer> body) { |
||||
|
||||
this.body = RxJava1ObservableConverter.fromPublisher(Flux.from(body) |
||||
.map(b -> dataBufferFactory.wrap(b.asByteBuffer()).getNativeBuffer())); |
||||
|
||||
return Mono.empty(); |
||||
} |
||||
|
||||
@Override |
||||
public HttpMethod getMethod() { |
||||
return this.httpMethod; |
||||
} |
||||
|
||||
@Override |
||||
public URI getURI() { |
||||
return this.uri; |
||||
} |
||||
|
||||
@Override |
||||
public Mono<ClientHttpResponse> execute() { |
||||
try { |
||||
HttpClientRequest<ByteBuf, ByteBuf> request = HttpClient |
||||
.newClient(this.uri.getHost(), this.uri.getPort()) |
||||
.createRequest(io.netty.handler.codec.http.HttpMethod.valueOf(this.httpMethod.name()), uri.getRawPath()); |
||||
|
||||
return applyBeforeCommit() |
||||
.then(() -> Mono.just(request)) |
||||
.map(req -> { |
||||
for (Map.Entry<String, List<String>> entry : getHeaders().entrySet()) { |
||||
for (String value : entry.getValue()) { |
||||
req = req.addHeader(entry.getKey(), value); |
||||
} |
||||
} |
||||
for (Map.Entry<String, List<HttpCookie>> entry : getCookies().entrySet()) { |
||||
for (HttpCookie cookie : entry.getValue()) { |
||||
req.addCookie(new DefaultCookie(cookie.getName(), cookie.getValue())); |
||||
} |
||||
} |
||||
return req; |
||||
}) |
||||
.map(req -> { |
||||
if (this.body != null) { |
||||
return RxJava1ObservableConverter.toPublisher(req.writeContent(this.body)); |
||||
} |
||||
else { |
||||
return RxJava1ObservableConverter.toPublisher(req); |
||||
} |
||||
}) |
||||
.flatMap(resp -> resp) |
||||
.next().map(response -> new RxNettyClientHttpResponse(response, |
||||
this.dataBufferFactory)); |
||||
} |
||||
catch (IllegalArgumentException exc) { |
||||
return Mono.error(exc); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -1,101 +0,0 @@
@@ -1,101 +0,0 @@
|
||||
/* |
||||
* 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.client.reactive; |
||||
|
||||
import java.util.Collection; |
||||
|
||||
import io.netty.buffer.ByteBuf; |
||||
import io.reactivex.netty.protocol.http.client.HttpClientResponse; |
||||
import reactor.core.converter.RxJava1ObservableConverter; |
||||
import reactor.core.publisher.Flux; |
||||
|
||||
import org.springframework.core.io.buffer.DataBuffer; |
||||
import org.springframework.core.io.buffer.NettyDataBufferFactory; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.http.ResponseCookie; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.CollectionUtils; |
||||
import org.springframework.util.LinkedMultiValueMap; |
||||
import org.springframework.util.MultiValueMap; |
||||
|
||||
/** |
||||
* {@link ClientHttpResponse} implementation for the RxNetty HTTP client |
||||
* |
||||
* @author Brian Clozel |
||||
*/ |
||||
public class RxNettyClientHttpResponse implements ClientHttpResponse { |
||||
|
||||
private final HttpClientResponse<ByteBuf> response; |
||||
|
||||
private final HttpHeaders headers; |
||||
|
||||
private final MultiValueMap<String, ResponseCookie> cookies; |
||||
|
||||
private final NettyDataBufferFactory dataBufferFactory; |
||||
|
||||
|
||||
public RxNettyClientHttpResponse(HttpClientResponse<ByteBuf> response, |
||||
NettyDataBufferFactory dataBufferFactory) { |
||||
Assert.notNull("'request', request must not be null"); |
||||
Assert.notNull(dataBufferFactory, "'dataBufferFactory' must not be null"); |
||||
this.dataBufferFactory = dataBufferFactory; |
||||
this.response = response; |
||||
this.headers = new HttpHeaders(); |
||||
this.response.headerIterator().forEachRemaining(e -> this.headers.set(e.getKey().toString(), e.getValue().toString())); |
||||
this.cookies = initCookies(response); |
||||
} |
||||
|
||||
private static MultiValueMap<String, ResponseCookie> initCookies(HttpClientResponse<ByteBuf> response) { |
||||
MultiValueMap<String, ResponseCookie> result = new LinkedMultiValueMap<>(); |
||||
response.getCookies().values().stream().flatMap(Collection::stream) |
||||
.forEach(cookie -> { |
||||
ResponseCookie responseCookie = ResponseCookie.from(cookie.name(), cookie.value()) |
||||
.domain(cookie.domain()) |
||||
.path(cookie.path()) |
||||
.maxAge(cookie.maxAge()) |
||||
.secure(cookie.isSecure()) |
||||
.httpOnly(cookie.isHttpOnly()) |
||||
.build(); |
||||
result.add(cookie.name(), responseCookie); |
||||
}); |
||||
return CollectionUtils.unmodifiableMultiValueMap(result); |
||||
} |
||||
|
||||
|
||||
@Override |
||||
public HttpStatus getStatusCode() { |
||||
return HttpStatus.valueOf(this.response.getStatus().code()); |
||||
} |
||||
|
||||
@Override |
||||
public Flux<DataBuffer> getBody() { |
||||
return RxJava1ObservableConverter |
||||
.toPublisher(this.response.getContent().map(dataBufferFactory::wrap)); |
||||
} |
||||
|
||||
@Override |
||||
public HttpHeaders getHeaders() { |
||||
return this.headers; |
||||
} |
||||
|
||||
@Override |
||||
public MultiValueMap<String, ResponseCookie> getCookies() { |
||||
return this.cookies; |
||||
} |
||||
|
||||
} |
||||
@ -1,48 +0,0 @@
@@ -1,48 +0,0 @@
|
||||
/* |
||||
* 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.client.reactive; |
||||
|
||||
import java.net.URI; |
||||
|
||||
import org.springframework.core.io.buffer.NettyDataBufferFactory; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Create a {@link ClientHttpRequestFactory} for the RxNetty HTTP client |
||||
* |
||||
* @author Brian Clozel |
||||
*/ |
||||
public class RxNettyHttpClientRequestFactory implements ClientHttpRequestFactory { |
||||
|
||||
private final NettyDataBufferFactory dataBufferFactory; |
||||
|
||||
public RxNettyHttpClientRequestFactory(NettyDataBufferFactory dataBufferFactory) { |
||||
this.dataBufferFactory = dataBufferFactory; |
||||
} |
||||
|
||||
@Override |
||||
public ClientHttpRequest createRequest(HttpMethod httpMethod, URI uri, HttpHeaders headers) { |
||||
Assert.notNull(httpMethod, "HTTP method is required"); |
||||
Assert.notNull(uri, "request URI is required"); |
||||
Assert.notNull(headers, "request headers are required"); |
||||
|
||||
return new RxNettyClientHttpRequest(httpMethod, uri, headers, |
||||
this.dataBufferFactory); |
||||
} |
||||
} |
||||
@ -0,0 +1,96 @@
@@ -0,0 +1,96 @@
|
||||
/* |
||||
* 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.web.client.reactive; |
||||
|
||||
import java.net.URI; |
||||
|
||||
import org.reactivestreams.Publisher; |
||||
|
||||
import org.springframework.core.ResolvableType; |
||||
import org.springframework.http.HttpCookie; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.util.MultiValueMap; |
||||
|
||||
/** |
||||
* Holds all the application information required to build an actual HTTP client request. |
||||
* <p>The request body is materialized by a {@code Publisher} of Objects and their type |
||||
* by a {@code ResolvableType} instance; it should be later converted to a |
||||
* {@code Publisher<DataBuffer>} to be written to the actual HTTP client request. |
||||
* |
||||
* @author Brian Clozel |
||||
*/ |
||||
public class ClientWebRequest { |
||||
|
||||
protected final HttpMethod httpMethod; |
||||
|
||||
protected final URI url; |
||||
|
||||
protected HttpHeaders httpHeaders; |
||||
|
||||
private MultiValueMap<String, HttpCookie> cookies; |
||||
|
||||
protected Publisher body; |
||||
|
||||
protected ResolvableType elementType; |
||||
|
||||
|
||||
public ClientWebRequest(HttpMethod httpMethod, URI url) { |
||||
this.httpMethod = httpMethod; |
||||
this.url = url; |
||||
} |
||||
|
||||
public HttpMethod getMethod() { |
||||
return httpMethod; |
||||
} |
||||
|
||||
public URI getUrl() { |
||||
return url; |
||||
} |
||||
|
||||
public HttpHeaders getHttpHeaders() { |
||||
return httpHeaders; |
||||
} |
||||
|
||||
public void setHttpHeaders(HttpHeaders httpHeaders) { |
||||
this.httpHeaders = httpHeaders; |
||||
} |
||||
|
||||
public MultiValueMap<String, HttpCookie> getCookies() { |
||||
return cookies; |
||||
} |
||||
|
||||
public void setCookies(MultiValueMap<String, HttpCookie> cookies) { |
||||
this.cookies = cookies; |
||||
} |
||||
|
||||
public Publisher getBody() { |
||||
return body; |
||||
} |
||||
|
||||
public void setBody(Publisher body) { |
||||
this.body = body; |
||||
} |
||||
|
||||
public ResolvableType getElementType() { |
||||
return elementType; |
||||
} |
||||
|
||||
public void setElementType(ResolvableType elementType) { |
||||
this.elementType = elementType; |
||||
} |
||||
} |
||||
@ -0,0 +1,111 @@
@@ -0,0 +1,111 @@
|
||||
/* |
||||
* 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.web.client.reactive; |
||||
|
||||
|
||||
import org.springframework.http.HttpMethod; |
||||
|
||||
/** |
||||
* Static factory methods for {@link DefaultClientWebRequestBuilder ClientWebRequestBuilders} |
||||
* |
||||
* @author Brian Clozel |
||||
*/ |
||||
public abstract class ClientWebRequestBuilders { |
||||
|
||||
/** |
||||
* Create a {@link DefaultClientWebRequestBuilder} for a GET request. |
||||
* |
||||
* @param urlTemplate a URL template; the resulting URL will be encoded |
||||
* @param urlVariables zero or more URL variables |
||||
*/ |
||||
public static DefaultClientWebRequestBuilder get(String urlTemplate, Object... urlVariables) { |
||||
return new DefaultClientWebRequestBuilder(HttpMethod.GET, urlTemplate, urlVariables); |
||||
} |
||||
|
||||
/** |
||||
* Create a {@link DefaultClientWebRequestBuilder} for a POST request. |
||||
* |
||||
* @param urlTemplate a URL template; the resulting URL will be encoded |
||||
* @param urlVariables zero or more URL variables |
||||
*/ |
||||
public static DefaultClientWebRequestBuilder post(String urlTemplate, Object... urlVariables) { |
||||
return new DefaultClientWebRequestBuilder(HttpMethod.POST, urlTemplate, urlVariables); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Create a {@link DefaultClientWebRequestBuilder} for a PUT request. |
||||
* |
||||
* @param urlTemplate a URL template; the resulting URL will be encoded |
||||
* @param urlVariables zero or more URL variables |
||||
*/ |
||||
public static DefaultClientWebRequestBuilder put(String urlTemplate, Object... urlVariables) { |
||||
return new DefaultClientWebRequestBuilder(HttpMethod.PUT, urlTemplate, urlVariables); |
||||
} |
||||
|
||||
/** |
||||
* Create a {@link DefaultClientWebRequestBuilder} for a PATCH request. |
||||
* |
||||
* @param urlTemplate a URL template; the resulting URL will be encoded |
||||
* @param urlVariables zero or more URL variables |
||||
*/ |
||||
public static DefaultClientWebRequestBuilder patch(String urlTemplate, Object... urlVariables) { |
||||
return new DefaultClientWebRequestBuilder(HttpMethod.PATCH, urlTemplate, urlVariables); |
||||
} |
||||
|
||||
/** |
||||
* Create a {@link DefaultClientWebRequestBuilder} for a DELETE request. |
||||
* |
||||
* @param urlTemplate a URL template; the resulting URL will be encoded |
||||
* @param urlVariables zero or more URL variables |
||||
*/ |
||||
public static DefaultClientWebRequestBuilder delete(String urlTemplate, Object... urlVariables) { |
||||
return new DefaultClientWebRequestBuilder(HttpMethod.DELETE, urlTemplate, urlVariables); |
||||
} |
||||
|
||||
/** |
||||
* Create a {@link DefaultClientWebRequestBuilder} for an OPTIONS request. |
||||
* |
||||
* @param urlTemplate a URL template; the resulting URL will be encoded |
||||
* @param urlVariables zero or more URL variables |
||||
*/ |
||||
public static DefaultClientWebRequestBuilder options(String urlTemplate, Object... urlVariables) { |
||||
return new DefaultClientWebRequestBuilder(HttpMethod.OPTIONS, urlTemplate, urlVariables); |
||||
} |
||||
|
||||
/** |
||||
* Create a {@link DefaultClientWebRequestBuilder} for a HEAD request. |
||||
* |
||||
* @param urlTemplate a URL template; the resulting URL will be encoded |
||||
* @param urlVariables zero or more URL variables |
||||
*/ |
||||
public static DefaultClientWebRequestBuilder head(String urlTemplate, Object... urlVariables) { |
||||
return new DefaultClientWebRequestBuilder(HttpMethod.HEAD, urlTemplate, urlVariables); |
||||
} |
||||
|
||||
/** |
||||
* Create a {@link DefaultClientWebRequestBuilder} for a request with the given HTTP method. |
||||
* |
||||
* @param httpMethod the HTTP method |
||||
* @param urlTemplate a URL template; the resulting URL will be encoded |
||||
* @param urlVariables zero or more URL variables |
||||
*/ |
||||
public static DefaultClientWebRequestBuilder request(HttpMethod httpMethod, String urlTemplate, Object... urlVariables) { |
||||
return new DefaultClientWebRequestBuilder(httpMethod, urlTemplate, urlVariables); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,196 @@
@@ -0,0 +1,196 @@
|
||||
/* |
||||
* 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.web.client.reactive; |
||||
|
||||
import java.net.URI; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
import java.util.stream.Collectors; |
||||
|
||||
import org.reactivestreams.Publisher; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import org.springframework.core.ResolvableType; |
||||
import org.springframework.http.HttpCookie; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.http.client.reactive.ClientHttpRequest; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.util.LinkedMultiValueMap; |
||||
import org.springframework.util.MultiValueMap; |
||||
import org.springframework.web.util.DefaultUriTemplateHandler; |
||||
import org.springframework.web.util.UriTemplateHandler; |
||||
|
||||
/** |
||||
* Builds a {@link ClientHttpRequest} using a {@link Publisher} |
||||
* as request body. |
||||
* |
||||
* <p>See static factory methods in {@link ClientWebRequestBuilders} |
||||
* |
||||
* @author Brian Clozel |
||||
* @see ClientWebRequestBuilders |
||||
*/ |
||||
public class DefaultClientWebRequestBuilder implements ClientWebRequestBuilder { |
||||
|
||||
|
||||
private final UriTemplateHandler uriTemplateHandler = new DefaultUriTemplateHandler(); |
||||
|
||||
private HttpMethod httpMethod; |
||||
|
||||
private HttpHeaders httpHeaders; |
||||
|
||||
private URI url; |
||||
|
||||
private final MultiValueMap<String, HttpCookie> cookies = new LinkedMultiValueMap<>(); |
||||
|
||||
private Publisher body; |
||||
|
||||
private ResolvableType elementType; |
||||
|
||||
private List<ClientWebRequestPostProcessor> postProcessors = new ArrayList<>(); |
||||
|
||||
protected DefaultClientWebRequestBuilder() { |
||||
} |
||||
|
||||
public DefaultClientWebRequestBuilder(HttpMethod httpMethod, String urlTemplate, |
||||
Object... urlVariables) { |
||||
this.httpMethod = httpMethod; |
||||
this.httpHeaders = new HttpHeaders(); |
||||
this.url = this.uriTemplateHandler.expand(urlTemplate, urlVariables); |
||||
} |
||||
|
||||
public DefaultClientWebRequestBuilder(HttpMethod httpMethod, URI url) { |
||||
this.httpMethod = httpMethod; |
||||
this.httpHeaders = new HttpHeaders(); |
||||
this.url = url; |
||||
} |
||||
|
||||
/** |
||||
* Add an HTTP request header |
||||
*/ |
||||
public DefaultClientWebRequestBuilder header(String name, String... values) { |
||||
Arrays.stream(values).forEach(value -> this.httpHeaders.add(name, value)); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Add all provided HTTP request headers |
||||
*/ |
||||
public DefaultClientWebRequestBuilder headers(HttpHeaders httpHeaders) { |
||||
this.httpHeaders = httpHeaders; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Set the Content-Type request header to the given {@link MediaType} |
||||
*/ |
||||
public DefaultClientWebRequestBuilder contentType(MediaType contentType) { |
||||
this.httpHeaders.setContentType(contentType); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Set the Content-Type request header to the given media type |
||||
*/ |
||||
public DefaultClientWebRequestBuilder contentType(String contentType) { |
||||
this.httpHeaders.setContentType(MediaType.parseMediaType(contentType)); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Set the Accept request header to the given {@link MediaType}s |
||||
*/ |
||||
public DefaultClientWebRequestBuilder accept(MediaType... mediaTypes) { |
||||
this.httpHeaders.setAccept(Arrays.asList(mediaTypes)); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Set the Accept request header to the given media types |
||||
*/ |
||||
public DefaultClientWebRequestBuilder accept(String... mediaTypes) { |
||||
this.httpHeaders.setAccept( |
||||
Arrays.stream(mediaTypes).map(type -> MediaType.parseMediaType(type)) |
||||
.collect(Collectors.toList())); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Add a Cookie to the HTTP request |
||||
*/ |
||||
public DefaultClientWebRequestBuilder cookie(String name, String value) { |
||||
return cookie(new HttpCookie(name, value)); |
||||
} |
||||
|
||||
/** |
||||
* Add a Cookie to the HTTP request |
||||
*/ |
||||
public DefaultClientWebRequestBuilder cookie(HttpCookie cookie) { |
||||
this.cookies.add(cookie.getName(), cookie); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Allows performing more complex operations with a strategy. For example, a |
||||
* {@link ClientWebRequestPostProcessor} implementation might accept the arguments of username |
||||
* and password and set an HTTP Basic authentication header. |
||||
* |
||||
* @param postProcessor the {@link ClientWebRequestPostProcessor} to use. Cannot be null. |
||||
* |
||||
* @return this instance for further modifications. |
||||
*/ |
||||
public DefaultClientWebRequestBuilder apply(ClientWebRequestPostProcessor postProcessor) { |
||||
Assert.notNull(postProcessor, "`postProcessor` is required"); |
||||
this.postProcessors.add(postProcessor); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Use the given object as the request body |
||||
*/ |
||||
public DefaultClientWebRequestBuilder body(Object content) { |
||||
this.body = Mono.just(content); |
||||
this.elementType = ResolvableType.forInstance(content); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Use the given {@link Publisher} as the request body and use its {@link ResolvableType} |
||||
* as type information for the element published by this reactive stream |
||||
*/ |
||||
public DefaultClientWebRequestBuilder body(Publisher<?> content, ResolvableType publisherType) { |
||||
this.body = content; |
||||
this.elementType = publisherType; |
||||
return this; |
||||
} |
||||
|
||||
@Override |
||||
public ClientWebRequest build() { |
||||
ClientWebRequest clientWebRequest = new ClientWebRequest(this.httpMethod, this.url); |
||||
clientWebRequest.setHttpHeaders(this.httpHeaders); |
||||
clientWebRequest.setCookies(this.cookies); |
||||
clientWebRequest.setBody(this.body); |
||||
clientWebRequest.setElementType(this.elementType); |
||||
for (ClientWebRequestPostProcessor postProcessor : this.postProcessors) { |
||||
clientWebRequest = postProcessor.postProcess(clientWebRequest); |
||||
} |
||||
return clientWebRequest; |
||||
} |
||||
|
||||
} |
||||
@ -1,167 +0,0 @@
@@ -1,167 +0,0 @@
|
||||
/* |
||||
* 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.web.client.reactive; |
||||
|
||||
import java.net.URI; |
||||
import java.util.ArrayList; |
||||
import java.util.Arrays; |
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
import java.util.stream.Collectors; |
||||
|
||||
import org.reactivestreams.Publisher; |
||||
import reactor.core.publisher.Flux; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import org.springframework.core.ResolvableType; |
||||
import org.springframework.core.codec.Encoder; |
||||
import org.springframework.http.HttpCookie; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpMethod; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.http.client.reactive.ClientHttpRequest; |
||||
import org.springframework.http.client.reactive.ClientHttpRequestFactory; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.web.client.RestClientException; |
||||
import org.springframework.web.util.DefaultUriTemplateHandler; |
||||
import org.springframework.web.util.UriTemplateHandler; |
||||
|
||||
/** |
||||
* Builds a {@link ClientHttpRequest} |
||||
* |
||||
* <p>See static factory methods in {@link HttpRequestBuilders} |
||||
* |
||||
* @author Brian Clozel |
||||
* @see HttpRequestBuilders |
||||
*/ |
||||
public class DefaultHttpRequestBuilder implements HttpRequestBuilder { |
||||
|
||||
private final UriTemplateHandler uriTemplateHandler = new DefaultUriTemplateHandler(); |
||||
|
||||
protected HttpMethod httpMethod; |
||||
|
||||
protected HttpHeaders httpHeaders; |
||||
|
||||
protected URI url; |
||||
|
||||
protected Publisher contentPublisher; |
||||
|
||||
protected ResolvableType contentType; |
||||
|
||||
protected final List<HttpCookie> cookies = new ArrayList<HttpCookie>(); |
||||
|
||||
protected DefaultHttpRequestBuilder() { |
||||
} |
||||
|
||||
public DefaultHttpRequestBuilder(HttpMethod httpMethod, String urlTemplate, Object... urlVariables) throws RestClientException { |
||||
this.httpMethod = httpMethod; |
||||
this.httpHeaders = new HttpHeaders(); |
||||
this.url = this.uriTemplateHandler.expand(urlTemplate, urlVariables); |
||||
} |
||||
|
||||
public DefaultHttpRequestBuilder(HttpMethod httpMethod, URI url) { |
||||
this.httpMethod = httpMethod; |
||||
this.httpHeaders = new HttpHeaders(); |
||||
this.url = url; |
||||
} |
||||
|
||||
public DefaultHttpRequestBuilder header(String name, String... values) { |
||||
Arrays.stream(values).forEach(value -> this.httpHeaders.add(name, value)); |
||||
return this; |
||||
} |
||||
|
||||
public DefaultHttpRequestBuilder headers(HttpHeaders httpHeaders) { |
||||
this.httpHeaders = httpHeaders; |
||||
return this; |
||||
} |
||||
|
||||
public DefaultHttpRequestBuilder contentType(MediaType contentType) { |
||||
this.httpHeaders.setContentType(contentType); |
||||
return this; |
||||
} |
||||
|
||||
public DefaultHttpRequestBuilder contentType(String contentType) { |
||||
this.httpHeaders.setContentType(MediaType.parseMediaType(contentType)); |
||||
return this; |
||||
} |
||||
|
||||
public DefaultHttpRequestBuilder accept(MediaType... mediaTypes) { |
||||
this.httpHeaders.setAccept(Arrays.asList(mediaTypes)); |
||||
return this; |
||||
} |
||||
|
||||
public DefaultHttpRequestBuilder accept(String... mediaTypes) { |
||||
this.httpHeaders.setAccept(Arrays.stream(mediaTypes) |
||||
.map(type -> MediaType.parseMediaType(type)) |
||||
.collect(Collectors.toList())); |
||||
return this; |
||||
} |
||||
|
||||
public DefaultHttpRequestBuilder content(Object content) { |
||||
this.contentPublisher = Mono.just(content); |
||||
this.contentType = ResolvableType.forInstance(content); |
||||
return this; |
||||
} |
||||
|
||||
public DefaultHttpRequestBuilder contentStream(Publisher<?> content, ResolvableType type) { |
||||
this.contentPublisher = Flux.from(content); |
||||
this.contentType = type; |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Allows performing more complex operations with a strategy. For example, a |
||||
* {@link RequestPostProcessor} implementation might accept the arguments of |
||||
* username and password and set an HTTP Basic authentication header. |
||||
* |
||||
* @param postProcessor the {@link RequestPostProcessor} to use. Cannot be null. |
||||
* |
||||
* @return this instance for further modifications. |
||||
*/ |
||||
public DefaultHttpRequestBuilder apply(RequestPostProcessor postProcessor) { |
||||
Assert.notNull(postProcessor, "`postProcessor` is required"); |
||||
postProcessor.postProcess(this); |
||||
return this; |
||||
} |
||||
|
||||
public ClientHttpRequest build(ClientHttpRequestFactory factory, List<Encoder<?>> messageEncoders) { |
||||
ClientHttpRequest request = factory.createRequest(this.httpMethod, this.url, this.httpHeaders); |
||||
request.getHeaders().putAll(this.httpHeaders); |
||||
|
||||
if (this.contentPublisher != null) { |
||||
MediaType mediaType = request.getHeaders().getContentType(); |
||||
|
||||
Optional<Encoder<?>> messageEncoder = messageEncoders |
||||
.stream() |
||||
.filter(e -> e.canEncode(this.contentType, mediaType)) |
||||
.findFirst(); |
||||
|
||||
if (messageEncoder.isPresent()) { |
||||
request.writeWith(messageEncoder.get() |
||||
.encode(this.contentPublisher, request.bufferFactory(), |
||||
this.contentType, mediaType)); |
||||
} |
||||
else { |
||||
throw new WebClientException("Can't write request body " + |
||||
"of type '" + this.contentType.toString() + |
||||
"' for content-type '" + mediaType.toString() + "'"); |
||||
} |
||||
} |
||||
return request; |
||||
} |
||||
|
||||
} |
||||
@ -1,52 +0,0 @@
@@ -1,52 +0,0 @@
|
||||
/* |
||||
* 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.web.client.reactive; |
||||
|
||||
import java.util.List; |
||||
|
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import org.springframework.core.codec.Decoder; |
||||
import org.springframework.http.client.reactive.ClientHttpResponse; |
||||
|
||||
/** |
||||
* Default implementation of the {@link WebResponse} interface
|
||||
* |
||||
* @author Brian Clozel |
||||
*/ |
||||
public class DefaultWebResponse implements WebResponse { |
||||
|
||||
private final Mono<ClientHttpResponse> clientResponse; |
||||
|
||||
private final List<Decoder<?>> messageDecoders; |
||||
|
||||
|
||||
public DefaultWebResponse(Mono<ClientHttpResponse> clientResponse, List<Decoder<?>> messageDecoders) { |
||||
this.clientResponse = clientResponse; |
||||
this.messageDecoders = messageDecoders; |
||||
} |
||||
|
||||
@Override |
||||
public Mono<ClientHttpResponse> getClientResponse() { |
||||
return this.clientResponse; |
||||
} |
||||
|
||||
@Override |
||||
public List<Decoder<?>> getMessageDecoders() { |
||||
return this.messageDecoders; |
||||
} |
||||
} |
||||
@ -1,40 +0,0 @@
@@ -1,40 +0,0 @@
|
||||
/* |
||||
* 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.web.client.reactive; |
||||
|
||||
import java.util.List; |
||||
|
||||
import org.springframework.core.codec.Encoder; |
||||
import org.springframework.http.client.reactive.ClientHttpRequest; |
||||
import org.springframework.http.client.reactive.ClientHttpRequestFactory; |
||||
|
||||
/** |
||||
* Build {@link ClientHttpRequest} using a {@link ClientHttpRequestFactory} |
||||
* which wraps an HTTP client implementation. |
||||
* |
||||
* @author Brian Clozel |
||||
*/ |
||||
public interface HttpRequestBuilder { |
||||
|
||||
/** |
||||
* Build a {@link ClientHttpRequest} |
||||
* |
||||
* @param factory the factory that creates the actual {@link ClientHttpRequest} |
||||
* @param messageEncoders the {@link Encoder}s to use for encoding the request body |
||||
*/ |
||||
ClientHttpRequest build(ClientHttpRequestFactory factory, List<Encoder<?>> messageEncoders); |
||||
} |
||||
@ -1,110 +0,0 @@
@@ -1,110 +0,0 @@
|
||||
/* |
||||
* 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.web.client.reactive; |
||||
|
||||
import org.springframework.http.HttpMethod; |
||||
|
||||
/** |
||||
* Static factory methods for {@link DefaultHttpRequestBuilder RequestBuilders}. |
||||
* |
||||
* @author Brian Clozel |
||||
*/ |
||||
public abstract class HttpRequestBuilders { |
||||
|
||||
/** |
||||
* Create a {@link DefaultHttpRequestBuilder} for a GET request. |
||||
* |
||||
* @param urlTemplate a URL template; the resulting URL will be encoded |
||||
* @param urlVariables zero or more URL variables |
||||
*/ |
||||
public static DefaultHttpRequestBuilder get(String urlTemplate, Object... urlVariables) { |
||||
return new DefaultHttpRequestBuilder(HttpMethod.GET, urlTemplate, urlVariables); |
||||
} |
||||
|
||||
/** |
||||
* Create a {@link DefaultHttpRequestBuilder} for a POST request. |
||||
* |
||||
* @param urlTemplate a URL template; the resulting URL will be encoded |
||||
* @param urlVariables zero or more URL variables |
||||
*/ |
||||
public static DefaultHttpRequestBuilder post(String urlTemplate, Object... urlVariables) { |
||||
return new DefaultHttpRequestBuilder(HttpMethod.POST, urlTemplate, urlVariables); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Create a {@link DefaultHttpRequestBuilder} for a PUT request. |
||||
* |
||||
* @param urlTemplate a URL template; the resulting URL will be encoded |
||||
* @param urlVariables zero or more URL variables |
||||
*/ |
||||
public static DefaultHttpRequestBuilder put(String urlTemplate, Object... urlVariables) { |
||||
return new DefaultHttpRequestBuilder(HttpMethod.PUT, urlTemplate, urlVariables); |
||||
} |
||||
|
||||
/** |
||||
* Create a {@link DefaultHttpRequestBuilder} for a PATCH request. |
||||
* |
||||
* @param urlTemplate a URL template; the resulting URL will be encoded |
||||
* @param urlVariables zero or more URL variables |
||||
*/ |
||||
public static DefaultHttpRequestBuilder patch(String urlTemplate, Object... urlVariables) { |
||||
return new DefaultHttpRequestBuilder(HttpMethod.PATCH, urlTemplate, urlVariables); |
||||
} |
||||
|
||||
/** |
||||
* Create a {@link DefaultHttpRequestBuilder} for a DELETE request. |
||||
* |
||||
* @param urlTemplate a URL template; the resulting URL will be encoded |
||||
* @param urlVariables zero or more URL variables |
||||
*/ |
||||
public static DefaultHttpRequestBuilder delete(String urlTemplate, Object... urlVariables) { |
||||
return new DefaultHttpRequestBuilder(HttpMethod.DELETE, urlTemplate, urlVariables); |
||||
} |
||||
|
||||
/** |
||||
* Create a {@link DefaultHttpRequestBuilder} for an OPTIONS request. |
||||
* |
||||
* @param urlTemplate a URL template; the resulting URL will be encoded |
||||
* @param urlVariables zero or more URL variables |
||||
*/ |
||||
public static DefaultHttpRequestBuilder options(String urlTemplate, Object... urlVariables) { |
||||
return new DefaultHttpRequestBuilder(HttpMethod.OPTIONS, urlTemplate, urlVariables); |
||||
} |
||||
|
||||
/** |
||||
* Create a {@link DefaultHttpRequestBuilder} for a HEAD request. |
||||
* |
||||
* @param urlTemplate a URL template; the resulting URL will be encoded |
||||
* @param urlVariables zero or more URL variables |
||||
*/ |
||||
public static DefaultHttpRequestBuilder head(String urlTemplate, Object... urlVariables) { |
||||
return new DefaultHttpRequestBuilder(HttpMethod.HEAD, urlTemplate, urlVariables); |
||||
} |
||||
|
||||
/** |
||||
* Create a {@link DefaultHttpRequestBuilder} for a request with the given HTTP method. |
||||
* |
||||
* @param httpMethod the HTTP method |
||||
* @param urlTemplate a URL template; the resulting URL will be encoded |
||||
* @param urlVariables zero or more URL variables |
||||
*/ |
||||
public static DefaultHttpRequestBuilder request(HttpMethod httpMethod, String urlTemplate, Object... urlVariables) { |
||||
return new DefaultHttpRequestBuilder(httpMethod, urlTemplate, urlVariables); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,163 @@
@@ -0,0 +1,163 @@
|
||||
/* |
||||
* 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.web.client.reactive; |
||||
|
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
|
||||
import org.springframework.core.ResolvableType; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.http.client.reactive.ClientHttpResponse; |
||||
import org.springframework.http.converter.reactive.HttpMessageConverter; |
||||
|
||||
import reactor.core.publisher.Flux; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
/** |
||||
* Static factory methods for {@link ResponseExtractor} based on the {@link Flux} and |
||||
* {@link Mono} API. |
||||
* |
||||
* @author Brian Clozel |
||||
*/ |
||||
public class ResponseExtractors { |
||||
|
||||
private static final Object EMPTY_BODY = new Object(); |
||||
|
||||
/** |
||||
* Extract the response body and decode it, returning it as a {@code Mono<T>} |
||||
* @see ResolvableType#forClassWithGenerics(Class, Class[]) |
||||
*/ |
||||
public static <T> ResponseExtractor<Mono<T>> body(ResolvableType bodyType) { |
||||
// noinspection unchecked
|
||||
return (clientResponse, messageConverters) -> (Mono<T>) clientResponse |
||||
.flatMap(resp -> decodeResponseBody(resp, bodyType, |
||||
messageConverters)) |
||||
.next(); |
||||
} |
||||
|
||||
/** |
||||
* Extract the response body and decode it, returning it as a {@code Mono<T>} |
||||
*/ |
||||
public static <T> ResponseExtractor<Mono<T>> body(Class<T> sourceClass) { |
||||
ResolvableType bodyType = ResolvableType.forClass(sourceClass); |
||||
return body(bodyType); |
||||
} |
||||
|
||||
/** |
||||
* Extract the response body and decode it, returning it as a {@code Flux<T>} |
||||
* @see ResolvableType#forClassWithGenerics(Class, Class[]) |
||||
*/ |
||||
public static <T> ResponseExtractor<Flux<T>> bodyStream(ResolvableType bodyType) { |
||||
return (clientResponse, messageConverters) -> clientResponse |
||||
.flatMap(resp -> decodeResponseBody(resp, bodyType, messageConverters)); |
||||
} |
||||
|
||||
/** |
||||
* Extract the response body and decode it, returning it as a {@code Flux<T>} |
||||
*/ |
||||
public static <T> ResponseExtractor<Flux<T>> bodyStream(Class<T> sourceClass) { |
||||
ResolvableType bodyType = ResolvableType.forClass(sourceClass); |
||||
return bodyStream(bodyType); |
||||
} |
||||
|
||||
/** |
||||
* Extract the full response body as a {@code ResponseEntity} with its body decoded as |
||||
* a single type {@code T} |
||||
* @see ResolvableType#forClassWithGenerics(Class, Class[]) |
||||
*/ |
||||
public static <T> ResponseExtractor<Mono<ResponseEntity<T>>> response( |
||||
ResolvableType bodyType) { |
||||
return (clientResponse, messageConverters) -> clientResponse.then(response -> { |
||||
return Mono.when( |
||||
decodeResponseBody(response, bodyType, |
||||
messageConverters).next().defaultIfEmpty( |
||||
EMPTY_BODY), |
||||
Mono.just(response.getHeaders()), |
||||
Mono.just(response.getStatusCode())); |
||||
}).map(tuple -> { |
||||
Object body = (tuple.getT1() != EMPTY_BODY ? tuple.getT1() : null); |
||||
// noinspection unchecked
|
||||
return new ResponseEntity<>((T) body, tuple.getT2(), tuple.getT3()); |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Extract the full response body as a {@code ResponseEntity} with its body decoded as |
||||
* a single type {@code T} |
||||
*/ |
||||
public static <T> ResponseExtractor<Mono<ResponseEntity<T>>> response( |
||||
Class<T> bodyClass) { |
||||
ResolvableType bodyType = ResolvableType.forClass(bodyClass); |
||||
return response(bodyType); |
||||
} |
||||
|
||||
/** |
||||
* Extract the full response body as a {@code ResponseEntity} with its body decoded as |
||||
* a {@code Flux<T>} |
||||
* @see ResolvableType#forClassWithGenerics(Class, Class[]) |
||||
*/ |
||||
public static <T> ResponseExtractor<Mono<ResponseEntity<Flux<T>>>> responseStream( |
||||
ResolvableType type) { |
||||
return (clientResponse, messageConverters) -> clientResponse |
||||
.map(response -> new ResponseEntity<>( |
||||
decodeResponseBody(response, type, |
||||
messageConverters), |
||||
response.getHeaders(), response.getStatusCode())); |
||||
} |
||||
|
||||
/** |
||||
* Extract the full response body as a {@code ResponseEntity} with its body decoded as |
||||
* a {@code Flux<T>} |
||||
*/ |
||||
public static <T> ResponseExtractor<Mono<ResponseEntity<Flux<T>>>> responseStream( |
||||
Class<T> sourceClass) { |
||||
ResolvableType resolvableType = ResolvableType.forClass(sourceClass); |
||||
return responseStream(resolvableType); |
||||
} |
||||
|
||||
/** |
||||
* Extract the response headers as an {@code HttpHeaders} instance |
||||
*/ |
||||
public static ResponseExtractor<Mono<HttpHeaders>> headers() { |
||||
return (clientResponse, messageConverters) -> clientResponse.map(resp -> resp.getHeaders()); |
||||
} |
||||
|
||||
protected static <T> Flux<T> decodeResponseBody(ClientHttpResponse response, |
||||
ResolvableType responseType, |
||||
List<HttpMessageConverter<?>> messageConverters) { |
||||
|
||||
MediaType contentType = response.getHeaders().getContentType(); |
||||
Optional<HttpMessageConverter<?>> converter = resolveConverter(messageConverters, |
||||
responseType, contentType); |
||||
if (!converter.isPresent()) { |
||||
return Flux.error(new IllegalStateException( |
||||
"Could not decode response body of type '" + contentType |
||||
+ "' with target type '" + responseType.toString() + "'")); |
||||
} |
||||
// noinspection unchecked
|
||||
return (Flux<T>) converter.get().read(responseType, response); |
||||
} |
||||
|
||||
protected static Optional<HttpMessageConverter<?>> resolveConverter( |
||||
List<HttpMessageConverter<?>> messageConverters, ResolvableType type, |
||||
MediaType mediaType) { |
||||
return messageConverters.stream().filter(e -> e.canRead(type, mediaType)) |
||||
.findFirst(); |
||||
} |
||||
} |
||||
@ -1,131 +0,0 @@
@@ -1,131 +0,0 @@
|
||||
/* |
||||
* 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.web.client.reactive; |
||||
|
||||
import java.nio.charset.Charset; |
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
|
||||
import reactor.core.converter.RxJava1ObservableConverter; |
||||
import reactor.core.converter.RxJava1SingleConverter; |
||||
import reactor.core.publisher.Flux; |
||||
import reactor.core.publisher.Mono; |
||||
import rx.Observable; |
||||
import rx.Single; |
||||
|
||||
import org.springframework.core.ResolvableType; |
||||
import org.springframework.core.codec.Decoder; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.http.client.reactive.ClientHttpResponse; |
||||
|
||||
/** |
||||
* Static factory methods for {@link WebResponseExtractor} |
||||
* based on the {@link Observable} and {@link Single} API. |
||||
* |
||||
* @author Brian Clozel |
||||
*/ |
||||
public class RxJava1WebResponseExtractors { |
||||
|
||||
private static final Charset UTF_8 = Charset.forName("UTF-8"); |
||||
|
||||
private static final Object[] HINTS = new Object[] {UTF_8}; |
||||
|
||||
/** |
||||
* Extract the response body and decode it, returning it as a {@code Single<T>} |
||||
*/ |
||||
public static <T> WebResponseExtractor<Single<T>> body(Class<T> sourceClass) { |
||||
|
||||
ResolvableType resolvableType = ResolvableType.forClass(sourceClass); |
||||
//noinspection unchecked
|
||||
return webResponse -> (Single<T>) RxJava1SingleConverter.fromPublisher(webResponse.getClientResponse() |
||||
.flatMap(resp -> decodeResponseBody(resp, resolvableType, webResponse.getMessageDecoders())) |
||||
.next()); |
||||
} |
||||
|
||||
/** |
||||
* Extract the response body and decode it, returning it as an {@code Observable<T>} |
||||
*/ |
||||
public static <T> WebResponseExtractor<Observable<T>> bodyStream(Class<T> sourceClass) { |
||||
|
||||
ResolvableType resolvableType = ResolvableType.forClass(sourceClass); |
||||
return webResponse -> RxJava1ObservableConverter.fromPublisher(webResponse.getClientResponse() |
||||
.flatMap(resp -> decodeResponseBody(resp, resolvableType, webResponse.getMessageDecoders()))); |
||||
} |
||||
|
||||
/** |
||||
* Extract the full response body as a {@code ResponseEntity} |
||||
* with its body decoded as a single type {@code T} |
||||
*/ |
||||
public static <T> WebResponseExtractor<Single<ResponseEntity<T>>> response(Class<T> sourceClass) { |
||||
|
||||
ResolvableType resolvableType = ResolvableType.forClass(sourceClass); |
||||
return webResponse -> (Single<ResponseEntity<T>>) |
||||
RxJava1SingleConverter.fromPublisher(webResponse.getClientResponse() |
||||
.then(response -> |
||||
Mono.when( |
||||
decodeResponseBody(response, resolvableType, webResponse.getMessageDecoders()).next(), |
||||
Mono.just(response.getHeaders()), |
||||
Mono.just(response.getStatusCode()))) |
||||
.map(tuple -> { |
||||
//noinspection unchecked
|
||||
return new ResponseEntity<>((T) tuple.getT1(), tuple.getT2(), tuple.getT3()); |
||||
})); |
||||
} |
||||
|
||||
/** |
||||
* Extract the full response body as a {@code ResponseEntity} |
||||
* with its body decoded as an {@code Observable<T>} |
||||
*/ |
||||
public static <T> WebResponseExtractor<Single<ResponseEntity<Observable<T>>>> responseStream(Class<T> sourceClass) { |
||||
ResolvableType resolvableType = ResolvableType.forClass(sourceClass); |
||||
return webResponse -> RxJava1SingleConverter.fromPublisher(webResponse.getClientResponse() |
||||
.map(response -> new ResponseEntity<>( |
||||
RxJava1ObservableConverter |
||||
.fromPublisher(decodeResponseBody(response, resolvableType, webResponse.getMessageDecoders())), |
||||
response.getHeaders(), |
||||
response.getStatusCode()))); |
||||
} |
||||
|
||||
/** |
||||
* Extract the response headers as an {@code HttpHeaders} instance |
||||
*/ |
||||
public static WebResponseExtractor<Single<HttpHeaders>> headers() { |
||||
return webResponse -> RxJava1SingleConverter |
||||
.fromPublisher(webResponse.getClientResponse().map(resp -> resp.getHeaders())); |
||||
} |
||||
|
||||
protected static <T> Flux<T> decodeResponseBody(ClientHttpResponse response, ResolvableType responseType, |
||||
List<Decoder<?>> messageDecoders) { |
||||
|
||||
MediaType contentType = response.getHeaders().getContentType(); |
||||
Optional<Decoder<?>> decoder = resolveDecoder(messageDecoders, responseType, contentType); |
||||
if (!decoder.isPresent()) { |
||||
return Flux.error(new IllegalStateException("Could not decode response body of type '" + contentType + |
||||
"' with target type '" + responseType.toString() + "'")); |
||||
} |
||||
//noinspection unchecked
|
||||
return (Flux<T>) decoder.get().decode(response.getBody(), responseType, contentType, HINTS); |
||||
} |
||||
|
||||
|
||||
protected static Optional<Decoder<?>> resolveDecoder(List<Decoder<?>> messageDecoders, ResolvableType type, |
||||
MediaType mediaType) { |
||||
return messageDecoders.stream().filter(e -> e.canDecode(type, mediaType)).findFirst(); |
||||
} |
||||
} |
||||
@ -1,161 +0,0 @@
@@ -1,161 +0,0 @@
|
||||
/* |
||||
* 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.web.client.reactive; |
||||
|
||||
import java.nio.charset.Charset; |
||||
import java.util.List; |
||||
import java.util.Optional; |
||||
|
||||
import reactor.core.publisher.Flux; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import org.springframework.core.ResolvableType; |
||||
import org.springframework.core.codec.Decoder; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.http.client.reactive.ClientHttpResponse; |
||||
|
||||
/** |
||||
* Static factory methods for {@link WebResponseExtractor} |
||||
* based on the {@link Flux} and {@link Mono} API. |
||||
* |
||||
* @author Brian Clozel |
||||
*/ |
||||
public class WebResponseExtractors { |
||||
|
||||
private static final Charset UTF_8 = Charset.forName("UTF-8"); |
||||
|
||||
private static final Object[] HINTS = new Object[] {UTF_8}; |
||||
|
||||
private static final Object EMPTY_BODY = new Object(); |
||||
|
||||
|
||||
/** |
||||
* Extract the response body and decode it, returning it as a {@code Mono<T>} |
||||
* @see ResolvableType#forClassWithGenerics(Class, Class[]) |
||||
*/ |
||||
public static <T> WebResponseExtractor<Mono<T>> body(ResolvableType bodyType) { |
||||
//noinspection unchecked
|
||||
return webResponse -> (Mono<T>) webResponse.getClientResponse() |
||||
.flatMap(resp -> decodeResponseBody(resp, bodyType, webResponse.getMessageDecoders())) |
||||
.next(); |
||||
} |
||||
|
||||
/** |
||||
* Extract the response body and decode it, returning it as a {@code Mono<T>} |
||||
*/ |
||||
public static <T> WebResponseExtractor<Mono<T>> body(Class<T> sourceClass) { |
||||
ResolvableType bodyType = ResolvableType.forClass(sourceClass); |
||||
return body(bodyType); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Extract the response body and decode it, returning it as a {@code Flux<T>} |
||||
* @see ResolvableType#forClassWithGenerics(Class, Class[]) |
||||
*/ |
||||
public static <T> WebResponseExtractor<Flux<T>> bodyStream(ResolvableType bodyType) { |
||||
return webResponse -> webResponse.getClientResponse() |
||||
.flatMap(resp -> decodeResponseBody(resp, bodyType, webResponse.getMessageDecoders())); |
||||
} |
||||
|
||||
/** |
||||
* Extract the response body and decode it, returning it as a {@code Flux<T>} |
||||
*/ |
||||
public static <T> WebResponseExtractor<Flux<T>> bodyStream(Class<T> sourceClass) { |
||||
ResolvableType bodyType = ResolvableType.forClass(sourceClass); |
||||
return bodyStream(bodyType); |
||||
} |
||||
|
||||
/** |
||||
* Extract the full response body as a {@code ResponseEntity} |
||||
* with its body decoded as a single type {@code T} |
||||
* @see ResolvableType#forClassWithGenerics(Class, Class[]) |
||||
*/ |
||||
public static <T> WebResponseExtractor<Mono<ResponseEntity<T>>> response(ResolvableType bodyType) { |
||||
return webResponse -> webResponse.getClientResponse() |
||||
.then(response -> { |
||||
List<Decoder<?>> decoders = webResponse.getMessageDecoders(); |
||||
return Mono.when( |
||||
decodeResponseBody(response, bodyType, decoders).next().defaultIfEmpty(EMPTY_BODY), |
||||
Mono.just(response.getHeaders()), |
||||
Mono.just(response.getStatusCode())); |
||||
}) |
||||
.map(tuple -> { |
||||
Object body = (tuple.getT1() != EMPTY_BODY ? tuple.getT1() : null); |
||||
//noinspection unchecked
|
||||
return new ResponseEntity<>((T) body, tuple.getT2(), tuple.getT3()); |
||||
}); |
||||
} |
||||
|
||||
/** |
||||
* Extract the full response body as a {@code ResponseEntity} |
||||
* with its body decoded as a single type {@code T} |
||||
*/ |
||||
public static <T> WebResponseExtractor<Mono<ResponseEntity<T>>> response(Class<T> bodyClass) { |
||||
ResolvableType bodyType = ResolvableType.forClass(bodyClass); |
||||
return response(bodyType); |
||||
} |
||||
|
||||
/** |
||||
* Extract the full response body as a {@code ResponseEntity} |
||||
* with its body decoded as a {@code Flux<T>} |
||||
* @see ResolvableType#forClassWithGenerics(Class, Class[]) |
||||
*/ |
||||
public static <T> WebResponseExtractor<Mono<ResponseEntity<Flux<T>>>> responseStream(ResolvableType type) { |
||||
return webResponse -> webResponse.getClientResponse() |
||||
.map(response -> new ResponseEntity<>( |
||||
decodeResponseBody(response, type, webResponse.getMessageDecoders()), |
||||
response.getHeaders(), response.getStatusCode())); |
||||
} |
||||
|
||||
/** |
||||
* Extract the full response body as a {@code ResponseEntity} |
||||
* with its body decoded as a {@code Flux<T>} |
||||
*/ |
||||
public static <T> WebResponseExtractor<Mono<ResponseEntity<Flux<T>>>> responseStream(Class<T> sourceClass) { |
||||
ResolvableType resolvableType = ResolvableType.forClass(sourceClass); |
||||
return responseStream(resolvableType); |
||||
} |
||||
|
||||
/** |
||||
* Extract the response headers as an {@code HttpHeaders} instance |
||||
*/ |
||||
public static WebResponseExtractor<Mono<HttpHeaders>> headers() { |
||||
return webResponse -> webResponse.getClientResponse().map(resp -> resp.getHeaders()); |
||||
} |
||||
|
||||
protected static <T> Flux<T> decodeResponseBody(ClientHttpResponse response, ResolvableType responseType, |
||||
List<Decoder<?>> messageDecoders) { |
||||
|
||||
MediaType contentType = response.getHeaders().getContentType(); |
||||
Optional<Decoder<?>> decoder = resolveDecoder(messageDecoders, responseType, contentType); |
||||
if (!decoder.isPresent()) { |
||||
return Flux.error(new IllegalStateException("Could not decode response body of type '" + contentType + |
||||
"' with target type '" + responseType.toString() + "'")); |
||||
} |
||||
//noinspection unchecked
|
||||
return (Flux<T>) decoder.get().decode(response.getBody(), responseType, contentType, HINTS); |
||||
} |
||||
|
||||
|
||||
protected static Optional<Decoder<?>> resolveDecoder(List<Decoder<?>> messageDecoders, ResolvableType type, |
||||
MediaType mediaType) { |
||||
return messageDecoders.stream().filter(e -> e.canDecode(type, mediaType)).findFirst(); |
||||
} |
||||
} |
||||
Loading…
Reference in new issue