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
This commit is contained in:
Arjen Poutsma
2013-07-01 13:54:42 +02:00
committed by Rossen Stoyanchev
parent 89b53cfcd5
commit ebcee26d57
28 changed files with 3220 additions and 409 deletions

View File

@@ -0,0 +1,183 @@
/*
* 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();
}
}
}
}

View File

@@ -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());
}
}
}

View File

@@ -0,0 +1,160 @@
/*
* 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());
}
}
}

View File

@@ -0,0 +1,48 @@
/*
* 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
}
}
}

View File

@@ -0,0 +1,40 @@
/*
* 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);
}
}

View File

@@ -0,0 +1,270 @@
/*
* 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);
}
}
}

View File

@@ -0,0 +1,247 @@
/*
* 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();
}
}

View File

@@ -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);
}
}
}
}