diff --git a/build.gradle b/build.gradle
index 184a82f9282e..f07a416ddc72 100644
--- a/build.gradle
+++ b/build.gradle
@@ -695,6 +695,7 @@ project("spring-web") {
optional("org.apache.httpcomponents:httpclient:${httpclientVersion}")
optional("org.apache.httpcomponents:httpasyncclient:${httpasyncVersion}")
optional("io.netty:netty-all:${nettyVersion}")
+ optional("com.squareup.okhttp:okhttp:2.3.0")
optional("com.fasterxml.jackson.core:jackson-databind:${jackson2Version}")
optional("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:${jackson2Version}")
optional("com.google.code.gson:gson:${gsonVersion}")
diff --git a/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpRequest.java
new file mode 100644
index 000000000000..9e14d12994b4
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpRequest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright 2002-2015 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.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+import com.squareup.okhttp.Call;
+import com.squareup.okhttp.Callback;
+import com.squareup.okhttp.MediaType;
+import com.squareup.okhttp.OkHttpClient;
+import com.squareup.okhttp.Request;
+import com.squareup.okhttp.RequestBody;
+import com.squareup.okhttp.Response;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.util.concurrent.ListenableFuture;
+import org.springframework.util.concurrent.SettableListenableFuture;
+
+/**
+ * {@link ClientHttpRequest} implementation that uses OkHttp to execute requests.
+ *
+ *
Created via the {@link OkHttpClientHttpRequestFactory}.
+ *
+ * @author Luciano Leggieri
+ * @author Arjen Poutsma
+ * @since 4.2
+ */
+class OkHttpClientHttpRequest extends AbstractBufferingAsyncClientHttpRequest
+ implements ClientHttpRequest {
+
+ private final OkHttpClient client;
+
+ private final URI uri;
+
+ private final HttpMethod method;
+
+
+ public OkHttpClientHttpRequest(OkHttpClient client, URI uri, HttpMethod method) {
+ this.client = client;
+ this.uri = uri;
+ this.method = method;
+ }
+
+
+ @Override
+ public HttpMethod getMethod() {
+ return method;
+ }
+
+ @Override
+ public URI getURI() {
+ return uri;
+ }
+
+ @Override
+ protected ListenableFuture executeInternal(HttpHeaders headers,
+ byte[] bufferedOutput) throws IOException {
+ RequestBody body = bufferedOutput.length > 0 ?
+ RequestBody.create(getContentType(headers), bufferedOutput) : null;
+
+ Request.Builder builder = new Request.Builder().
+ url(this.uri.toURL()).
+ method(this.method.name(), body);
+
+ for (Map.Entry> entry : headers.entrySet()) {
+ String headerName = entry.getKey();
+ for (String headerValue : entry.getValue()) {
+ builder.addHeader(headerName, headerValue);
+ }
+ }
+ Request request = builder.build();
+
+ return new ListenableFutureCall(client.newCall(request));
+ }
+
+ private MediaType getContentType(HttpHeaders headers) {
+ org.springframework.http.MediaType contentType = headers.getContentType();
+ return contentType != null ? MediaType.parse(contentType.toString()) : null;
+ }
+
+ @Override
+ public ClientHttpResponse execute() throws IOException {
+ try {
+ return executeAsync().get();
+ }
+ catch (InterruptedException ex) {
+ throw new IOException(ex.getMessage(), ex);
+ }
+ catch (ExecutionException ex) {
+ if (ex.getCause() instanceof IOException) {
+ throw (IOException) ex.getCause();
+ }
+ else {
+ throw new IOException(ex.getMessage(), ex);
+ }
+ }
+ }
+
+ private static class ListenableFutureCall extends
+ SettableListenableFuture {
+
+ private final Call call;
+
+ public ListenableFutureCall(Call call) {
+ this.call = call;
+ this.call.enqueue(new Callback() {
+ @Override
+ public void onResponse(Response response) throws IOException {
+ set(new OkHttpClientHttpResponse(response));
+ }
+
+ @Override
+ public void onFailure(Request request, IOException ex) {
+ setException(ex);
+ }
+ });
+ }
+
+ @Override
+ protected void interruptTask() {
+ call.cancel();
+ }
+ }
+
+}
diff --git a/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpRequestFactory.java b/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpRequestFactory.java
new file mode 100644
index 000000000000..c1f2b0b0532b
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpRequestFactory.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2002-2015 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.URI;
+import java.util.concurrent.TimeUnit;
+
+import com.squareup.okhttp.OkHttpClient;
+
+import org.springframework.beans.factory.DisposableBean;
+import org.springframework.http.HttpMethod;
+import org.springframework.util.Assert;
+
+/**
+ * {@link ClientHttpRequestFactory} implementation that uses
+ * OkHttp to create requests.
+ *
+ * @author Luciano Leggieri
+ * @author Arjen Poutsma
+ * @since 4.2
+ */
+public class OkHttpClientHttpRequestFactory
+ implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory,
+ DisposableBean {
+
+ private final OkHttpClient client;
+
+ private final boolean defaultClient;
+
+
+ /**
+ * Create a new {@code OkHttpClientHttpRequestFactory} with a default
+ * {@link OkHttpClient}.
+ */
+ public OkHttpClientHttpRequestFactory() {
+ client = new OkHttpClient();
+ defaultClient = true;
+ }
+
+ /**
+ * Create a new {@code OkHttpClientHttpRequestFactory} with the given
+ * {@link OkHttpClient}.
+ * @param okHttpClient the client to use
+ */
+ public OkHttpClientHttpRequestFactory(OkHttpClient okHttpClient) {
+ Assert.notNull(okHttpClient, "'okHttpClient' must not be null");
+ client = okHttpClient;
+ defaultClient = false;
+ }
+
+
+ /**
+ * Sets the underlying read timeout (in milliseconds).
+ * A timeout value of 0 specifies an infinite timeout.
+ * @see OkHttpClient#setReadTimeout(long, TimeUnit)
+ */
+ public void setReadTimeout(int readTimeout) {
+ this.client.setReadTimeout(readTimeout, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Sets the underlying write timeout (in milliseconds).
+ * A timeout value of 0 specifies an infinite timeout.
+ * @see OkHttpClient#setWriteTimeout(long, TimeUnit)
+ */
+ public void setWriteTimeout(int writeTimeout) {
+ this.client.setWriteTimeout(writeTimeout, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Sets the underlying connect timeout (in milliseconds).
+ * A timeout value of 0 specifies an infinite timeout.
+ * @see OkHttpClient#setConnectTimeout(long, TimeUnit)
+ */
+ public void setConnectTimeout(int connectTimeout) {
+ this.client.setConnectTimeout(connectTimeout, TimeUnit.MILLISECONDS);
+ }
+
+
+ @Override
+ public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) {
+ return createRequestInternal(uri, httpMethod);
+ }
+
+ @Override
+ public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) {
+ return createRequestInternal(uri, httpMethod);
+ }
+
+ private OkHttpClientHttpRequest createRequestInternal(URI uri, HttpMethod httpMethod) {
+ return new OkHttpClientHttpRequest(this.client, uri, httpMethod);
+ }
+
+ @Override
+ public void destroy() throws Exception {
+ if (defaultClient) {
+ // Clean up the client if we created it in the constructor
+ if (this.client.getCache() != null) {
+ this.client.getCache().close();
+ }
+ this.client.getDispatcher().getExecutorService().shutdown();
+ }
+ }
+}
diff --git a/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpResponse.java
new file mode 100644
index 000000000000..370d1fdde184
--- /dev/null
+++ b/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpResponse.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2002-2015 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.Map;
+
+import com.squareup.okhttp.Response;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.util.Assert;
+
+/**
+ * {@link org.springframework.http.client.ClientHttpResponse} implementation that uses
+ * OkHttp.
+ *
+ * @author Luciano Leggieri
+ * @author Arjen Poutsma
+ * @since 4.2
+ */
+class OkHttpClientHttpResponse extends AbstractClientHttpResponse {
+
+ private final Response response;
+
+ private HttpHeaders headers;
+
+
+ public OkHttpClientHttpResponse(Response response) {
+ Assert.notNull(response, "'response' must not be null");
+ this.response = response;
+ }
+
+
+ @Override
+ public int getRawStatusCode() {
+ return response.code();
+ }
+
+ @Override
+ public String getStatusText() {
+ return response.message();
+ }
+
+ @Override
+ public InputStream getBody() throws IOException {
+ return response.body().byteStream();
+ }
+
+ @Override
+ public HttpHeaders getHeaders() {
+ if (this.headers == null) {
+ HttpHeaders headers = new HttpHeaders();
+ for (String headerName : this.response.headers().names()) {
+ for (String headerValue : this.response.headers(headerName)) {
+ headers.add(headerName, headerValue);
+ }
+ }
+ this.headers = headers;
+ }
+ return this.headers;
+ }
+
+ @Override
+ public void close() {
+ try {
+ response.body().close();
+ }
+ catch (IOException ignored) {
+ }
+ }
+}
diff --git a/spring-web/src/test/java/org/springframework/http/client/BufferedSimpleAsyncHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/BufferedSimpleAsyncHttpRequestFactoryTests.java
index f79edea646f4..1b7d420ddfb9 100644
--- a/spring-web/src/test/java/org/springframework/http/client/BufferedSimpleAsyncHttpRequestFactoryTests.java
+++ b/spring-web/src/test/java/org/springframework/http/client/BufferedSimpleAsyncHttpRequestFactoryTests.java
@@ -37,6 +37,7 @@ protected AsyncClientHttpRequestFactory createRequestFactory() {
@Override
@Test
public void httpMethods() throws Exception {
+ super.httpMethods();
try {
assertHttpMethod("patch", HttpMethod.PATCH);
}
diff --git a/spring-web/src/test/java/org/springframework/http/client/HttpComponentsAsyncClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/HttpComponentsAsyncClientHttpRequestFactoryTests.java
index 1e5c6fbe4d01..79d1db92d624 100644
--- a/spring-web/src/test/java/org/springframework/http/client/HttpComponentsAsyncClientHttpRequestFactoryTests.java
+++ b/spring-web/src/test/java/org/springframework/http/client/HttpComponentsAsyncClientHttpRequestFactoryTests.java
@@ -34,6 +34,7 @@ protected AsyncClientHttpRequestFactory createRequestFactory() {
@Override
@Test
public void httpMethods() throws Exception {
+ super.httpMethods();
assertHttpMethod("patch", HttpMethod.PATCH);
}
diff --git a/spring-web/src/test/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactoryTests.java
index 0ad0024e6d91..7678a6a7adb9 100644
--- a/spring-web/src/test/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactoryTests.java
+++ b/spring-web/src/test/java/org/springframework/http/client/HttpComponentsClientHttpRequestFactoryTests.java
@@ -45,6 +45,7 @@ protected ClientHttpRequestFactory createRequestFactory() {
@Override
@Test
public void httpMethods() throws Exception {
+ super.httpMethods();
assertHttpMethod("patch", HttpMethod.PATCH);
}
diff --git a/spring-web/src/test/java/org/springframework/http/client/Netty4AsyncClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/Netty4AsyncClientHttpRequestFactoryTests.java
index 3ca6055ac8e0..a97f7b383788 100644
--- a/spring-web/src/test/java/org/springframework/http/client/Netty4AsyncClientHttpRequestFactoryTests.java
+++ b/spring-web/src/test/java/org/springframework/http/client/Netty4AsyncClientHttpRequestFactoryTests.java
@@ -50,6 +50,7 @@ protected AsyncClientHttpRequestFactory createRequestFactory() {
@Override
@Test
public void httpMethods() throws Exception {
+ super.httpMethods();
assertHttpMethod("patch", HttpMethod.PATCH);
}
diff --git a/spring-web/src/test/java/org/springframework/http/client/Netty4ClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/Netty4ClientHttpRequestFactoryTests.java
index 802d5f9fadfe..dd9c3c9c7691 100644
--- a/spring-web/src/test/java/org/springframework/http/client/Netty4ClientHttpRequestFactoryTests.java
+++ b/spring-web/src/test/java/org/springframework/http/client/Netty4ClientHttpRequestFactoryTests.java
@@ -50,6 +50,7 @@ protected ClientHttpRequestFactory createRequestFactory() {
@Override
@Test
public void httpMethods() throws Exception {
+ super.httpMethods();
assertHttpMethod("patch", HttpMethod.PATCH);
}
diff --git a/spring-web/src/test/java/org/springframework/http/client/OkHttpAsyncClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/OkHttpAsyncClientHttpRequestFactoryTests.java
new file mode 100644
index 000000000000..82ce0f939425
--- /dev/null
+++ b/spring-web/src/test/java/org/springframework/http/client/OkHttpAsyncClientHttpRequestFactoryTests.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2015 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 com.squareup.okhttp.OkHttpClient;
+import org.junit.Test;
+import org.springframework.http.HttpMethod;
+
+/**
+ * @author Luciano Leggieri
+ */
+public class OkHttpAsyncClientHttpRequestFactoryTests extends AbstractAsyncHttpRequestFactoryTestCase {
+
+ @Override
+ protected AsyncClientHttpRequestFactory createRequestFactory() {
+ return new OkHttpClientHttpRequestFactory();
+ }
+
+ @Override
+ @Test
+ public void httpMethods() throws Exception {
+ super.httpMethods();
+ assertHttpMethod("patch", HttpMethod.PATCH);
+ }
+
+}
diff --git a/spring-web/src/test/java/org/springframework/http/client/OkHttpClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/OkHttpClientHttpRequestFactoryTests.java
new file mode 100644
index 000000000000..32399d4c4fdc
--- /dev/null
+++ b/spring-web/src/test/java/org/springframework/http/client/OkHttpClientHttpRequestFactoryTests.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2002-2015 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 com.squareup.okhttp.OkHttpClient;
+import org.junit.Test;
+import org.springframework.http.HttpMethod;
+
+/**
+ * @author Luciano Leggieri
+ */
+public class OkHttpClientHttpRequestFactoryTests extends AbstractHttpRequestFactoryTestCase {
+
+ @Override
+ protected ClientHttpRequestFactory createRequestFactory() {
+ return new OkHttpClientHttpRequestFactory();
+ }
+
+ @Override
+ @Test
+ public void httpMethods() throws Exception {
+ assertHttpMethod("patch", HttpMethod.PATCH);
+ }
+
+}