提交 ebcee26d 编写于 作者: A Arjen Poutsma 提交者: Rossen Stoyanchev

Add AsyncRestTemplate

Added AsyncRestTemplate, the asynchronous counterpart to the
RestTemplate that was introduced in Spring 3. All methods on the
AsyncRestTemplate are similar to those found on the synchronous
RestTemplatem, except that they return Future wrappers instead of
concrete results.

To enable this, this commit introduces the AsyncClientHttpRequest and
AsyncClientHttpRequestFactory, similar to the ClientHttpRequest and
ClientHttpRequestFactory, except that ClientHttpRequest returns a
Future<ClientHttpResponse> for the execute method. Two implementations
of these interfaces are provided, one based on the HttpURLConnection
incombination with a Spring AsyncTaskExecutor and one based on Apache
HttpComponents HttpAsyncClient.

Issue: SPR-8804
上级 89b53cfc
......@@ -478,7 +478,8 @@ project("spring-web") {
optional("com.caucho:hessian:4.0.7")
optional("rome:rome:1.0")
optional("commons-fileupload:commons-fileupload:1.3")
optional("org.apache.httpcomponents:httpclient:4.2")
optional("org.apache.httpcomponents:httpclient:4.3-beta2")
optional("org.apache.httpcomponents:httpasyncclient:4.0-beta4")
optional("org.codehaus.jackson:jackson-mapper-asl:1.9.12")
optional("com.fasterxml.jackson.core:jackson-databind:2.2.0")
optional("taglibs:standard:1.1.2")
......@@ -619,7 +620,7 @@ project("spring-webmvc") {
testCompile("commons-fileupload:commons-fileupload:1.2")
testCompile("commons-io:commons-io:1.3")
testCompile("org.hibernate:hibernate-validator:4.3.0.Final")
testCompile("org.apache.httpcomponents:httpclient:4.2")
testCompile("org.apache.httpcomponents:httpclient:4.3-beta2")
}
// pick up DispatcherServlet.properties in src/main
......
/*
* Copyright 2002-2013 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;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.Future;
import org.springframework.http.HttpHeaders;
import org.springframework.util.Assert;
/**
* Abstract base for {@link AsyncClientHttpRequest} that makes sure that headers and body
* are not written multiple times.
*
* @author Arjen Poutsma
* @since 4.0
*/
abstract class AbstractAsyncClientHttpRequest implements AsyncClientHttpRequest {
private final HttpHeaders headers = new HttpHeaders();
private boolean executed = false;
@Override
public final HttpHeaders getHeaders() {
return (this.executed ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers);
}
@Override
public final OutputStream getBody() throws IOException {
assertNotExecuted();
return getBodyInternal(this.headers);
}
@Override
public Future<ClientHttpResponse> executeAsync() throws IOException {
assertNotExecuted();
Future<ClientHttpResponse> result = executeInternal(this.headers);
this.executed = true;
return result;
}
/**
* Asserts that this request has not been {@linkplain #execute() executed} yet.
*
* @throws IllegalStateException if this request has been executed
*/
protected void assertNotExecuted() {
Assert.state(!this.executed, "ClientHttpRequest already executed");
}
/**
* Abstract template method that returns the body.
* @param headers the HTTP headers
* @return the body output stream
*/
protected abstract OutputStream getBodyInternal(HttpHeaders headers) throws IOException;
/**
* Abstract template method that writes the given headers and content to the HTTP request.
* @param headers the HTTP headers
* @return the response object for the executed request
*/
protected abstract Future<ClientHttpResponse> executeInternal(HttpHeaders headers) throws IOException;
}
/*
* Copyright 2002-2013 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;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.concurrent.Future;
import org.springframework.http.HttpHeaders;
/**
* Abstract base for {@link org.springframework.http.client.ClientHttpRequest} that buffers output in a byte array before sending it over the wire.
*
* @author Arjen Poutsma
* @since 3.0.6
*/
abstract class AbstractBufferingAsyncClientHttpRequest
extends AbstractAsyncClientHttpRequest {
private ByteArrayOutputStream bufferedOutput = new ByteArrayOutputStream();
@Override
protected OutputStream getBodyInternal(HttpHeaders headers) throws IOException {
return this.bufferedOutput;
}
@Override
protected Future<ClientHttpResponse> executeInternal(HttpHeaders headers) throws IOException {
byte[] bytes = this.bufferedOutput.toByteArray();
if (headers.getContentLength() == -1) {
headers.setContentLength(bytes.length);
}
Future<ClientHttpResponse> result = executeInternal(headers, bytes);
this.bufferedOutput = null;
return result;
}
/**
* Abstract template method that writes the given headers and content to the HTTP
* request.
*
* @param headers the HTTP headers
* @param bufferedOutput the body content
* @return the response object for the executed request
*/
protected abstract Future<ClientHttpResponse> executeInternal(HttpHeaders headers,
byte[] bufferedOutput) throws IOException;
}
/*
* Copyright 2002-2013 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;
import java.io.IOException;
import java.util.concurrent.Future;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.HttpRequest;
/**
* Represents a client-side asynchronous HTTP request. Created via an implementation of
* the {@link AsyncClientHttpRequestFactory}.
* <p>A {@code AsyncHttpRequest} can be {@linkplain #executeAsync() executed}, getting a
* future {@link ClientHttpResponse} which can be read from.
*
* @author Arjen Poutsma
* @since 4.0
* @see AsyncClientHttpRequestFactory#createAsyncRequest(java.net.URI, org.springframework.http.HttpMethod)
*/
public interface AsyncClientHttpRequest extends HttpRequest, HttpOutputMessage {
/**
* Execute this request asynchronously, resulting in a future
* {@link ClientHttpResponse} that can be read.
*
* @return the future response result of the execution
* @throws java.io.IOException in case of I/O errors
*/
Future<ClientHttpResponse> executeAsync() throws IOException;
}
/*
* Copyright 2002-2013 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;
import java.io.IOException;
import java.net.URI;
import org.springframework.http.HttpMethod;
/**
* Factory for {@link AsyncClientHttpRequest} objects. Requests are created by the
* {@link #createAsyncRequest(URI, HttpMethod)} method.
*
* @author Arjen Poutsma
* @since 4.0
*/
public interface AsyncClientHttpRequestFactory {
/**
* Create a new asynchronous {@link AsyncClientHttpRequest} for the specified URI and
* HTTP method.
* <p>The returned request can be written to, and then executed by calling
* {@link AsyncClientHttpRequest#executeAsync()}.
*
* @param uri the URI to create a request for
* @param httpMethod the HTTP method to execute
* @return the created request
* @throws IOException in case of I/O errors
*/
AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod)
throws IOException;
}
......@@ -25,8 +25,8 @@ import org.springframework.http.HttpRequest;
/**
* Represents a client-side HTTP request. Created via an implementation of the {@link ClientHttpRequestFactory}.
*
* <p>A {@code HttpRequest} can be {@linkplain #execute() executed}, getting a {@link ClientHttpResponse}
* which can be read from.
* <p>A {@code ClientHttpRequest} can be {@linkplain #execute() executed}, getting a
* {@link ClientHttpResponse} which can be read from.
*
* @author Arjen Poutsma
* @since 3.0
......
/*
* Copyright 2002-2013 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;
import java.io.IOException;
import java.net.URI;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.nio.client.HttpAsyncClient;
import org.apache.http.protocol.HttpContext;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
/**
* {@link ClientHttpRequest} implementation that uses Apache HttpComponents HttpClient to
* execute requests.
*
* <p>Created via the {@link org.springframework.http.client.HttpComponentsClientHttpRequestFactory}.
*
* @author Oleg Kalnichevski
* @author Arjen Poutsma
* @see org.springframework.http.client.HttpComponentsClientHttpRequestFactory#createRequest(java.net.URI,
* org.springframework.http.HttpMethod)
* @since 3.1
*/
final class HttpComponentsAsyncClientHttpRequest extends AbstractBufferingAsyncClientHttpRequest {
private final HttpAsyncClient httpClient;
private final HttpUriRequest httpRequest;
private final HttpContext httpContext;
public HttpComponentsAsyncClientHttpRequest(HttpAsyncClient httpClient,
HttpUriRequest httpRequest, HttpContext httpContext) {
this.httpClient = httpClient;
this.httpRequest = httpRequest;
this.httpContext = httpContext;
}
@Override
public HttpMethod getMethod() {
return HttpMethod.valueOf(this.httpRequest.getMethod());
}
@Override
public URI getURI() {
return this.httpRequest.getURI();
}
@Override
protected Future<ClientHttpResponse> executeInternal(HttpHeaders headers,
byte[] bufferedOutput) throws IOException {
HttpComponentsClientHttpRequest.addHeaders(this.httpRequest, headers);
if (this.httpRequest instanceof HttpEntityEnclosingRequest) {
HttpEntityEnclosingRequest entityEnclosingRequest =
(HttpEntityEnclosingRequest) this.httpRequest;
HttpEntity requestEntity = new ByteArrayEntity(bufferedOutput);
entityEnclosingRequest.setEntity(requestEntity);
}
final Future<HttpResponse> futureResponse =
this.httpClient.execute(this.httpRequest, this.httpContext, null);
return new ClientHttpResponseFuture(futureResponse);
}
private static class ClientHttpResponseFuture implements Future<ClientHttpResponse> {
private final Future<HttpResponse> futureResponse;
public ClientHttpResponseFuture(Future<HttpResponse> futureResponse) {
this.futureResponse = futureResponse;
}
@Override
public boolean cancel(boolean mayInterruptIfRunning) {
return futureResponse.cancel(mayInterruptIfRunning);
}
@Override
public boolean isCancelled() {
return futureResponse.isCancelled();
}
@Override
public boolean isDone() {
return futureResponse.isDone();
}
@Override
public ClientHttpResponse get()
throws InterruptedException, ExecutionException {
HttpResponse response = futureResponse.get();
return new HttpComponentsClientHttpResponse(response);
}
@Override
public ClientHttpResponse get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
HttpResponse response = futureResponse.get(timeout, unit);
return new HttpComponentsClientHttpResponse(response);
}
}
}
/*
* Copyright 2002-2013 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;
import java.io.IOException;
import java.net.URI;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.nio.client.HttpAsyncClient;
import org.apache.http.nio.reactor.IOReactorStatus;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.http.HttpMethod;
import org.springframework.util.Assert;
/**
* Asynchronous extension of the {@link HttpComponentsClientHttpRequestFactory}. Uses
* <a href="http://hc.apache.org/httpcomponents-asyncclient-dev/">Apache HttpComponents
* HttpAsyncClient</a> to create requests.
*
* @author Arjen Poutsma
* @since 4.0
* @see HttpAsyncClient
*/
public class HttpComponentsAsyncClientHttpRequestFactory
extends HttpComponentsClientHttpRequestFactory
implements AsyncClientHttpRequestFactory, InitializingBean {
private HttpAsyncClient httpAsyncClient;
/**
* Create a new instance of the {@code HttpComponentsAsyncClientHttpRequestFactory}
* with a default {@link HttpAsyncClient} and {@link HttpClient}.
*/
public HttpComponentsAsyncClientHttpRequestFactory() {
this(HttpAsyncClients.createDefault());
}
/**
* Create a new instance of the {@code HttpComponentsAsyncClientHttpRequestFactory}
* with the given {@link HttpAsyncClient} instance and a default {@link HttpClient}.
* @param httpAsyncClient the HttpAsyncClient instance to use for this request factory
*/
public HttpComponentsAsyncClientHttpRequestFactory(HttpAsyncClient httpAsyncClient) {
super();
Assert.notNull(httpAsyncClient, "'httpAsyncClient' must not be null");
this.httpAsyncClient = httpAsyncClient;
}
/**
* Create a new instance of the {@code HttpComponentsAsyncClientHttpRequestFactory}
* with the given {@link HttpClient} and {@link HttpAsyncClient} instances.
* @param httpClient the HttpClient instance to use for this request factory
* @param httpAsyncClient the HttpAsyncClient instance to use for this request factory
*/
public HttpComponentsAsyncClientHttpRequestFactory(HttpClient httpClient,
HttpAsyncClient httpAsyncClient) {
super(httpClient);
Assert.notNull(httpAsyncClient, "'httpAsyncClient' must not be null");
this.httpAsyncClient = httpAsyncClient;
}
/**
* Set the {@code HttpClient} used for
* {@linkplain #createAsyncRequest(java.net.URI, org.springframework.http.HttpMethod) asynchronous execution}.
*/
public void setHttpAsyncClient(HttpAsyncClient httpAsyncClient) {
this.httpAsyncClient = httpAsyncClient;
}
/**
* Return the {@code HttpClient} used for
* {@linkplain #createAsyncRequest(URI, HttpMethod) asynchronous execution}.
*/
public HttpAsyncClient getHttpAsyncClient() {
return httpAsyncClient;
}
@Override
public void afterPropertiesSet() {
startAsyncClient();
}
private void startAsyncClient() {
HttpAsyncClient asyncClient = getHttpAsyncClient();
if (asyncClient.getStatus() != IOReactorStatus.ACTIVE) {
asyncClient.start();
}
}
@Override
public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod)
throws IOException {
HttpAsyncClient asyncClient = getHttpAsyncClient();
startAsyncClient();
HttpUriRequest httpRequest = createHttpUriRequest(httpMethod, uri);
postProcessHttpRequest(httpRequest);
return new HttpComponentsAsyncClientHttpRequest(asyncClient, httpRequest,
createHttpContext(httpMethod, uri));
}
@Override
public void destroy() throws Exception {
try {
super.destroy();
}
finally {
getHttpAsyncClient().shutdown();
}
}
}
......@@ -29,12 +29,7 @@ import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpTrace;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.protocol.HttpContext;
......@@ -43,9 +38,9 @@ import org.springframework.http.HttpMethod;
import org.springframework.util.Assert;
/**
* {@link org.springframework.http.client.ClientHttpRequestFactory} implementation that uses
* <a href="http://hc.apache.org/httpcomponents-client-ga/httpclient/">Apache HttpComponents HttpClient</a>
* to create requests.
* {@link org.springframework.http.client.ClientHttpRequestFactory} implementation that
* uses <a href="http://hc.apache.org/httpcomponents-client-ga/">Apache HttpComponents
* HttpClient</a> to create requests.
*
* <p>Allows to use a pre-configured {@link HttpClient} instance -
* potentially with authentication, HTTP connection pooling, etc.
......@@ -54,56 +49,43 @@ import org.springframework.util.Assert;
* @author Arjen Poutsma
* @since 3.1
*/
public class HttpComponentsClientHttpRequestFactory implements ClientHttpRequestFactory, DisposableBean {
private static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 100;
private static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 5;
private static final int DEFAULT_READ_TIMEOUT_MILLISECONDS = (60 * 1000);
public class HttpComponentsClientHttpRequestFactory
implements ClientHttpRequestFactory, DisposableBean {
private HttpClient httpClient;
private boolean bufferRequestBody = true;
/**
* Create a new instance of the HttpComponentsClientHttpRequestFactory with a default
* {@link HttpClient} that uses a default {@link org.apache.http.impl.conn.PoolingClientConnectionManager}.
* Create a new instance of the {@code HttpComponentsClientHttpRequestFactory} with
* a default {@link HttpClient}.
*/
public HttpComponentsClientHttpRequestFactory() {
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory()));
schemeRegistry.register(new Scheme("https", 443, SSLSocketFactory.getSocketFactory()));
PoolingClientConnectionManager connectionManager = new PoolingClientConnectionManager(schemeRegistry);
connectionManager.setMaxTotal(DEFAULT_MAX_TOTAL_CONNECTIONS);
connectionManager.setDefaultMaxPerRoute(DEFAULT_MAX_CONNECTIONS_PER_ROUTE);
this.httpClient = new DefaultHttpClient(connectionManager);
setReadTimeout(DEFAULT_READ_TIMEOUT_MILLISECONDS);
this(HttpClients.createDefault());
}
/**
* Create a new instance of the HttpComponentsClientHttpRequestFactory
* Create a new instance of the {@code HttpComponentsClientHttpRequestFactory}
* with the given {@link HttpClient} instance.
* @param httpClient the HttpClient instance to use for this request factory
*/
public HttpComponentsClientHttpRequestFactory(HttpClient httpClient) {
Assert.notNull(httpClient, "HttpClient must not be null");
Assert.notNull(httpClient, "'httpClient' must not be null");
this.httpClient = httpClient;
}
/**
* Set the {@code HttpClient} used by this factory.
* Set the {@code HttpClient} used for
* {@linkplain #createRequest(URI, HttpMethod) synchronous execution}.
*/
public void setHttpClient(HttpClient httpClient) {
this.httpClient = httpClient;
}
/**
* Return the {@code HttpClient} used by this factory.
* Return the {@code HttpClient} used for
* {@linkplain #createRequest(URI, HttpMethod) synchronous execution}.
*/
public HttpClient getHttpClient() {
return this.httpClient;
......@@ -113,7 +95,9 @@ public class HttpComponentsClientHttpRequestFactory implements ClientHttpRequest
* Set the connection timeout for the underlying HttpClient.
* A timeout value of 0 specifies an infinite timeout.
* @param timeout the timeout value in milliseconds
* @deprecated With no direct replacement
*/
@Deprecated
public void setConnectTimeout(int timeout) {
Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
getHttpClient().getParams().setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, timeout);
......@@ -123,7 +107,9 @@ public class HttpComponentsClientHttpRequestFactory implements ClientHttpRequest
* Set the socket read timeout for the underlying HttpClient.
* A timeout value of 0 specifies an infinite timeout.
* @param timeout the timeout value in milliseconds
* @deprecated With no direct replacement
*/
@Deprecated
public void setReadTimeout(int timeout) {
Assert.isTrue(timeout >= 0, "Timeout must be a non-negative value");
getHttpClient().getParams().setIntParameter(CoreConnectionPNames.SO_TIMEOUT, timeout);
......@@ -139,16 +125,28 @@ public class HttpComponentsClientHttpRequestFactory implements ClientHttpRequest
this.bufferRequestBody = bufferRequestBody;
}
/**
* Indicates whether this request factory should buffer the request body internally.
*/
public boolean isBufferRequestBody() {
return bufferRequestBody;
}
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
HttpClient client = getHttpClient();
Assert.state(client != null,
"Synchronous execution requires an HttpClient to be set");
HttpUriRequest httpRequest = createHttpUriRequest(httpMethod, uri);
postProcessHttpRequest(httpRequest);
if (bufferRequestBody) {
return new HttpComponentsClientHttpRequest(getHttpClient(), httpRequest,
return new HttpComponentsClientHttpRequest(client, httpRequest,
createHttpContext(httpMethod, uri));
}
else {
return new HttpComponentsStreamingClientHttpRequest(getHttpClient(),
return new HttpComponentsStreamingClientHttpRequest(client,
httpRequest, createHttpContext(httpMethod, uri));
}
}
......@@ -208,7 +206,7 @@ public class HttpComponentsClientHttpRequestFactory implements ClientHttpRequest
* connection pool, if any.
*/
@Override
public void destroy() {
public void destroy() throws Exception {
getHttpClient().getConnectionManager().shutdown();
}
......
......@@ -108,6 +108,7 @@ final class HttpComponentsStreamingClientHttpRequest extends AbstractClientHttpR
private final StreamingHttpOutputMessage.Body body;
private StreamingHttpEntity(HttpHeaders headers,
StreamingHttpOutputMessage.Body body) {
this.headers = headers;
......
/*
* Copyright 2002-2013 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;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.util.FileCopyUtils;
/**
* {@link org.springframework.http.client.ClientHttpRequest} implementation that uses
* standard J2SE facilities to execute buffered requests. Created via the
* {@link org.springframework.http.client.SimpleClientHttpRequestFactory}.
*
* @author Arjen Poutsma
* @since 3.0
* @see org.springframework.http.client.SimpleClientHttpRequestFactory#createRequest(java.net.URI, org.springframework.http.HttpMethod)
*/
final class SimpleBufferingAsyncClientHttpRequest extends AbstractBufferingAsyncClientHttpRequest {
private final HttpURLConnection connection;
private final boolean outputStreaming;
private final AsyncTaskExecutor taskExecutor;
SimpleBufferingAsyncClientHttpRequest(HttpURLConnection connection,
boolean outputStreaming, AsyncTaskExecutor taskExecutor) {
this.connection = connection;
this.outputStreaming = outputStreaming;
this.taskExecutor = taskExecutor;
}
@Override
public HttpMethod getMethod() {
return HttpMethod.valueOf(this.connection.getRequestMethod());
}
@Override
public URI getURI() {
try {
return this.connection.getURL().toURI();
}
catch (URISyntaxException ex) {
throw new IllegalStateException("Could not get HttpURLConnection URI: " + ex.getMessage(), ex);
}
}
@Override
protected Future<ClientHttpResponse> executeInternal(final HttpHeaders headers,
final byte[] bufferedOutput) throws IOException {
return taskExecutor.submit(new Callable<ClientHttpResponse>() {
@Override
public ClientHttpResponse call() throws Exception {
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
String headerName = entry.getKey();
for (String headerValue : entry.getValue()) {
connection.addRequestProperty(headerName, headerValue);
}
}
if (connection.getDoOutput() && outputStreaming) {
connection.setFixedLengthStreamingMode(bufferedOutput.length);
}
connection.connect();
if (connection.getDoOutput()) {
FileCopyUtils.copy(bufferedOutput, connection.getOutputStream());
}
return new SimpleClientHttpResponse(connection);
}
});
}
}
......@@ -23,6 +23,7 @@ import java.net.URI;
import java.net.URL;
import java.net.URLConnection;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.http.HttpMethod;
import org.springframework.util.Assert;
......@@ -33,9 +34,10 @@ import org.springframework.util.Assert;
* @author Juergen Hoeller
* @since 3.0
* @see java.net.HttpURLConnection
* @see CommonsClientHttpRequestFactory
* @see HttpComponentsClientHttpRequestFactory
*/
public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory {
public class SimpleClientHttpRequestFactory
implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory {
private static final int DEFAULT_CHUNK_SIZE = 4096;
......@@ -52,6 +54,8 @@ public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory
private boolean outputStreaming = true;
private AsyncTaskExecutor taskExecutor;
/**
* Set the {@link Proxy} to use for this request factory.
......@@ -121,6 +125,15 @@ public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory
this.outputStreaming = outputStreaming;
}
/**
* Sets the task executor for this request factory. Setting this property is required
* for {@linkplain #createAsyncRequest(URI, HttpMethod) creating asynchronous
* request}.
* @param taskExecutor the task executor
*/
public void setTaskExecutor(AsyncTaskExecutor taskExecutor) {
this.taskExecutor = taskExecutor;
}
@Override
public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException {
......@@ -130,8 +143,28 @@ public class SimpleClientHttpRequestFactory implements ClientHttpRequestFactory
return new SimpleBufferingClientHttpRequest(connection, this.outputStreaming);
}
else {
return new SimpleStreamingClientHttpRequest(connection, this.chunkSize,
this.outputStreaming);
return new SimpleStreamingClientHttpRequest(connection, this.chunkSize, this.outputStreaming);
}
}
/**
* {@inheritDoc}
* <p>Setting the {@link #setTaskExecutor(AsyncTaskExecutor) taskExecutor} property
* is required before calling this method.
*/
@Override
public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod)
throws IOException {
Assert.state(this.taskExecutor != null, "Asynchronous execution requires an " +
"AsyncTaskExecutor to be set");
HttpURLConnection connection = openConnection(uri.toURL(), this.proxy);
prepareConnection(connection, httpMethod.name());
if (this.bufferRequestBody) {
return new SimpleBufferingAsyncClientHttpRequest(connection, this.outputStreaming, this.taskExecutor);
}
else {
return new SimpleStreamingAsyncClientHttpRequest(connection, this.chunkSize,
this.outputStreaming, this.taskExecutor);
}
}
......
/*
* Copyright 2002-2013 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;
import java.io.IOException;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.util.StreamUtils;
/**
* {@link org.springframework.http.client.ClientHttpRequest} implementation that uses
* standard J2SE facilities to execute streaming requests. Created via the {@link
* org.springframework.http.client.SimpleClientHttpRequestFactory}.
*
* @author Arjen Poutsma
* @see org.springframework.http.client.SimpleClientHttpRequestFactory#createRequest(java.net.URI,
* org.springframework.http.HttpMethod)
* @since 3.0
*/
final class SimpleStreamingAsyncClientHttpRequest extends AbstractAsyncClientHttpRequest {
private final HttpURLConnection connection;
private final int chunkSize;
private OutputStream body;
private final boolean outputStreaming;
private final AsyncTaskExecutor taskExecutor;
SimpleStreamingAsyncClientHttpRequest(HttpURLConnection connection, int chunkSize,
boolean outputStreaming, AsyncTaskExecutor taskExecutor) {
this.connection = connection;
this.chunkSize = chunkSize;
this.outputStreaming = outputStreaming;
this.taskExecutor = taskExecutor;
}
@Override
public HttpMethod getMethod() {
return HttpMethod.valueOf(this.connection.getRequestMethod());
}
@Override
public URI getURI() {
try {
return this.connection.getURL().toURI();
}
catch (URISyntaxException ex) {
throw new IllegalStateException(
"Could not get HttpURLConnection URI: " + ex.getMessage(), ex);
}
}
@Override
protected OutputStream getBodyInternal(HttpHeaders headers) throws IOException {
if (this.body == null) {
if (this.outputStreaming) {
int contentLength = (int) headers.getContentLength();
if (contentLength >= 0) {
this.connection.setFixedLengthStreamingMode(contentLength);
}
else {
this.connection.setChunkedStreamingMode(this.chunkSize);
}
}
writeHeaders(headers);
this.connection.connect();
this.body = this.connection.getOutputStream();
}
return StreamUtils.nonClosing(this.body);
}
private void writeHeaders(HttpHeaders headers) {
for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
String headerName = entry.getKey();
for (String headerValue : entry.getValue()) {
this.connection.addRequestProperty(headerName, headerValue);
}
}
}
@Override
protected Future<ClientHttpResponse> executeInternal(final HttpHeaders headers)
throws IOException {
return taskExecutor.submit(new Callable<ClientHttpResponse>() {
@Override
public ClientHttpResponse call() throws Exception {
try {
if (body != null) {
body.close();
}
else {
writeHeaders(headers);
connection.connect();
}
}
catch (IOException ex) {
// ignore
}
return new SimpleClientHttpResponse(connection);
}
});
}
}
/*
* Copyright 2002-2013 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.support;
import java.io.IOException;
import java.net.URI;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.AsyncClientHttpRequest;
import org.springframework.http.client.AsyncClientHttpRequestFactory;
import org.springframework.util.Assert;
/**
* Base class for {@link org.springframework.web.client.AsyncRestTemplate}
* and other HTTP accessing gateway helpers, defining common properties
* such as the {@link AsyncClientHttpRequestFactory} to operate on.
*
* <p>Not intended to be used directly. See
* {@link org.springframework.web.client.AsyncRestTemplate}.
*
* @author Arjen Poutsma
* @since 4.0
* @see org.springframework.web.client.AsyncRestTemplate
*/
public class AsyncHttpAccessor {
/** Logger available to subclasses. */
protected final Log logger = LogFactory.getLog(getClass());
private AsyncClientHttpRequestFactory asyncRequestFactory;
/**
* Set the request factory that this accessor uses for obtaining {@link
* org.springframework.http.client.ClientHttpRequest HttpRequests}.
*/
public void setAsyncRequestFactory(AsyncClientHttpRequestFactory asyncRequestFactory) {
Assert.notNull(asyncRequestFactory, "'asyncRequestFactory' must not be null");
this.asyncRequestFactory = asyncRequestFactory;
}
/**
* Return the request factory that this accessor uses for obtaining {@link
* org.springframework.http.client.ClientHttpRequest HttpRequests}.
*/
public AsyncClientHttpRequestFactory getAsyncRequestFactory() {
return this.asyncRequestFactory;
}
/**
* Create a new {@link AsyncClientHttpRequest} via this template's {@link
* AsyncClientHttpRequestFactory}.
*
* @param url the URL to connect to
* @param method the HTTP method to execute (GET, POST, etc.)
* @return the created request
* @throws IOException in case of I/O errors
*/
protected AsyncClientHttpRequest createAsyncRequest(URI url, HttpMethod method)
throws IOException {
AsyncClientHttpRequest request = getAsyncRequestFactory().createAsyncRequest(url, method);
if (logger.isDebugEnabled()) {
logger.debug("Created asynchronous " + method.name() + " request for \"" + url + "\"");
}
return request;
}
}
/*
* Copyright 2002-2013 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;
import java.io.IOException;
import org.springframework.http.client.AsyncClientHttpRequest;
/**
* Callback interface for code that operates on an {@link AsyncClientHttpRequest}. Allows
* to manipulate the request headers, and write to the request body.
*
* <p>Used internally by the {@link AsyncRestTemplate}, but also useful for application code.
*
* @author Arjen Poutsma
* @see org.springframework.web.client.AsyncRestTemplate#execute
* @since 4.0
*/
public interface AsyncRequestCallback {
/**
* Gets called by {@link AsyncRestTemplate#execute} with an opened {@code ClientHttpRequest}.
* Does not need to care about closing the request or about handling errors:
* this will all be handled by the {@code RestTemplate}.
* @param request the active HTTP request
* @throws java.io.IOException in case of I/O errors
*/
void doWithRequest(AsyncClientHttpRequest request) throws IOException;
}
/*
* Copyright 2002-2013 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;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.client.AsyncClientHttpRequest;
import org.springframework.http.client.ClientHttpRequest;
import org.springframework.http.client.ClientHttpResponse;
/**
* Adapts a {@link RequestCallback} to the {@link AsyncRequestCallback} interface.
*
* @author Arjen Poutsma
* @since 4.0
*/
public class AsyncRequestCallbackAdapter implements AsyncRequestCallback {
private final RequestCallback adaptee;
/**
* Creates a new {@code AsyncRequestCallbackAdapter} from the given
* {@link RequestCallback}.
*
* @param requestCallback the callback to base this adapter on
*/
public AsyncRequestCallbackAdapter(RequestCallback requestCallback) {
this.adaptee = requestCallback;
}
@Override
public void doWithRequest(final AsyncClientHttpRequest request) throws IOException {
if (adaptee != null) {
adaptee.doWithRequest(new ClientHttpRequest() {
@Override
public ClientHttpResponse execute() throws IOException {
throw new UnsupportedOperationException("execute not supported");
}
@Override
public OutputStream getBody() throws IOException {
return request.getBody();
}
@Override
public HttpMethod getMethod() {
return request.getMethod();
}
@Override
public URI getURI() {
return request.getURI();
}
@Override
public HttpHeaders getHeaders() {
return request.getHeaders();
}
});
}
}
}
/*
* Copyright 2002-2013 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;
import java.net.URI;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Future;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
/**
* Interface specifying a basic set of asynchronous RESTful operations. Implemented by
* {@link AsyncRestTemplate}. Not often used directly, but a useful option to enhance
* testability, as it can easily be mocked or stubbed.
*
* @author Arjen Poutsma
* @since 4.0
*/
public interface AsyncRestOperations {
/**
* Expose the synchronous Spring RestTemplate to allow synchronous invocation.
*/
RestOperations getRestOperations();
// GET
/**
* Asynchronously retrieve an entity by doing a GET on the specified URL. The response is
* converted and stored in an {@link ResponseEntity}.
* <p>URI Template variables are expanded using the given URI variables, if any.
* @param url the URL
* @param responseType the type of the return value
* @param uriVariables the variables to expand the template
* @return the entity wrapped in a {@link Future}
*/
<T> Future<ResponseEntity<T>> getForEntity(String url, Class<T> responseType,
Object... uriVariables) throws RestClientException;
/**
* Asynchronously retrieve a representation by doing a GET on the URI template. The
* response is converted and stored in an {@link ResponseEntity}.
* <p>URI Template variables are expanded using the given map.
* @param url the URL
* @param responseType the type of the return value
* @param uriVariables the map containing variables for the URI template
* @return the entity wrapped in a {@link Future}
*/
<T> Future<ResponseEntity<T>> getForEntity(String url, Class<T> responseType,
Map<String, ?> uriVariables) throws RestClientException;
/**
* Asynchronously retrieve a representation by doing a GET on the URL.
* The response is converted and stored in an {@link ResponseEntity}.
* @param url the URL
* @param responseType the type of the return value
* @return the entity wrapped in a {@link Future}
*/
<T> Future<ResponseEntity<T>> getForEntity(URI url, Class<T> responseType)
throws RestClientException;
// HEAD
/**
* Asynchronously retrieve all headers of the resource specified by the URI template.
* <p>URI Template variables are expanded using the given URI variables, if any.
* @param url the URL
* @param uriVariables the variables to expand the template
* @return all HTTP headers of that resource wrapped in a {@link Future}
*/
Future<HttpHeaders> headForHeaders(String url, Object... uriVariables)
throws RestClientException;
/**
* Asynchronously retrieve all headers of the resource specified by the URI template.
* <p>URI Template variables are expanded using the given map.
* @param url the URL
* @param uriVariables the map containing variables for the URI template
* @return all HTTP headers of that resource wrapped in a {@link Future}
*/
Future<HttpHeaders> headForHeaders(String url, Map<String, ?> uriVariables)
throws RestClientException;
/**
* Asynchronously retrieve all headers of the resource specified by the URL.
* @param url the URL
* @return all HTTP headers of that resource wrapped in a {@link Future}
*/
Future<HttpHeaders> headForHeaders(URI url) throws RestClientException;
// POST
/**
* Create a new resource by POSTing the given object to the URI template, and
* asynchronously returns the value of the {@code Location} header. This header
* typically indicates where the new resource is stored.
* <p>URI Template variables are expanded using the given URI variables, if any.
* @param url the URL
* @param request the Object to be POSTed, may be {@code null}
* @param uriVariables the variables to expand the template
* @return the value for the {@code Location} header wrapped in a {@link Future}
* @see org.springframework.http.HttpEntity
*/
Future<URI> postForLocation(String url, HttpEntity<?> request, Object... uriVariables)
throws RestClientException;
/**
* Create a new resource by POSTing the given object to the URI template, and
* asynchronously returns the value of the {@code Location} header. This header
* typically indicates where the new resource is stored.
* <p>URI Template variables are expanded using the given map.
* @param url the URL
* @param request the Object to be POSTed, may be {@code null}
* @param uriVariables the variables to expand the template
* @return the value for the {@code Location} header wrapped in a {@link Future}
* @see org.springframework.http.HttpEntity
*/
Future<URI> postForLocation(String url, HttpEntity<?> request, Map<String, ?> uriVariables)
throws RestClientException;
/**
* Create a new resource by POSTing the given object to the URL, and asynchronously
* returns the value of the {@code Location} header. This header typically indicates
* where the new resource is stored.
* @param url the URL
* @param request the Object to be POSTed, may be {@code null}
* @return the value for the {@code Location} header wrapped in a {@link Future}
* @see org.springframework.http.HttpEntity
*/
Future<URI> postForLocation(URI url, HttpEntity<?> request) throws RestClientException;
/**
* Create a new resource by POSTing the given object to the URI template,
* and asynchronously returns the response as {@link ResponseEntity}.
* <p>URI Template variables are expanded using the given URI variables, if any.
* @param url the URL
* @param request the Object to be POSTed, may be {@code null}
* @param uriVariables the variables to expand the template
* @return the entity wrapped in a {@link Future}
* @see org.springframework.http.HttpEntity
*/
<T> Future<ResponseEntity<T>> postForEntity(String url, HttpEntity<?> request,
Class<T> responseType, Object... uriVariables) throws RestClientException;
/**
* Create a new resource by POSTing the given object to the URI template,
* and asynchronously returns the response as {@link ResponseEntity}.
* <p>URI Template variables are expanded using the given map.
* @param url the URL
* @param request the Object to be POSTed, may be {@code null}
* @param uriVariables the variables to expand the template
* @return the entity wrapped in a {@link Future}
* @see org.springframework.http.HttpEntity
*/
<T> Future<ResponseEntity<T>> postForEntity(String url, HttpEntity<?> request,
Class<T> responseType, Map<String, ?> uriVariables)
throws RestClientException;
/**
* Create a new resource by POSTing the given object to the URL,
* and asynchronously returns the response as {@link ResponseEntity}.
* @param url the URL
* @param request the Object to be POSTed, may be {@code null}
* @return the entity wrapped in a {@link Future}
* @see org.springframework.http.HttpEntity
*/
<T> Future<ResponseEntity<T>> postForEntity(URI url, HttpEntity<?> request,
Class<T> responseType) throws RestClientException;
// PUT
/**
* Create or update a resource by PUTting the given object to the URI.
* <p>URI Template variables are expanded using the given URI variables, if any.
* @param url the URL
* @param request the Object to be PUT, may be {@code null}
* @param uriVariables the variables to expand the template
* @see HttpEntity
*/
Future<Void> put(String url, HttpEntity<?> request, Object... uriVariables)
throws RestClientException;
/**
* Creates a new resource by PUTting the given object to URI template.
* <p>URI Template variables are expanded using the given map.
* @param url the URL
* @param request the Object to be PUT, may be {@code null}
* @param uriVariables the variables to expand the template
* @see HttpEntity
*/
Future<Void> put(String url, HttpEntity<?> request, Map<String, ?> uriVariables)
throws RestClientException;
/**
* Creates a new resource by PUTting the given object to URL.
* @param url the URL
* @param request the Object to be PUT, may be {@code null}
* @see HttpEntity
*/
Future<Void> put(URI url, HttpEntity<?> request) throws RestClientException;
// DELETE
/**
* Asynchronously delete the resources at the specified URI.
* <p>URI Template variables are expanded using the given URI variables, if any.
* @param url the URL
* @param uriVariables the variables to expand in the template
*/
Future<Void> delete(String url, Object... uriVariables) throws RestClientException;
/**
* Asynchronously delete the resources at the specified URI.
* <p>URI Template variables are expanded using the given URI variables, if any.
* @param url the URL
* @param uriVariables the variables to expand in the template
*/
Future<Void> delete(String url, Map<String, ?> uriVariables) throws RestClientException;
/**
* Asynchronously delete the resources at the specified URI.
* <p>URI Template variables are expanded using the given URI variables, if any.
* @param url the URL
*/
Future<Void> delete(URI url) throws RestClientException;
// OPTIONS
/**
* Asynchronously return the value of the Allow header for the given URI.
* <p>URI Template variables are expanded using the given URI variables, if any.
* @param url the URL
* @param uriVariables the variables to expand in the template
* @return the value of the allow header wrapped in a {@link Future}
*/
Future<Set<HttpMethod>> optionsForAllow(String url, Object... uriVariables)
throws RestClientException;
/**
* Asynchronously return the value of the Allow header for the given URI.
* <p>URI Template variables are expanded using the given map.
* @param url the URL
* @param uriVariables the variables to expand in the template
* @return the value of the allow header wrapped in a {@link Future}
*/
Future<Set<HttpMethod>> optionsForAllow(String url, Map<String, ?> uriVariables)
throws RestClientException;
/**
* Asynchronously return the value of the Allow header for the given URL.
* @param url the URL
* @return the value of the allow header wrapped in a {@link Future}
*/
Future<Set<HttpMethod>> optionsForAllow(URI url) throws RestClientException;
// exchange
/**
* Asynchronously execute the HTTP method to the given URI template, writing the
* given request entity to the request, and returns the response as
* {@link ResponseEntity}.
* <p>URI Template variables are expanded using the given URI variables, if any.
* @param url the URL
* @param method the HTTP method (GET, POST, etc)
* @param requestEntity the entity (headers and/or body) to write to the request, may
* be {@code null}
* @param responseType the type of the return value
* @param uriVariables the variables to expand in the template
* @return the response as entity wrapped in a {@link Future}
*/
<T> Future<ResponseEntity<T>> exchange(String url, HttpMethod method,
HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables)
throws RestClientException;
/**
* Asynchronously execute the HTTP method to the given URI template, writing the
* given request entity to the request, and returns the response as
* {@link ResponseEntity}.
* <p>URI Template variables are expanded using the given URI variables, if any.
* @param url the URL
* @param method the HTTP method (GET, POST, etc)
* @param requestEntity the entity (headers and/or body) to write to the request, may
* be {@code null}
* @param responseType the type of the return value
* @param uriVariables the variables to expand in the template
* @return the response as entity wrapped in a {@link Future}
*/
<T> Future<ResponseEntity<T>> exchange(String url, HttpMethod method,
HttpEntity<?> requestEntity, Class<T> responseType,
Map<String, ?> uriVariables) throws RestClientException;
/**
* Asynchronously execute the HTTP method to the given URI template, writing the
* given request entity to the request, and returns the response as
* {@link ResponseEntity}.
* @param url the URL
* @param method the HTTP method (GET, POST, etc)
* @param requestEntity the entity (headers and/or body) to write to the request, may
* be {@code null}
* @param responseType the type of the return value
* @return the response as entity wrapped in a {@link Future}
*/
<T> Future<ResponseEntity<T>> exchange(URI url, HttpMethod method,
HttpEntity<?> requestEntity, Class<T> responseType)
throws RestClientException;
/**
* Asynchronously execute the HTTP method to the given URI template, writing the given
* request entity to the request, and returns the response as {@link ResponseEntity}.
* The given {@link ParameterizedTypeReference} is used to pass generic type
* information:
*
* <pre class="code">
* ParameterizedTypeReference&lt;List&lt;MyBean&gt;&gt; myBean = new ParameterizedTypeReference&lt;List&lt;MyBean&gt;&gt;() {};
* ResponseEntity&lt;List&lt;MyBean&gt;&gt; response = template.exchange(&quot;http://example.com&quot;,HttpMethod.GET, null, myBean);
* </pre>
*
* @param url the URL
* @param method the HTTP method (GET, POST, etc)
* @param requestEntity the entity (headers and/or body) to write to the
* request, may be {@code null}
* @param responseType the type of the return value
* @param uriVariables the variables to expand in the template
* @return the response as entity wrapped in a {@link Future}
*/
<T> Future<ResponseEntity<T>> exchange(String url, HttpMethod method,
HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType,
Object... uriVariables) throws RestClientException;
/**
* Asynchronously execute the HTTP method to the given URI template, writing the given
* request entity to the request, and returns the response as {@link ResponseEntity}.
* The given {@link ParameterizedTypeReference} is used to pass generic type
* information:
*
* <pre class="code">
* ParameterizedTypeReference&lt;List&lt;MyBean&gt;&gt; myBean = new ParameterizedTypeReference&lt;List&lt;MyBean&gt;&gt;() {};
* ResponseEntity&lt;List&lt;MyBean&gt;&gt; response = template.exchange(&quot;http://example.com&quot;,HttpMethod.GET, null, myBean);
* </pre>
*
* @param url the URL
* @param method the HTTP method (GET, POST, etc)
* @param requestEntity the entity (headers and/or body) to write to the request, may be {@code null}
* @param responseType the type of the return value
* @param uriVariables the variables to expand in the template
* @return the response as entity wrapped in a {@link Future}
*/
<T> Future<ResponseEntity<T>> exchange(String url, HttpMethod method,
HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType,
Map<String, ?> uriVariables) throws RestClientException;
/**
* Asynchronously execute the HTTP method to the given URI template, writing the given
* request entity to the request, and returns the response as {@link ResponseEntity}.
* The given {@link ParameterizedTypeReference} is used to pass generic type
* information:
*
* <pre class="code">
* ParameterizedTypeReference&lt;List&lt;MyBean&gt;&gt; myBean = new ParameterizedTypeReference&lt;List&lt;MyBean&gt;&gt;() {};
* ResponseEntity&lt;List&lt;MyBean&gt;&gt; response = template.exchange(&quot;http://example.com&quot;,HttpMethod.GET, null, myBean);
* </pre>
*
* @param url the URL
* @param method the HTTP method (GET, POST, etc)
* @param requestEntity the entity (headers and/or body) to write to the request, may be {@code null}
* @param responseType the type of the return value
* @return the response as entity wrapped in a {@link Future}
*/
<T> Future<ResponseEntity<T>> exchange(URI url, HttpMethod method,
HttpEntity<?> requestEntity, ParameterizedTypeReference<T> responseType)
throws RestClientException;
// general execution
/**
* Asynchronously execute the HTTP method to the given URI template, preparing the
* request with the {@link AsyncRequestCallback}, and reading the response with a
* {@link ResponseExtractor}.
* <p>URI Template variables are expanded using the given URI variables, if any.
* @param url the URL
* @param method the HTTP method (GET, POST, etc)
* @param requestCallback object that prepares the request
* @param responseExtractor object that extracts the return value from the response
* @param uriVariables the variables to expand in the template
* @return an arbitrary object, as returned by the {@link ResponseExtractor}
*/
<T> Future<T> execute(String url, HttpMethod method,
AsyncRequestCallback requestCallback, ResponseExtractor<T> responseExtractor,
Object... uriVariables) throws RestClientException;
/**
* Asynchronously execute the HTTP method to the given URI template, preparing the
* request with the {@link AsyncRequestCallback}, and reading the response with a
* {@link ResponseExtractor}.
* <p>URI Template variables are expanded using the given URI variables map.
* @param url the URL
* @param method the HTTP method (GET, POST, etc)
* @param requestCallback object that prepares the request
* @param responseExtractor object that extracts the return value from the response
* @param uriVariables the variables to expand in the template
* @return an arbitrary object, as returned by the {@link ResponseExtractor}
*/
<T> Future<T> execute(String url, HttpMethod method,
AsyncRequestCallback requestCallback, ResponseExtractor<T> responseExtractor,
Map<String, ?> uriVariables) throws RestClientException;
/**
* Asynchronously execute the HTTP method to the given URL, preparing the request
* with the {@link AsyncRequestCallback}, and reading the response with a
* {@link ResponseExtractor}.
* @param url the URL
* @param method the HTTP method (GET, POST, etc)
* @param requestCallback object that prepares the request
* @param responseExtractor object that extracts the return value from the response
* @return an arbitrary object, as returned by the {@link ResponseExtractor}
*/
<T> Future<T> execute(URI url, HttpMethod method,
AsyncRequestCallback requestCallback, ResponseExtractor<T> responseExtractor)
throws RestClientException;
}
......@@ -35,6 +35,7 @@ import org.springframework.http.ResponseEntity;
* @author Juergen Hoeller
* @since 3.0
* @see RestTemplate
* @see AsyncRestOperations
*/
public interface RestOperations {
......
......@@ -51,7 +51,7 @@ import org.springframework.util.ClassUtils;
import org.springframework.web.util.UriTemplate;
/**
* <strong>Spring's central class for client-side HTTP access.</strong>
* <strong>Spring's central class for synchronous client-side HTTP access.</strong>
* It simplifies communication with HTTP servers, and enforces RESTful principles.
* It handles HTTP connections, leaving application code to provide URLs
* (with possible template variables) and extract results.
......@@ -119,6 +119,7 @@ import org.springframework.web.util.UriTemplate;
* @see RequestCallback
* @see ResponseExtractor
* @see ResponseErrorHandler
* @see AsyncRestTemplate
*/
public class RestTemplate extends InterceptingHttpAccessor implements RestOperations {
......@@ -217,7 +218,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
@Override
public <T> T getForObject(String url, Class<T> responseType, Object... urlVariables) throws RestClientException {
AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(responseType);
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
return execute(url, HttpMethod.GET, requestCallback, responseExtractor, urlVariables);
......@@ -225,7 +226,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
@Override
public <T> T getForObject(String url, Class<T> responseType, Map<String, ?> urlVariables) throws RestClientException {
AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(responseType);
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
return execute(url, HttpMethod.GET, requestCallback, responseExtractor, urlVariables);
......@@ -233,7 +234,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
@Override
public <T> T getForObject(URI url, Class<T> responseType) throws RestClientException {
AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(responseType);
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
return execute(url, HttpMethod.GET, requestCallback, responseExtractor);
......@@ -242,26 +243,23 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
@Override
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Object... urlVariables)
throws RestClientException {
AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(responseType);
ResponseEntityResponseExtractor<T> responseExtractor =
new ResponseEntityResponseExtractor<T>(responseType);
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return execute(url, HttpMethod.GET, requestCallback, responseExtractor, urlVariables);
}
@Override
public <T> ResponseEntity<T> getForEntity(String url, Class<T> responseType, Map<String, ?> urlVariables)
throws RestClientException {
AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(responseType);
ResponseEntityResponseExtractor<T> responseExtractor =
new ResponseEntityResponseExtractor<T>(responseType);
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return execute(url, HttpMethod.GET, requestCallback, responseExtractor, urlVariables);
}
@Override
public <T> ResponseEntity<T> getForEntity(URI url, Class<T> responseType) throws RestClientException {
AcceptHeaderRequestCallback requestCallback = new AcceptHeaderRequestCallback(responseType);
ResponseEntityResponseExtractor<T> responseExtractor =
new ResponseEntityResponseExtractor<T>(responseType);
RequestCallback requestCallback = acceptHeaderRequestCallback(responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return execute(url, HttpMethod.GET, requestCallback, responseExtractor);
}
......@@ -269,24 +267,27 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
@Override
public HttpHeaders headForHeaders(String url, Object... urlVariables) throws RestClientException {
return execute(url, HttpMethod.HEAD, null, this.headersExtractor, urlVariables);
ResponseExtractor<HttpHeaders> headersExtractor = headersExtractor();
return execute(url, HttpMethod.HEAD, null, headersExtractor, urlVariables);
}
@Override
public HttpHeaders headForHeaders(String url, Map<String, ?> urlVariables) throws RestClientException {
return execute(url, HttpMethod.HEAD, null, this.headersExtractor, urlVariables);
ResponseExtractor<HttpHeaders> headersExtractor = headersExtractor();
return execute(url, HttpMethod.HEAD, null, headersExtractor, urlVariables);
}
@Override
public HttpHeaders headForHeaders(URI url) throws RestClientException {
return execute(url, HttpMethod.HEAD, null, this.headersExtractor);
ResponseExtractor<HttpHeaders> headersExtractor = headersExtractor();
return execute(url, HttpMethod.HEAD, null, headersExtractor);
}
// POST
@Override
public URI postForLocation(String url, Object request, Object... urlVariables) throws RestClientException {
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request);
RequestCallback requestCallback = httpEntityCallback(request);
HttpHeaders headers = execute(url, HttpMethod.POST, requestCallback, this.headersExtractor, urlVariables);
return headers.getLocation();
}
......@@ -294,14 +295,14 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
@Override
public URI postForLocation(String url, Object request, Map<String, ?> urlVariables)
throws RestClientException {
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request);
RequestCallback requestCallback = httpEntityCallback(request);
HttpHeaders headers = execute(url, HttpMethod.POST, requestCallback, this.headersExtractor, urlVariables);
return headers.getLocation();
}
@Override
public URI postForLocation(URI url, Object request) throws RestClientException {
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request);
RequestCallback requestCallback = httpEntityCallback(request);
HttpHeaders headers = execute(url, HttpMethod.POST, requestCallback, this.headersExtractor);
return headers.getLocation();
}
......@@ -309,7 +310,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
@Override
public <T> T postForObject(String url, Object request, Class<T> responseType, Object... uriVariables)
throws RestClientException {
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, responseType);
RequestCallback requestCallback = httpEntityCallback(request, responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
......@@ -318,7 +319,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
@Override
public <T> T postForObject(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)
throws RestClientException {
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, responseType);
RequestCallback requestCallback = httpEntityCallback(request, responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<T>(responseType, getMessageConverters(), logger);
return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
......@@ -326,7 +327,7 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
@Override
public <T> T postForObject(URI url, Object request, Class<T> responseType) throws RestClientException {
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, responseType);
RequestCallback requestCallback = httpEntityCallback(request, responseType);
HttpMessageConverterExtractor<T> responseExtractor =
new HttpMessageConverterExtractor<T>(responseType, getMessageConverters());
return execute(url, HttpMethod.POST, requestCallback, responseExtractor);
......@@ -336,9 +337,8 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
public <T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Object... uriVariables)
throws RestClientException {
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, responseType);
ResponseEntityResponseExtractor<T> responseExtractor =
new ResponseEntityResponseExtractor<T>(responseType);
RequestCallback requestCallback = httpEntityCallback(request, responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
}
......@@ -346,17 +346,15 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
public <T> ResponseEntity<T> postForEntity(String url, Object request, Class<T> responseType, Map<String, ?> uriVariables)
throws RestClientException {
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, responseType);
ResponseEntityResponseExtractor<T> responseExtractor =
new ResponseEntityResponseExtractor<T>(responseType);
RequestCallback requestCallback = httpEntityCallback(request, responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return execute(url, HttpMethod.POST, requestCallback, responseExtractor, uriVariables);
}
@Override
public <T> ResponseEntity<T> postForEntity(URI url, Object request, Class<T> responseType) throws RestClientException {
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request, responseType);
ResponseEntityResponseExtractor<T> responseExtractor =
new ResponseEntityResponseExtractor<T>(responseType);
RequestCallback requestCallback = httpEntityCallback(request, responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return execute(url, HttpMethod.POST, requestCallback, responseExtractor);
}
......@@ -364,19 +362,19 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
@Override
public void put(String url, Object request, Object... urlVariables) throws RestClientException {
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request);
RequestCallback requestCallback = httpEntityCallback(request);
execute(url, HttpMethod.PUT, requestCallback, null, urlVariables);
}
@Override
public void put(String url, Object request, Map<String, ?> urlVariables) throws RestClientException {
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request);
RequestCallback requestCallback = httpEntityCallback(request);
execute(url, HttpMethod.PUT, requestCallback, null, urlVariables);
}
@Override
public void put(URI url, Object request) throws RestClientException {
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(request);
RequestCallback requestCallback = httpEntityCallback(request);
execute(url, HttpMethod.PUT, requestCallback, null);
}
......@@ -401,19 +399,22 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
@Override
public Set<HttpMethod> optionsForAllow(String url, Object... urlVariables) throws RestClientException {
HttpHeaders headers = execute(url, HttpMethod.OPTIONS, null, this.headersExtractor, urlVariables);
ResponseExtractor<HttpHeaders> headersExtractor = headersExtractor();
HttpHeaders headers = execute(url, HttpMethod.OPTIONS, null, headersExtractor, urlVariables);
return headers.getAllow();
}
@Override
public Set<HttpMethod> optionsForAllow(String url, Map<String, ?> urlVariables) throws RestClientException {
HttpHeaders headers = execute(url, HttpMethod.OPTIONS, null, this.headersExtractor, urlVariables);
ResponseExtractor<HttpHeaders> headersExtractor = headersExtractor();
HttpHeaders headers = execute(url, HttpMethod.OPTIONS, null, headersExtractor, urlVariables);
return headers.getAllow();
}
@Override
public Set<HttpMethod> optionsForAllow(URI url) throws RestClientException {
HttpHeaders headers = execute(url, HttpMethod.OPTIONS, null, this.headersExtractor);
ResponseExtractor<HttpHeaders> headersExtractor = headersExtractor();
HttpHeaders headers = execute(url, HttpMethod.OPTIONS, null, headersExtractor);
return headers.getAllow();
}
......@@ -423,8 +424,8 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
public <T> ResponseEntity<T> exchange(String url, HttpMethod method,
HttpEntity<?> requestEntity, Class<T> responseType, Object... uriVariables) throws RestClientException {
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, responseType);
ResponseEntityResponseExtractor<T> responseExtractor = new ResponseEntityResponseExtractor<T>(responseType);
RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return execute(url, method, requestCallback, responseExtractor, uriVariables);
}
......@@ -432,8 +433,8 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
public <T> ResponseEntity<T> exchange(String url, HttpMethod method,
HttpEntity<?> requestEntity, Class<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, responseType);
ResponseEntityResponseExtractor<T> responseExtractor = new ResponseEntityResponseExtractor<T>(responseType);
RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return execute(url, method, requestCallback, responseExtractor, uriVariables);
}
......@@ -441,8 +442,8 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
public <T> ResponseEntity<T> exchange(URI url, HttpMethod method, HttpEntity<?> requestEntity,
Class<T> responseType) throws RestClientException {
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, responseType);
ResponseEntityResponseExtractor<T> responseExtractor = new ResponseEntityResponseExtractor<T>(responseType);
RequestCallback requestCallback = httpEntityCallback(requestEntity, responseType);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(responseType);
return execute(url, method, requestCallback, responseExtractor);
}
......@@ -451,8 +452,8 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
ParameterizedTypeReference<T> responseType, Object... uriVariables) throws RestClientException {
Type type = responseType.getType();
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, type);
ResponseEntityResponseExtractor<T> responseExtractor = new ResponseEntityResponseExtractor<T>(type);
RequestCallback requestCallback = httpEntityCallback(requestEntity, type);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(type);
return execute(url, method, requestCallback, responseExtractor, uriVariables);
}
......@@ -461,8 +462,8 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
ParameterizedTypeReference<T> responseType, Map<String, ?> uriVariables) throws RestClientException {
Type type = responseType.getType();
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, type);
ResponseEntityResponseExtractor<T> responseExtractor = new ResponseEntityResponseExtractor<T>(type);
RequestCallback requestCallback = httpEntityCallback(requestEntity, type);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(type);
return execute(url, method, requestCallback, responseExtractor, uriVariables);
}
......@@ -471,8 +472,8 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
ParameterizedTypeReference<T> responseType) throws RestClientException {
Type type = responseType.getType();
HttpEntityRequestCallback requestCallback = new HttpEntityRequestCallback(requestEntity, type);
ResponseEntityResponseExtractor<T> responseExtractor = new ResponseEntityResponseExtractor<T>(type);
RequestCallback requestCallback = httpEntityCallback(requestEntity, type);
ResponseExtractor<ResponseEntity<T>> responseExtractor = responseEntityExtractor(type);
return execute(url, method, requestCallback, responseExtractor);
}
......@@ -549,9 +550,9 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
private void logResponseStatus(HttpMethod method, URI url, ClientHttpResponse response) {
if (logger.isDebugEnabled()) {
try {
logger.debug(
method.name() + " request for \"" + url + "\" resulted in " + response.getStatusCode() + " (" +
response.getStatusText() + ")");
logger.debug(method.name() + " request for \"" + url + "\" resulted in " +
response.getStatusCode() + " (" +
response.getStatusText() + ")");
}
catch (IOException e) {
// ignore
......@@ -573,6 +574,46 @@ public class RestTemplate extends InterceptingHttpAccessor implements RestOperat
getErrorHandler().handleError(response);
}
/**
* Returns a request callback implementation that prepares the request {@code Accept}
* headers based on the given response type and configured
* {@linkplain #getMessageConverters() message converters}.
*/
protected <T> RequestCallback acceptHeaderRequestCallback(Class<T> responseType) {
return new AcceptHeaderRequestCallback(responseType);
}
/**
* Returns a request callback implementation that writes the given object to the
* request stream.
*/
protected <T> RequestCallback httpEntityCallback(Object requestBody) {
return new HttpEntityRequestCallback(requestBody);
}
/**
* Returns a request callback implementation that writes the given object to the
* request stream.
*/
protected <T> RequestCallback httpEntityCallback(Object requestBody,
Type responseType) {
return new HttpEntityRequestCallback(requestBody, responseType);
}
/**
* Returns a response extractor for {@link ResponseEntity}.
*/
protected <T> ResponseExtractor<ResponseEntity<T>> responseEntityExtractor(Type responseType) {
return new ResponseEntityResponseExtractor(responseType);
}
/**
* Returns a response extractor for {@link HttpHeaders}.
*/
protected ResponseExtractor<HttpHeaders> headersExtractor() {
return this.headersExtractor;
}
/**
* Request callback implementation that prepares the request's accept headers.
......
/*
* Copyright 2002-2013 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;
import java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.util.Arrays;
import java.util.Locale;
import java.util.concurrent.Future;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.StreamingHttpOutputMessage;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.StreamUtils;
public abstract class AbstractAsyncHttpRequestFactoryTestCase extends
AbstractJettyServerTestCase {
protected AsyncClientHttpRequestFactory factory;
@Before
public final void createFactory() throws Exception {
factory = createRequestFactory();
if (factory instanceof InitializingBean) {
((InitializingBean) factory).afterPropertiesSet();
}
}
protected abstract AsyncClientHttpRequestFactory createRequestFactory();
@Test
public void status() throws Exception {
URI uri = new URI(baseUrl + "/status/notfound");
AsyncClientHttpRequest request = factory.createAsyncRequest(uri, HttpMethod.GET);
assertEquals("Invalid HTTP method", HttpMethod.GET, request.getMethod());
assertEquals("Invalid HTTP URI", uri, request.getURI());
Future<ClientHttpResponse> futureResponse = request.executeAsync();
ClientHttpResponse response = futureResponse.get();
assertEquals("Invalid status code", HttpStatus.NOT_FOUND,
response.getStatusCode());
}
@Test
public void echo() throws Exception {
AsyncClientHttpRequest
request = factory.createAsyncRequest(new URI(baseUrl + "/echo"),
HttpMethod.PUT);
assertEquals("Invalid HTTP method", HttpMethod.PUT, request.getMethod());
String headerName = "MyHeader";
String headerValue1 = "value1";
request.getHeaders().add(headerName, headerValue1);
String headerValue2 = "value2";
request.getHeaders().add(headerName, headerValue2);
final byte[] body = "Hello World".getBytes("UTF-8");
request.getHeaders().setContentLength(body.length);
if (request instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingRequest =
(StreamingHttpOutputMessage) request;
streamingRequest.setBody(new StreamingHttpOutputMessage.Body() {
@Override
public void writeTo(OutputStream outputStream) throws IOException {
StreamUtils.copy(body, outputStream);
}
});
}
else {
StreamUtils.copy(body, request.getBody());
}
Future<ClientHttpResponse> futureResponse = request.executeAsync();
ClientHttpResponse response = futureResponse.get();
try {
assertEquals("Invalid status code", HttpStatus.OK, response.getStatusCode());
assertTrue("Header not found", response.getHeaders().containsKey(headerName));
assertEquals("Header value not found", Arrays.asList(headerValue1, headerValue2),
response.getHeaders().get(headerName));
byte[] result = FileCopyUtils.copyToByteArray(response.getBody());
assertTrue("Invalid body", Arrays.equals(body, result));
}
finally {
response.close();
}
}
@Test(expected = IllegalStateException.class)
public void multipleWrites() throws Exception {
AsyncClientHttpRequest
request = factory.createAsyncRequest(new URI(baseUrl + "/echo"),
HttpMethod.POST);
final byte[] body = "Hello World".getBytes("UTF-8");
if (request instanceof StreamingHttpOutputMessage) {
StreamingHttpOutputMessage streamingRequest =
(StreamingHttpOutputMessage) request;
streamingRequest.setBody(new StreamingHttpOutputMessage.Body() {
@Override
public void writeTo(OutputStream outputStream) throws IOException {
StreamUtils.copy(body, outputStream);
}
});
}
else {
StreamUtils.copy(body, request.getBody());
}
Future<ClientHttpResponse> futureResponse = request.executeAsync();
ClientHttpResponse response = futureResponse.get();
try {
FileCopyUtils.copy(body, request.getBody());
}
finally {
response.close();
}
}
@Test(expected = UnsupportedOperationException.class)
public void headersAfterExecute() throws Exception {
AsyncClientHttpRequest
request = factory.createAsyncRequest(new URI(baseUrl + "/echo"),
HttpMethod.POST);
request.getHeaders().add("MyHeader", "value");
byte[] body = "Hello World".getBytes("UTF-8");
FileCopyUtils.copy(body, request.getBody());
Future<ClientHttpResponse> futureResponse = request.executeAsync();
ClientHttpResponse response = futureResponse.get();
try {
request.getHeaders().add("MyHeader", "value");
}
finally {
response.close();
}
}
@Test
public void httpMethods() throws Exception {
assertHttpMethod("get", HttpMethod.GET);
assertHttpMethod("head", HttpMethod.HEAD);
assertHttpMethod("post", HttpMethod.POST);
assertHttpMethod("put", HttpMethod.PUT);
assertHttpMethod("options", HttpMethod.OPTIONS);
assertHttpMethod("delete", HttpMethod.DELETE);
}
protected void assertHttpMethod(String path, HttpMethod method) throws Exception {
ClientHttpResponse response = null;
try {
AsyncClientHttpRequest request = factory.createAsyncRequest(
new URI(baseUrl + "/methods/" + path), method);
Future<ClientHttpResponse> futureResponse = request.executeAsync();
response = futureResponse.get();
assertEquals("Invalid response status", HttpStatus.OK, response.getStatusCode());
assertEquals("Invalid method", path.toUpperCase(Locale.ENGLISH), request.getMethod().name());
}
finally {
if (response != null) {
response.close();
}
}
}
}
......@@ -17,70 +17,27 @@
package org.springframework.http.client;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Locale;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.StreamingHttpOutputMessage;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.SocketUtils;
import org.springframework.util.StreamUtils;
import static org.junit.Assert.*;
public abstract class AbstractHttpRequestFactoryTestCase {
/** @author Arjen Poutsma */
public abstract class AbstractHttpRequestFactoryTestCase extends
AbstractJettyServerTestCase {
protected ClientHttpRequestFactory factory;
protected static String baseUrl;
private static Server jettyServer;
@BeforeClass
public static void startJettyServer() throws Exception {
int port = SocketUtils.findAvailableTcpPort();
jettyServer = new Server(port);
baseUrl = "http://localhost:" + port;
ServletContextHandler handler = new ServletContextHandler();
handler.setContextPath("/");
handler.addServlet(new ServletHolder(new EchoServlet()), "/echo");
handler.addServlet(new ServletHolder(new EchoServlet()), "/echo");
handler.addServlet(new ServletHolder(new StatusServlet(200)), "/status/ok");
handler.addServlet(new ServletHolder(new StatusServlet(404)), "/status/notfound");
handler.addServlet(new ServletHolder(new MethodServlet("DELETE")), "/methods/delete");
handler.addServlet(new ServletHolder(new MethodServlet("GET")), "/methods/get");
handler.addServlet(new ServletHolder(new MethodServlet("HEAD")), "/methods/head");
handler.addServlet(new ServletHolder(new MethodServlet("OPTIONS")), "/methods/options");
handler.addServlet(new ServletHolder(new PostServlet()), "/methods/post");
handler.addServlet(new ServletHolder(new MethodServlet("PUT")), "/methods/put");
handler.addServlet(new ServletHolder(new MethodServlet("PATCH")), "/methods/patch");
jettyServer.setHandler(handler);
jettyServer.start();
}
@Before
public final void createFactory() {
factory = createRequestFactory();
......@@ -88,13 +45,6 @@ public abstract class AbstractHttpRequestFactoryTestCase {
protected abstract ClientHttpRequestFactory createRequestFactory();
@AfterClass
public static void stopJettyServer() throws Exception {
if (jettyServer != null) {
jettyServer.stop();
}
}
@Test
public void status() throws Exception {
URI uri = new URI(baseUrl + "/status/notfound");
......@@ -210,85 +160,4 @@ public abstract class AbstractHttpRequestFactoryTestCase {
}
}
/**
* Servlet that sets a given status code.
*/
@SuppressWarnings("serial")
private static class StatusServlet extends GenericServlet {
private final int sc;
private StatusServlet(int sc) {
this.sc = sc;
}
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
((HttpServletResponse) response).setStatus(sc);
}
}
@SuppressWarnings("serial")
private static class MethodServlet extends GenericServlet {
private final String method;
private MethodServlet(String method) {
this.method = method;
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest httpReq = (HttpServletRequest) req;
assertEquals("Invalid HTTP method", method, httpReq.getMethod());
res.setContentLength(0);
((HttpServletResponse) res).setStatus(200);
}
}
@SuppressWarnings("serial")
private static class PostServlet extends MethodServlet {
private PostServlet() {
super("POST");
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
super.service(req, res);
long contentLength = req.getContentLength();
if (contentLength != -1) {
InputStream in = req.getInputStream();
long byteCount = 0;
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
byteCount += bytesRead;
}
assertEquals("Invalid content-length", contentLength, byteCount);
}
}
}
@SuppressWarnings("serial")
private static class EchoServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
echo(req, resp);
}
private void echo(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setStatus(HttpServletResponse.SC_OK);
for (Enumeration e1 = request.getHeaderNames(); e1.hasMoreElements();) {
String headerName = (String) e1.nextElement();
for (Enumeration e2 = request.getHeaders(headerName); e2.hasMoreElements();) {
String headerValue = (String) e2.nextElement();
response.addHeader(headerName, headerValue);
}
}
FileCopyUtils.copy(request.getInputStream(), response.getOutputStream());
}
}
}
/*
* Copyright 2002-2013 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;
import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.AfterClass;
import static org.junit.Assert.assertEquals;
import org.junit.BeforeClass;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.SocketUtils;
/** @author Arjen Poutsma */
public class AbstractJettyServerTestCase {
protected static String baseUrl;
private static Server jettyServer;
@BeforeClass
public static void startJettyServer() throws Exception {
int port = SocketUtils.findAvailableTcpPort();
jettyServer = new Server(port);
baseUrl = "http://localhost:" + port;
ServletContextHandler handler = new ServletContextHandler();
handler.setContextPath("/");
handler.addServlet(new ServletHolder(new EchoServlet()), "/echo");
handler.addServlet(new ServletHolder(new EchoServlet()), "/echo");
handler.addServlet(new ServletHolder(new StatusServlet(200)), "/status/ok");
handler.addServlet(new ServletHolder(new StatusServlet(404)), "/status/notfound");
handler.addServlet(new ServletHolder(new MethodServlet("DELETE")), "/methods/delete");
handler.addServlet(new ServletHolder(new MethodServlet("GET")), "/methods/get");
handler.addServlet(new ServletHolder(new MethodServlet("HEAD")), "/methods/head");
handler.addServlet(new ServletHolder(new MethodServlet("OPTIONS")), "/methods/options");
handler.addServlet(new ServletHolder(new PostServlet()), "/methods/post");
handler.addServlet(new ServletHolder(new MethodServlet("PUT")), "/methods/put");
handler.addServlet(new ServletHolder(new MethodServlet("PATCH")), "/methods/patch");
jettyServer.setHandler(handler);
jettyServer.start();
}
@AfterClass
public static void stopJettyServer() throws Exception {
if (jettyServer != null) {
jettyServer.stop();
}
}
/**
* Servlet that sets a given status code.
*/
@SuppressWarnings("serial")
private static class StatusServlet extends GenericServlet {
private final int sc;
private StatusServlet(int sc) {
this.sc = sc;
}
@Override
public void service(ServletRequest request, ServletResponse response) throws
ServletException, IOException {
((HttpServletResponse) response).setStatus(sc);
}
}
@SuppressWarnings("serial")
private static class MethodServlet extends GenericServlet {
private final String method;
private MethodServlet(String method) {
this.method = method;
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest httpReq = (HttpServletRequest) req;
assertEquals("Invalid HTTP method", method, httpReq.getMethod());
res.setContentLength(0);
((HttpServletResponse) res).setStatus(200);
}
}
@SuppressWarnings("serial")
private static class PostServlet extends MethodServlet {
private PostServlet() {
super("POST");
}
@Override
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
super.service(req, res);
long contentLength = req.getContentLength();
if (contentLength != -1) {
InputStream in = req.getInputStream();
long byteCount = 0;
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
byteCount += bytesRead;
}
assertEquals("Invalid content-length", contentLength, byteCount);
}
}
}
@SuppressWarnings("serial")
private static class EchoServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
echo(req, resp);
}
private void echo(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.setStatus(HttpServletResponse.SC_OK);
for (Enumeration e1 = request.getHeaderNames(); e1.hasMoreElements();) {
String headerName = (String) e1.nextElement();
for (Enumeration e2 = request.getHeaders(headerName); e2.hasMoreElements();) {
String headerValue = (String) e2.nextElement();
response.addHeader(headerName, headerValue);
}
}
FileCopyUtils.copy(request.getInputStream(), response.getOutputStream());
}
}
}
/*
* Copyright 2002-2013 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;
import java.net.ProtocolException;
import org.junit.Test;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.http.HttpMethod;
public class BufferedSimpleAsyncHttpRequestFactoryTests extends AbstractAsyncHttpRequestFactoryTestCase {
@Override
protected AsyncClientHttpRequestFactory createRequestFactory() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
AsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor();
requestFactory.setTaskExecutor(taskExecutor);
return requestFactory;
}
@Override
@Test
public void httpMethods() throws Exception {
try {
assertHttpMethod("patch", HttpMethod.PATCH);
}
catch (ProtocolException ex) {
// Currently HttpURLConnection does not support HTTP PATCH
}
}
}
/*
* Copyright 2002-2012 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;
import org.junit.Test;
import org.springframework.http.HttpMethod;
/**
* @author Arjen Poutsma
*/
public class HttpComponentsAsyncClientHttpRequestFactoryTests extends AbstractAsyncHttpRequestFactoryTestCase {
@Override
protected AsyncClientHttpRequestFactory createRequestFactory() {
return new HttpComponentsAsyncClientHttpRequestFactory();
}
@Override
@Test
public void httpMethods() throws Exception {
assertHttpMethod("patch", HttpMethod.PATCH);
}
}
/*
* Copyright 2002-2013 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;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileUploadException;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.AfterClass;
import static org.junit.Assert.*;
import org.junit.BeforeClass;
import org.springframework.http.MediaType;
import org.springframework.util.FileCopyUtils;
import org.springframework.util.SocketUtils;
/** @author Arjen Poutsma */
public class AbstractJettyServerTestCase {
protected static String helloWorld = "H\u00e9llo W\u00f6rld";
protected static String baseUrl;
protected static MediaType contentType;
private static Server jettyServer;
@BeforeClass
public static void startJettyServer() throws Exception {
int port = SocketUtils.findAvailableTcpPort();
jettyServer = new Server(port);
baseUrl = "http://localhost:" + port;
ServletContextHandler handler = new ServletContextHandler();
byte[] bytes = helloWorld.getBytes("UTF-8");
contentType = new MediaType("text", "plain", Collections
.singletonMap("charset", "UTF-8"));
handler.addServlet(new ServletHolder(new GetServlet(bytes, contentType)), "/get");
handler.addServlet(new ServletHolder(new GetServlet(new byte[0], contentType)), "/get/nothing");
handler.addServlet(new ServletHolder(new GetServlet(bytes, null)), "/get/nocontenttype");
handler.addServlet(
new ServletHolder(new PostServlet(helloWorld, baseUrl + "/post/1", bytes, contentType)),
"/post");
handler.addServlet(new ServletHolder(new StatusCodeServlet(204)), "/status/nocontent");
handler.addServlet(new ServletHolder(new StatusCodeServlet(304)), "/status/notmodified");
handler.addServlet(new ServletHolder(new ErrorServlet(404)), "/status/notfound");
handler.addServlet(new ServletHolder(new ErrorServlet(500)), "/status/server");
handler.addServlet(new ServletHolder(new UriServlet()), "/uri/*");
handler.addServlet(new ServletHolder(new MultipartServlet()), "/multipart");
handler.addServlet(new ServletHolder(new DeleteServlet()), "/delete");
handler.addServlet(
new ServletHolder(new PutServlet(helloWorld, bytes, contentType)),
"/put");
jettyServer.setHandler(handler);
jettyServer.start();
}
@AfterClass
public static void stopJettyServer() throws Exception {
if (jettyServer != null) {
jettyServer.stop();
}
}
/** Servlet that sets the given status code. */
@SuppressWarnings("serial")
private static class StatusCodeServlet extends GenericServlet {
private final int sc;
private StatusCodeServlet(int sc) {
this.sc = sc;
}
@Override
public void service(ServletRequest request, ServletResponse response) throws
ServletException, IOException {
((HttpServletResponse) response).setStatus(sc);
}
}
/** Servlet that returns an error message for a given status code. */
@SuppressWarnings("serial")
private static class ErrorServlet extends GenericServlet {
private final int sc;
private ErrorServlet(int sc) {
this.sc = sc;
}
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
((HttpServletResponse) response).sendError(sc);
}
}
@SuppressWarnings("serial")
private static class GetServlet extends HttpServlet {
private final byte[] buf;
private final MediaType contentType;
private GetServlet(byte[] buf, MediaType contentType) {
this.buf = buf;
this.contentType = contentType;
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (contentType != null) {
response.setContentType(contentType.toString());
}
response.setContentLength(buf.length);
FileCopyUtils.copy(buf, response.getOutputStream());
}
}
@SuppressWarnings("serial")
private static class PostServlet extends HttpServlet {
private final String s;
private final String location;
private final byte[] buf;
private final MediaType contentType;
private PostServlet(String s, String location, byte[] buf, MediaType contentType) {
this.s = s;
this.location = location;
this.buf = buf;
this.contentType = contentType;
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
assertTrue("Invalid request content-length", request.getContentLength() > 0);
assertNotNull("No content-type", request.getContentType());
String body = FileCopyUtils.copyToString(request.getReader());
assertEquals("Invalid request body", s, body);
response.setStatus(HttpServletResponse.SC_CREATED);
response.setHeader("Location", location);
response.setContentLength(buf.length);
response.setContentType(contentType.toString());
FileCopyUtils.copy(buf, response.getOutputStream());
}
}
@SuppressWarnings("serial")
private static class PutServlet extends HttpServlet {
private final String s;
private final byte[] buf;
private final MediaType contentType;
private PutServlet(String s, byte[] buf, MediaType contentType) {
this.s = s;
this.buf = buf;
this.contentType = contentType;
}
@Override
protected void doPut(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
assertTrue("Invalid request content-length", request.getContentLength() > 0);
assertNotNull("No content-type", request.getContentType());
String body = FileCopyUtils.copyToString(request.getReader());
assertEquals("Invalid request body", s, body);
response.setStatus(HttpServletResponse.SC_ACCEPTED);
}
}
@SuppressWarnings("serial")
private static class UriServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/plain");
resp.setCharacterEncoding("UTF-8");
resp.getWriter().write(req.getRequestURI());
}
}
@SuppressWarnings("serial")
private static class MultipartServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
assertTrue(ServletFileUpload.isMultipartContent(req));
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
try {
List items = upload.parseRequest(req);
assertEquals(4, items.size());
FileItem item = (FileItem) items.get(0);
assertTrue(item.isFormField());
assertEquals("name 1", item.getFieldName());
assertEquals("value 1", item.getString());
item = (FileItem) items.get(1);
assertTrue(item.isFormField());
assertEquals("name 2", item.getFieldName());
assertEquals("value 2+1", item.getString());
item = (FileItem) items.get(2);
assertTrue(item.isFormField());
assertEquals("name 2", item.getFieldName());
assertEquals("value 2+2", item.getString());
item = (FileItem) items.get(3);
assertFalse(item.isFormField());
assertEquals("logo", item.getFieldName());
assertEquals("logo.jpg", item.getName());
assertEquals("image/jpeg", item.getContentType());
}
catch (FileUploadException ex) {
throw new ServletException(ex);
}
}
}
@SuppressWarnings("serial")
private static class DeleteServlet extends HttpServlet {
@Override
protected void doDelete(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
resp.setStatus(200);
}
}
}
/*
* Copyright 2002-2013 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;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.Charset;
import java.util.EnumSet;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.HttpComponentsAsyncClientHttpRequestFactory;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
/** @author Arjen Poutsma */
public class AsyncRestTemplateIntegrationTests extends AbstractJettyServerTestCase {
private AsyncRestTemplate template;
@Before
public void createTemplate() {
template = new AsyncRestTemplate(
new HttpComponentsAsyncClientHttpRequestFactory());
}
@Test
public void getEntity() throws ExecutionException, InterruptedException {
Future<ResponseEntity<String>>
futureEntity = template.getForEntity(baseUrl + "/{method}", String.class, "get");
ResponseEntity<String> entity = futureEntity.get();
assertEquals("Invalid content", helloWorld, entity.getBody());
assertFalse("No headers", entity.getHeaders().isEmpty());
assertEquals("Invalid content-type", contentType, entity.getHeaders().getContentType());
assertEquals("Invalid status code", HttpStatus.OK, entity.getStatusCode());
}
@Test
public void getNoResponse() throws ExecutionException, InterruptedException {
Future<ResponseEntity<String>>
futureEntity = template.getForEntity(baseUrl + "/get/nothing", String.class);
ResponseEntity<String> entity = futureEntity.get();
assertNull("Invalid content", entity.getBody());
}
@Test
public void getNoContentTypeHeader()
throws UnsupportedEncodingException, ExecutionException,
InterruptedException {
Future<ResponseEntity<byte[]>>
futureEntity = template.getForEntity(baseUrl + "/get/nocontenttype",
byte[].class);
ResponseEntity<byte[]> responseEntity = futureEntity.get();
assertArrayEquals("Invalid content", helloWorld.getBytes("UTF-8"),
responseEntity.getBody());
}
@Test
public void getNoContent() throws ExecutionException, InterruptedException {
Future<ResponseEntity<String>>
responseFuture = template.getForEntity(baseUrl + "/status/nocontent", String.class);
ResponseEntity<String> entity = responseFuture.get();
assertEquals("Invalid response code", HttpStatus.NO_CONTENT, entity.getStatusCode());
assertNull("Invalid content", entity.getBody());
}
@Test
public void getNotModified() throws ExecutionException, InterruptedException {
Future<ResponseEntity<String>>
responseFuture = template.getForEntity(baseUrl + "/status/notmodified",
String.class);
ResponseEntity<String> entity = responseFuture.get();
assertEquals("Invalid response code", HttpStatus.NOT_MODIFIED, entity.getStatusCode());
assertNull("Invalid content", entity.getBody());
}
@Test
public void headForHeaders() throws ExecutionException, InterruptedException {
Future<HttpHeaders> headersFuture = template.headForHeaders(baseUrl + "/get");
HttpHeaders headers = headersFuture.get();
assertTrue("No Content-Type header", headers.containsKey("Content-Type"));
}
@Test
public void postForLocation()
throws URISyntaxException, ExecutionException, InterruptedException {
HttpEntity<String> requestEntity = new HttpEntity<>(helloWorld);
Future<URI> locationFuture = template.postForLocation(baseUrl + "/{method}", requestEntity,
"post");
URI location = locationFuture.get();
assertEquals("Invalid location", new URI(baseUrl + "/post/1"), location);
}
@Test
public void postForLocationEntity()
throws URISyntaxException, ExecutionException, InterruptedException {
HttpHeaders entityHeaders = new HttpHeaders();
entityHeaders.setContentType(new MediaType("text", "plain", Charset.forName("ISO-8859-15")));
HttpEntity<String> entity = new HttpEntity<String>(helloWorld, entityHeaders);
Future<URI>
locationFuture = template.postForLocation(baseUrl + "/{method}", entity,
"post");
URI location = locationFuture.get();
assertEquals("Invalid location", new URI(baseUrl + "/post/1"), location);
}
@Test
public void postForEntity()
throws URISyntaxException, ExecutionException, InterruptedException {
HttpEntity<String> requestEntity = new HttpEntity<>(helloWorld);
Future<ResponseEntity<String>>
responseEntityFuture = template.postForEntity(baseUrl + "/{method}", requestEntity,
String.class, "post");
ResponseEntity<String> responseEntity = responseEntityFuture.get();
assertEquals("Invalid content", helloWorld, responseEntity.getBody());
}
@Test
public void put()
throws URISyntaxException, ExecutionException, InterruptedException {
HttpEntity<String> requestEntity = new HttpEntity<>(helloWorld);
Future<Void>
responseEntityFuture = template.put(baseUrl + "/{method}", requestEntity,
"put");
responseEntityFuture.get();
}
@Test
public void delete()
throws URISyntaxException, ExecutionException, InterruptedException {
Future<Void> deletedFuture = template.delete(new URI(baseUrl + "/delete"));
deletedFuture.get();
}
@Test
public void notFound() throws ExecutionException, InterruptedException {
try {
Future<Void> future = template.execute(baseUrl + "/status/notfound", HttpMethod.GET, null, null);
future.get();
fail("HttpClientErrorException expected");
}
catch (HttpClientErrorException ex) {
assertEquals(HttpStatus.NOT_FOUND, ex.getStatusCode());
assertNotNull(ex.getStatusText());
assertNotNull(ex.getResponseBodyAsString());
}
}
@Test
public void serverError() throws ExecutionException, InterruptedException {
try {
Future<Void> future = template.execute(baseUrl + "/status/server", HttpMethod.GET, null, null);
future.get();
fail("HttpServerErrorException expected");
}
catch (HttpServerErrorException ex) {
assertEquals(HttpStatus.INTERNAL_SERVER_ERROR, ex.getStatusCode());
assertNotNull(ex.getStatusText());
assertNotNull(ex.getResponseBodyAsString());
}
}
@Test
public void optionsForAllow()
throws URISyntaxException, ExecutionException, InterruptedException {
Future<Set<HttpMethod>>
allowedFuture = template.optionsForAllow(new URI(baseUrl + "/get"));
Set<HttpMethod> allowed = allowedFuture.get();
assertEquals("Invalid response",
EnumSet.of(HttpMethod.GET, HttpMethod.OPTIONS, HttpMethod.HEAD, HttpMethod.TRACE), allowed);
}
@Test
@SuppressWarnings("unchecked")
public void exchangeGet() throws Exception {
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("MyHeader", "MyValue");
HttpEntity<?> requestEntity = new HttpEntity(requestHeaders);
Future<ResponseEntity<String>> responseFuture =
template.exchange(baseUrl + "/{method}", HttpMethod.GET, requestEntity,
String.class, "get");
ResponseEntity<String> response = responseFuture.get();
assertEquals("Invalid content", helloWorld, response.getBody());
}
@Test
public void exchangePost() throws Exception {
HttpHeaders requestHeaders = new HttpHeaders();
requestHeaders.set("MyHeader", "MyValue");
requestHeaders.setContentType(MediaType.TEXT_PLAIN);
HttpEntity<String> requestEntity = new HttpEntity<String>(helloWorld, requestHeaders);
Future<ResponseEntity<Void>>
resultFuture = template.exchange(baseUrl + "/{method}", HttpMethod.POST,
requestEntity, Void.class, "post");
ResponseEntity<Void> result = resultFuture.get();
assertEquals("Invalid location", new URI(baseUrl + "/post/1"),
result.getHeaders().getLocation());
assertFalse(result.hasBody());
}
@Test
public void multipart() throws UnsupportedEncodingException, ExecutionException,
InterruptedException {
MultiValueMap<String, Object> parts = new LinkedMultiValueMap<String, Object>();
parts.add("name 1", "value 1");
parts.add("name 2", "value 2+1");
parts.add("name 2", "value 2+2");
Resource logo = new ClassPathResource("/org/springframework/http/converter/logo.jpg");
parts.add("logo", logo);
HttpEntity<MultiValueMap<String, Object>> requestBody = new HttpEntity<>(parts);
Future<URI> future =
template.postForLocation(baseUrl + "/multipart", requestBody);
future.get();
}
}
......@@ -63,54 +63,15 @@ import org.springframework.util.SocketUtils;
import static org.junit.Assert.*;
/** @author Arjen Poutsma */
public class RestTemplateIntegrationTests {
public class RestTemplateIntegrationTests extends AbstractJettyServerTestCase {
private RestTemplate template;
private static Server jettyServer;
private static String helloWorld = "H\u00e9llo W\u00f6rld";
private static String baseUrl;
private static MediaType contentType;
@BeforeClass
public static void startJettyServer() throws Exception {
int port = SocketUtils.findAvailableTcpPort();
jettyServer = new Server(port);
baseUrl = "http://localhost:" + port;
ServletContextHandler handler = new ServletContextHandler();
byte[] bytes = helloWorld.getBytes("UTF-8");
contentType = new MediaType("text", "plain", Collections.singletonMap("charset", "UTF-8"));
handler.addServlet(new ServletHolder(new GetServlet(bytes, contentType)), "/get");
handler.addServlet(new ServletHolder(new GetServlet(new byte[0], contentType)), "/get/nothing");
handler.addServlet(new ServletHolder(new GetServlet(bytes, null)), "/get/nocontenttype");
handler.addServlet(
new ServletHolder(new PostServlet(helloWorld, baseUrl + "/post/1", bytes, contentType)),
"/post");
handler.addServlet(new ServletHolder(new StatusCodeServlet(204)), "/status/nocontent");
handler.addServlet(new ServletHolder(new StatusCodeServlet(304)), "/status/notmodified");
handler.addServlet(new ServletHolder(new ErrorServlet(404)), "/status/notfound");
handler.addServlet(new ServletHolder(new ErrorServlet(500)), "/status/server");
handler.addServlet(new ServletHolder(new UriServlet()), "/uri/*");
handler.addServlet(new ServletHolder(new MultipartServlet()), "/multipart");
jettyServer.setHandler(handler);
jettyServer.start();
}
@Before
public void createTemplate() {
template = new RestTemplate(new HttpComponentsClientHttpRequestFactory());
}
@AfterClass
public static void stopJettyServer() throws Exception {
if (jettyServer != null) {
jettyServer.stop();
}
}
@Test
public void getString() {
String s = template.getForObject(baseUrl + "/{method}", String.class, "get");
......@@ -258,142 +219,4 @@ public class RestTemplateIntegrationTests {
assertFalse(result.hasBody());
}
/** Servlet that sets the given status code. */
@SuppressWarnings("serial")
private static class StatusCodeServlet extends GenericServlet {
private final int sc;
private StatusCodeServlet(int sc) {
this.sc = sc;
}
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
((HttpServletResponse) response).setStatus(sc);
}
}
/** Servlet that returns an error message for a given status code. */
@SuppressWarnings("serial")
private static class ErrorServlet extends GenericServlet {
private final int sc;
private ErrorServlet(int sc) {
this.sc = sc;
}
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
((HttpServletResponse) response).sendError(sc);
}
}
@SuppressWarnings("serial")
private static class GetServlet extends HttpServlet {
private final byte[] buf;
private final MediaType contentType;
private GetServlet(byte[] buf, MediaType contentType) {
this.buf = buf;
this.contentType = contentType;
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
if (contentType != null) {
response.setContentType(contentType.toString());
}
response.setContentLength(buf.length);
FileCopyUtils.copy(buf, response.getOutputStream());
}
}
@SuppressWarnings("serial")
private static class PostServlet extends HttpServlet {
private final String s;
private final String location;
private final byte[] buf;
private final MediaType contentType;
private PostServlet(String s, String location, byte[] buf, MediaType contentType) {
this.s = s;
this.location = location;
this.buf = buf;
this.contentType = contentType;
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
assertTrue("Invalid request content-length", request.getContentLength() > 0);
assertNotNull("No content-type", request.getContentType());
String body = FileCopyUtils.copyToString(request.getReader());
assertEquals("Invalid request body", s, body);
response.setStatus(HttpServletResponse.SC_CREATED);
response.setHeader("Location", location);
response.setContentLength(buf.length);
response.setContentType(contentType.toString());
FileCopyUtils.copy(buf, response.getOutputStream());
}
}
@SuppressWarnings("serial")
private static class UriServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/plain");
resp.setCharacterEncoding("UTF-8");
resp.getWriter().write(req.getRequestURI());
}
}
@SuppressWarnings("serial")
private static class MultipartServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
assertTrue(ServletFileUpload.isMultipartContent(req));
FileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
try {
List items = upload.parseRequest(req);
assertEquals(4, items.size());
FileItem item = (FileItem) items.get(0);
assertTrue(item.isFormField());
assertEquals("name 1", item.getFieldName());
assertEquals("value 1", item.getString());
item = (FileItem) items.get(1);
assertTrue(item.isFormField());
assertEquals("name 2", item.getFieldName());
assertEquals("value 2+1", item.getString());
item = (FileItem) items.get(2);
assertTrue(item.isFormField());
assertEquals("name 2", item.getFieldName());
assertEquals("value 2+2", item.getString());
item = (FileItem) items.get(3);
assertFalse(item.isFormField());
assertEquals("logo", item.getFieldName());
assertEquals("logo.jpg", item.getName());
assertEquals("image/jpeg", item.getContentType());
}
catch (FileUploadException ex) {
throw new ServletException(ex);
}
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册