Skip to content

Commit ebcee26

Browse files
Arjen Poutsmarstoyanchev
authored andcommitted
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
1 parent 89b53cf commit ebcee26

28 files changed

+3220
-409
lines changed

build.gradle

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,8 @@ project("spring-web") {
478478
optional("com.caucho:hessian:4.0.7")
479479
optional("rome:rome:1.0")
480480
optional("commons-fileupload:commons-fileupload:1.3")
481-
optional("org.apache.httpcomponents:httpclient:4.2")
481+
optional("org.apache.httpcomponents:httpclient:4.3-beta2")
482+
optional("org.apache.httpcomponents:httpasyncclient:4.0-beta4")
482483
optional("org.codehaus.jackson:jackson-mapper-asl:1.9.12")
483484
optional("com.fasterxml.jackson.core:jackson-databind:2.2.0")
484485
optional("taglibs:standard:1.1.2")
@@ -619,7 +620,7 @@ project("spring-webmvc") {
619620
testCompile("commons-fileupload:commons-fileupload:1.2")
620621
testCompile("commons-io:commons-io:1.3")
621622
testCompile("org.hibernate:hibernate-validator:4.3.0.Final")
622-
testCompile("org.apache.httpcomponents:httpclient:4.2")
623+
testCompile("org.apache.httpcomponents:httpclient:4.3-beta2")
623624
}
624625

625626
// pick up DispatcherServlet.properties in src/main
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright 2002-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.http.client;
18+
19+
import java.io.IOException;
20+
import java.io.OutputStream;
21+
import java.util.concurrent.Future;
22+
23+
import org.springframework.http.HttpHeaders;
24+
import org.springframework.util.Assert;
25+
26+
/**
27+
* Abstract base for {@link AsyncClientHttpRequest} that makes sure that headers and body
28+
* are not written multiple times.
29+
*
30+
* @author Arjen Poutsma
31+
* @since 4.0
32+
*/
33+
abstract class AbstractAsyncClientHttpRequest implements AsyncClientHttpRequest {
34+
35+
private final HttpHeaders headers = new HttpHeaders();
36+
37+
private boolean executed = false;
38+
39+
40+
@Override
41+
public final HttpHeaders getHeaders() {
42+
return (this.executed ? HttpHeaders.readOnlyHttpHeaders(this.headers) : this.headers);
43+
}
44+
45+
@Override
46+
public final OutputStream getBody() throws IOException {
47+
assertNotExecuted();
48+
return getBodyInternal(this.headers);
49+
}
50+
51+
@Override
52+
public Future<ClientHttpResponse> executeAsync() throws IOException {
53+
assertNotExecuted();
54+
Future<ClientHttpResponse> result = executeInternal(this.headers);
55+
this.executed = true;
56+
return result;
57+
}
58+
59+
/**
60+
* Asserts that this request has not been {@linkplain #execute() executed} yet.
61+
*
62+
* @throws IllegalStateException if this request has been executed
63+
*/
64+
protected void assertNotExecuted() {
65+
Assert.state(!this.executed, "ClientHttpRequest already executed");
66+
}
67+
68+
69+
/**
70+
* Abstract template method that returns the body.
71+
* @param headers the HTTP headers
72+
* @return the body output stream
73+
*/
74+
protected abstract OutputStream getBodyInternal(HttpHeaders headers) throws IOException;
75+
76+
/**
77+
* Abstract template method that writes the given headers and content to the HTTP request.
78+
* @param headers the HTTP headers
79+
* @return the response object for the executed request
80+
*/
81+
protected abstract Future<ClientHttpResponse> executeInternal(HttpHeaders headers) throws IOException;
82+
83+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2002-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.http.client;
18+
19+
import java.io.ByteArrayOutputStream;
20+
import java.io.IOException;
21+
import java.io.OutputStream;
22+
import java.util.concurrent.Future;
23+
24+
import org.springframework.http.HttpHeaders;
25+
26+
/**
27+
* Abstract base for {@link org.springframework.http.client.ClientHttpRequest} that buffers output in a byte array before sending it over the wire.
28+
*
29+
* @author Arjen Poutsma
30+
* @since 3.0.6
31+
*/
32+
abstract class AbstractBufferingAsyncClientHttpRequest
33+
extends AbstractAsyncClientHttpRequest {
34+
35+
private ByteArrayOutputStream bufferedOutput = new ByteArrayOutputStream();
36+
37+
@Override
38+
protected OutputStream getBodyInternal(HttpHeaders headers) throws IOException {
39+
return this.bufferedOutput;
40+
}
41+
42+
@Override
43+
protected Future<ClientHttpResponse> executeInternal(HttpHeaders headers) throws IOException {
44+
byte[] bytes = this.bufferedOutput.toByteArray();
45+
if (headers.getContentLength() == -1) {
46+
headers.setContentLength(bytes.length);
47+
}
48+
Future<ClientHttpResponse> result = executeInternal(headers, bytes);
49+
this.bufferedOutput = null;
50+
return result;
51+
}
52+
53+
/**
54+
* Abstract template method that writes the given headers and content to the HTTP
55+
* request.
56+
*
57+
* @param headers the HTTP headers
58+
* @param bufferedOutput the body content
59+
* @return the response object for the executed request
60+
*/
61+
protected abstract Future<ClientHttpResponse> executeInternal(HttpHeaders headers,
62+
byte[] bufferedOutput) throws IOException;
63+
64+
65+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2002-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.http.client;
18+
19+
import java.io.IOException;
20+
import java.util.concurrent.Future;
21+
22+
import org.springframework.http.HttpOutputMessage;
23+
import org.springframework.http.HttpRequest;
24+
25+
/**
26+
* Represents a client-side asynchronous HTTP request. Created via an implementation of
27+
* the {@link AsyncClientHttpRequestFactory}.
28+
* <p>A {@code AsyncHttpRequest} can be {@linkplain #executeAsync() executed}, getting a
29+
* future {@link ClientHttpResponse} which can be read from.
30+
*
31+
* @author Arjen Poutsma
32+
* @since 4.0
33+
* @see AsyncClientHttpRequestFactory#createAsyncRequest(java.net.URI, org.springframework.http.HttpMethod)
34+
*/
35+
public interface AsyncClientHttpRequest extends HttpRequest, HttpOutputMessage {
36+
37+
/**
38+
* Execute this request asynchronously, resulting in a future
39+
* {@link ClientHttpResponse} that can be read.
40+
*
41+
* @return the future response result of the execution
42+
* @throws java.io.IOException in case of I/O errors
43+
*/
44+
Future<ClientHttpResponse> executeAsync() throws IOException;
45+
46+
47+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*
2+
* Copyright 2002-2013 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.http.client;
18+
19+
import java.io.IOException;
20+
import java.net.URI;
21+
22+
import org.springframework.http.HttpMethod;
23+
24+
/**
25+
* Factory for {@link AsyncClientHttpRequest} objects. Requests are created by the
26+
* {@link #createAsyncRequest(URI, HttpMethod)} method.
27+
*
28+
* @author Arjen Poutsma
29+
* @since 4.0
30+
*/
31+
public interface AsyncClientHttpRequestFactory {
32+
33+
/**
34+
* Create a new asynchronous {@link AsyncClientHttpRequest} for the specified URI and
35+
* HTTP method.
36+
* <p>The returned request can be written to, and then executed by calling
37+
* {@link AsyncClientHttpRequest#executeAsync()}.
38+
*
39+
* @param uri the URI to create a request for
40+
* @param httpMethod the HTTP method to execute
41+
* @return the created request
42+
* @throws IOException in case of I/O errors
43+
*/
44+
AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod)
45+
throws IOException;
46+
47+
}

spring-web/src/main/java/org/springframework/http/client/ClientHttpRequest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@
2525
/**
2626
* Represents a client-side HTTP request. Created via an implementation of the {@link ClientHttpRequestFactory}.
2727
*
28-
* <p>A {@code HttpRequest} can be {@linkplain #execute() executed}, getting a {@link ClientHttpResponse}
29-
* which can be read from.
28+
* <p>A {@code ClientHttpRequest} can be {@linkplain #execute() executed}, getting a
29+
* {@link ClientHttpResponse} which can be read from.
3030
*
3131
* @author Arjen Poutsma
3232
* @since 3.0

0 commit comments

Comments
 (0)