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