diff --git a/build.gradle b/build.gradle index 29a356261d88..c1a31e2c17e1 100644 --- a/build.gradle +++ b/build.gradle @@ -64,6 +64,7 @@ configure(allprojects) { project -> ext.jtaVersion = "1.2" ext.junitVersion = "4.12" ext.nettyVersion = "4.0.34.Final" + ext.okhttp3Version = "3.2.0" ext.okhttpVersion = "2.7.4" ext.openjpaVersion = "2.4.0" ext.poiVersion = "3.13" @@ -712,6 +713,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.okhttp3:okhttp:${okhttp3Version}") optional("com.squareup.okhttp:okhttp:${okhttpVersion}") optional("com.fasterxml.jackson.core:jackson-databind:${jackson2Version}") optional("com.fasterxml.jackson.dataformat:jackson-dataformat-xml:${jackson2Version}") diff --git a/spring-web/src/main/java/org/springframework/http/client/OkHttp3AsyncClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/OkHttp3AsyncClientHttpRequest.java new file mode 100644 index 000000000000..b8e74cfaa310 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/client/OkHttp3AsyncClientHttpRequest.java @@ -0,0 +1,102 @@ +/* + * Copyright 2002-2016 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 okhttp3.Call; +import okhttp3.Callback; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.util.concurrent.ListenableFuture; +import org.springframework.util.concurrent.SettableListenableFuture; + +/** + * {@link AsyncClientHttpRequest} implementation that uses OkHttp to execute requests. + * + *

Created via the {@link OkHttpClientHttpRequestFactory}. + * + * @author Luciano Leggieri + * @author Arjen Poutsma + * @author Roy Clarkson + * @since 4.3 + */ +class OkHttp3AsyncClientHttpRequest extends AbstractBufferingAsyncClientHttpRequest { + + private final OkHttpClient client; + + private final URI uri; + + private final HttpMethod method; + + + public OkHttp3AsyncClientHttpRequest(OkHttpClient client, URI uri, HttpMethod method) { + this.client = client; + this.uri = uri; + this.method = method; + } + + + @Override + public HttpMethod getMethod() { + return this.method; + } + + @Override + public URI getURI() { + return this.uri; + } + + @Override + protected ListenableFuture executeInternal(HttpHeaders headers, byte[] content) + throws IOException { + + Request request = OkHttp3ClientHttpRequestFactory.buildRequest(headers, content, this.uri, this.method); + return new OkHttpListenableFuture(this.client.newCall(request)); + } + + + private static class OkHttpListenableFuture extends SettableListenableFuture { + + private final Call call; + + public OkHttpListenableFuture(Call call) { + this.call = call; + this.call.enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + set(new OkHttp3ClientHttpResponse(response)); + } + @Override + public void onFailure(Call call, IOException ex) { + setException(ex); + } + }); + } + + @Override + protected void interruptTask() { + this.call.cancel(); + } + } + +} diff --git a/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequest.java new file mode 100644 index 000000000000..e0ebaac3378d --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequest.java @@ -0,0 +1,71 @@ +/* + * Copyright 2002-2016 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 okhttp3.OkHttpClient; +import okhttp3.Request; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; + +/** + * {@link ClientHttpRequest} implementation that uses OkHttp 3.x to execute requests. + * + *

Created via the {@link OkHttp3ClientHttpRequestFactory}. + * + * @author Luciano Leggieri + * @author Arjen Poutsma + * @author Roy Clarkson + * @since 4.3 + */ +class OkHttp3ClientHttpRequest extends AbstractBufferingClientHttpRequest { + + private final OkHttpClient client; + + private final URI uri; + + private final HttpMethod method; + + + public OkHttp3ClientHttpRequest(OkHttpClient client, URI uri, HttpMethod method) { + this.client = client; + this.uri = uri; + this.method = method; + } + + + @Override + public HttpMethod getMethod() { + return this.method; + } + + @Override + public URI getURI() { + return this.uri; + } + + + @Override + protected ClientHttpResponse executeInternal(HttpHeaders headers, byte[] content) throws IOException { + Request request = OkHttp3ClientHttpRequestFactory.buildRequest(headers, content, this.uri, this.method); + return new OkHttp3ClientHttpResponse(this.client.newCall(request).execute()); + } + +} diff --git a/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequestFactory.java b/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequestFactory.java new file mode 100644 index 000000000000..546fde34d11b --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpRequestFactory.java @@ -0,0 +1,155 @@ +/* + * Copyright 2002-2016 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.MalformedURLException; +import java.net.URI; +import java.net.URL; +import java.util.List; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; + +import org.springframework.beans.factory.DisposableBean; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * {@link ClientHttpRequestFactory} implementation that uses + * OkHttp 3.x to create requests. + * + * @author Luciano Leggieri + * @author Arjen Poutsma + * @author Roy Clarkson + * @since 4.3 + */ +public class OkHttp3ClientHttpRequestFactory + implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory, DisposableBean { + + private OkHttpClient client; + + private final boolean defaultClient; + + + /** + * Create a factory with a default {@link OkHttpClient} instance. + */ + public OkHttp3ClientHttpRequestFactory() { + this.client = new OkHttpClient(); + this.defaultClient = true; + } + + /** + * Create a factory with the given {@link OkHttpClient} instance. + * @param client the client to use + */ + public OkHttp3ClientHttpRequestFactory(OkHttpClient client) { + Assert.notNull(client, "OkHttpClient must not be null"); + this.client = client; + this.defaultClient = false; + } + + + /** + * Sets the underlying read timeout in milliseconds. + * A value of 0 specifies an infinite timeout. + * @see okhttp3.OkHttpClient.Builder#readTimeout(long, TimeUnit) + */ + public void setReadTimeout(int readTimeout) { + this.client = this.client.newBuilder() + .readTimeout(readTimeout, TimeUnit.MILLISECONDS) + .build(); + } + + /** + * Sets the underlying write timeout in milliseconds. + * A value of 0 specifies an infinite timeout. + * @see okhttp3.OkHttpClient.Builder#writeTimeout(long, TimeUnit) + */ + public void setWriteTimeout(int writeTimeout) { + this.client = this.client.newBuilder() + .writeTimeout(writeTimeout, TimeUnit.MILLISECONDS) + .build(); + } + + /** + * Sets the underlying connect timeout in milliseconds. + * A value of 0 specifies an infinite timeout. + * @see okhttp3.OkHttpClient.Builder#connectTimeout(long, TimeUnit) + */ + public void setConnectTimeout(int connectTimeout) { + this.client = this.client.newBuilder() + .connectTimeout(connectTimeout, TimeUnit.MILLISECONDS) + .build(); + } + + + @Override + public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) { + return new OkHttp3ClientHttpRequest(this.client, uri, httpMethod); + } + + @Override + public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) { + return new OkHttp3AsyncClientHttpRequest(this.client, uri, httpMethod); + } + + + @Override + public void destroy() throws IOException { + if (this.defaultClient) { + // Clean up the client if we created it in the constructor + if (this.client.cache() != null) { + this.client.cache().close(); + } + this.client.dispatcher().executorService().shutdown(); + } + } + + + static Request buildRequest(HttpHeaders headers, byte[] content, URI uri, + HttpMethod method) throws MalformedURLException { + + okhttp3.MediaType contentType = getContentType(headers); + RequestBody body = (content.length > 0 ? RequestBody.create(contentType, content) : null); + + URL url = uri.toURL(); + String methodName = method.name(); + Request.Builder builder = new Request.Builder().url(url).method(methodName, body); + + for (Map.Entry> entry : headers.entrySet()) { + String headerName = entry.getKey(); + for (String headerValue : entry.getValue()) { + builder.addHeader(headerName, headerValue); + } + } + + return builder.build(); + } + + private static okhttp3.MediaType getContentType(HttpHeaders headers) { + String rawContentType = headers.getFirst(HttpHeaders.CONTENT_TYPE); + return (StringUtils.hasText(rawContentType) ? okhttp3.MediaType.parse(rawContentType) : null); + } + +} diff --git a/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpResponse.java b/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpResponse.java new file mode 100644 index 000000000000..b6a7ed19029c --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/client/OkHttp3ClientHttpResponse.java @@ -0,0 +1,82 @@ +/* + * Copyright 2002-2016 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 okhttp3.Response; + +import org.springframework.http.HttpHeaders; +import org.springframework.util.Assert; + +/** + * {@link ClientHttpResponse} implementation based on OkHttp 3.x. + * + * @author Luciano Leggieri + * @author Arjen Poutsma + * @author Roy Clarkson + * @since 4.3 + */ +class OkHttp3ClientHttpResponse extends AbstractClientHttpResponse { + + private final Response response; + + private HttpHeaders headers; + + + public OkHttp3ClientHttpResponse(Response response) { + Assert.notNull(response, "Response must not be null"); + this.response = response; + } + + + @Override + public int getRawStatusCode() { + return this.response.code(); + } + + @Override + public String getStatusText() { + return this.response.message(); + } + + @Override + public InputStream getBody() throws IOException { + return this.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() { + this.response.body().close(); + } + +} diff --git a/spring-web/src/main/java/org/springframework/http/client/OkHttpAsyncClientHttpRequest.java b/spring-web/src/main/java/org/springframework/http/client/OkHttpAsyncClientHttpRequest.java index 238495c2e776..6b0424226272 100644 --- a/spring-web/src/main/java/org/springframework/http/client/OkHttpAsyncClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/OkHttpAsyncClientHttpRequest.java @@ -31,13 +31,14 @@ import org.springframework.util.concurrent.SettableListenableFuture; /** - * {@link AsyncClientHttpRequest} implementation that uses OkHttp to execute requests. + * {@link AsyncClientHttpRequest} implementation that uses OkHttp 2.x to execute requests. * *

Created via the {@link OkHttpClientHttpRequestFactory}. * * @author Luciano Leggieri * @author Arjen Poutsma * @since 4.3 + * @see org.springframework.http.client.OkHttp3AsyncClientHttpRequest */ class OkHttpAsyncClientHttpRequest extends AbstractBufferingAsyncClientHttpRequest { 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 index 7c264f350d2f..a2f75be435a1 100644 --- a/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpRequest.java +++ b/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpRequest.java @@ -26,13 +26,14 @@ import org.springframework.http.HttpMethod; /** - * {@link ClientHttpRequest} implementation that uses OkHttp to execute requests. + * {@link ClientHttpRequest} implementation that uses OkHttp 2.x to execute requests. * *

Created via the {@link OkHttpClientHttpRequestFactory}. * * @author Luciano Leggieri * @author Arjen Poutsma * @since 4.2 + * @see org.springframework.http.client.OkHttp3ClientHttpRequest */ class OkHttpClientHttpRequest extends AbstractBufferingClientHttpRequest { 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 index 5148ebb0a7cb..d5ae9e97eee7 100644 --- a/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpRequestFactory.java +++ b/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpRequestFactory.java @@ -36,11 +36,12 @@ /** * {@link ClientHttpRequestFactory} implementation that uses - * OkHttp to create requests. + * OkHttp 2.x to create requests. * * @author Luciano Leggieri * @author Arjen Poutsma * @since 4.2 + * @see org.springframework.http.client.OkHttp3ClientHttpRequestFactory */ public class OkHttpClientHttpRequestFactory implements ClientHttpRequestFactory, AsyncClientHttpRequestFactory, DisposableBean { 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 index 392a2d7d9b1f..6a0639f536b6 100644 --- a/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpResponse.java +++ b/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpResponse.java @@ -25,11 +25,12 @@ import org.springframework.util.Assert; /** - * {@link ClientHttpResponse} implementation based on OkHttp. + * {@link ClientHttpResponse} implementation based on OkHttp 2.x. * * @author Luciano Leggieri * @author Arjen Poutsma * @since 4.2 + * @see org.springframework.http.client.OkHttp3ClientHttpResponse */ class OkHttpClientHttpResponse extends AbstractClientHttpResponse { diff --git a/spring-web/src/test/java/org/springframework/http/client/OkHttp3AsyncClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/OkHttp3AsyncClientHttpRequestFactoryTests.java new file mode 100644 index 000000000000..12e857e679e0 --- /dev/null +++ b/spring-web/src/test/java/org/springframework/http/client/OkHttp3AsyncClientHttpRequestFactoryTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2016 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 Roy Clarkson + */ +public class OkHttp3AsyncClientHttpRequestFactoryTests extends AbstractAsyncHttpRequestFactoryTestCase { + + @Override + protected AsyncClientHttpRequestFactory createRequestFactory() { + return new OkHttp3ClientHttpRequestFactory(); + } + + @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/OkHttp3ClientHttpRequestFactoryTests.java b/spring-web/src/test/java/org/springframework/http/client/OkHttp3ClientHttpRequestFactoryTests.java new file mode 100644 index 000000000000..dd1874adbbe0 --- /dev/null +++ b/spring-web/src/test/java/org/springframework/http/client/OkHttp3ClientHttpRequestFactoryTests.java @@ -0,0 +1,40 @@ +/* + * Copyright 2002-2016 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 Roy Clarkson + */ +public class OkHttp3ClientHttpRequestFactoryTests extends AbstractHttpRequestFactoryTestCase { + + @Override + protected ClientHttpRequestFactory createRequestFactory() { + return new OkHttp3ClientHttpRequestFactory(); + } + + @Override + @Test + public void httpMethods() throws Exception { + super.httpMethods(); + assertHttpMethod("patch", HttpMethod.PATCH); + } + +}