diff --git a/build.gradle b/build.gradle index 4e424f249a3c..cb4b2d05b2b3 100644 --- a/build.gradle +++ b/build.gradle @@ -704,6 +704,7 @@ project("spring-web") { optional("com.googlecode.protobuf-java-format:protobuf-java-format:1.2") optional("com.google.protobuf:protobuf-java:${protobufVersion}") optional("javax.mail:javax.mail-api:1.5.2") + optional("com.squareup.okhttp:okhttp:2.2.0") testCompile(project(":spring-context-support")) // for JafMediaTypeFactory testCompile("xmlunit:xmlunit:${xmlunitVersion}") testCompile("org.slf4j:slf4j-jcl:${slf4jVersion}") diff --git a/spring-web/src/main/java/org/springframework/http/client/ListenableFutureCall.java b/spring-web/src/main/java/org/springframework/http/client/ListenableFutureCall.java new file mode 100644 index 000000000000..d5acb06464a3 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/client/ListenableFutureCall.java @@ -0,0 +1,19 @@ +package org.springframework.http.client; + +import com.squareup.okhttp.Call; +import org.springframework.util.concurrent.SettableListenableFuture; + +public final class ListenableFutureCall extends SettableListenableFuture { + + private final Call call; + + public ListenableFutureCall(Call c) { + call = c; + call.enqueue(new OkHttpCallback2SettableFutureAdapter(this)); + } + + @Override + protected void interruptTask() { + call.cancel(); + } +} 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 new file mode 100644 index 000000000000..283076448db4 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/client/OkHttpAsyncClientHttpRequest.java @@ -0,0 +1,77 @@ +/* + * 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.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.StreamingHttpOutputMessage; +import org.springframework.util.concurrent.ListenableFuture; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; + +import static org.springframework.http.client.OkHttpRequestHelper.buildRequest; +import static org.springframework.http.client.OkHttpRequestHelper.getRequestBody; + +/** + * @author Luciano Leggieri + */ +public final class OkHttpAsyncClientHttpRequest extends AbstractAsyncClientHttpRequest + implements StreamingHttpOutputMessage { + + private final OkHttpClient client; + private final URI uri; + private final HttpMethod httpMethod; + private final OkHttpRequestBody body; + + public OkHttpAsyncClientHttpRequest(OkHttpClient okHttpClient, URI u, HttpMethod method) { + this.client = okHttpClient; + uri = u; + httpMethod = method; + body = new OkHttpRequestBody(); + } + + @Override + public HttpMethod getMethod() { + return httpMethod; + } + + @Override + public URI getURI() { + return uri; + } + + @Override + public void setBody(Body b) { + assertNotExecuted(); + body.setBody(b); + } + + @Override + protected OutputStream getBodyInternal(HttpHeaders ignored) { + return body.getAsBuffer(); + } + + @Override + protected ListenableFuture executeInternal(HttpHeaders headers) + throws IOException { + return new ListenableFutureCall( + client.newCall(buildRequest(httpMethod, uri, headers, getRequestBody(body)))); + } +} diff --git a/spring-web/src/main/java/org/springframework/http/client/OkHttpCallback2SettableFutureAdapter.java b/spring-web/src/main/java/org/springframework/http/client/OkHttpCallback2SettableFutureAdapter.java new file mode 100644 index 000000000000..0cda294747ae --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/client/OkHttpCallback2SettableFutureAdapter.java @@ -0,0 +1,30 @@ +package org.springframework.http.client; + +import com.squareup.okhttp.Callback; +import com.squareup.okhttp.Request; +import com.squareup.okhttp.Response; +import org.springframework.util.concurrent.SettableListenableFuture; + +import java.io.IOException; + +/** + * @author Luciano Leggieri + */ +public final class OkHttpCallback2SettableFutureAdapter implements Callback { + + private final SettableListenableFuture delegate; + + public OkHttpCallback2SettableFutureAdapter(SettableListenableFuture future) { + delegate = future; + } + + @Override + public void onFailure(Request request, IOException e) { + delegate.setException(e); + } + + @Override + public void onResponse(Response response) { + delegate.set(new OkHttpClientHttpResponse(response)); + } +} 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..6ff06e7df6ea --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpRequest.java @@ -0,0 +1,74 @@ +/* + * 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.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.StreamingHttpOutputMessage; + +import java.io.IOException; +import java.io.OutputStream; +import java.net.URI; + +import static org.springframework.http.client.OkHttpRequestHelper.buildRequest; +import static org.springframework.http.client.OkHttpRequestHelper.getRequestBody; + +/** + * @author Luciano Leggieri + */ +public final class OkHttpClientHttpRequest extends AbstractClientHttpRequest implements StreamingHttpOutputMessage { + + private final OkHttpClient client; + private final URI uri; + private final HttpMethod httpMethod; + private final OkHttpRequestBody body; + + public OkHttpClientHttpRequest(OkHttpClient okHttpClient, URI u, HttpMethod method) { + client = okHttpClient; + uri = u; + httpMethod = method; + body = new OkHttpRequestBody(); + } + + @Override + public HttpMethod getMethod() { + return httpMethod; + } + + @Override + public URI getURI() { + return uri; + } + + @Override + public void setBody(Body b) { + assertNotExecuted(); + body.setBody(b); + } + + @Override + protected OutputStream getBodyInternal(HttpHeaders ignored) { + return body.getAsBuffer(); + } + + @Override + protected ClientHttpResponse executeInternal(HttpHeaders headers) throws IOException { + return new OkHttpClientHttpResponse( + client.newCall(buildRequest(httpMethod, uri, headers, getRequestBody(body))).execute()); + } +} 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..d13725ed91dd --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpRequestFactory.java @@ -0,0 +1,65 @@ +/* + * 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.springframework.beans.factory.DisposableBean; +import org.springframework.http.HttpMethod; +import org.springframework.util.Assert; + +import java.net.URI; + +/** + * @author Luciano Leggieri + */ +public final class OkHttpClientHttpRequestFactory implements + ClientHttpRequestFactory, AsyncClientHttpRequestFactory, DisposableBean { + + private final OkHttpClient client; + private final boolean locallyBuilt; + + public OkHttpClientHttpRequestFactory() { + client = new OkHttpClient(); + locallyBuilt = true; + } + + public OkHttpClientHttpRequestFactory(OkHttpClient okHttpClient) { + Assert.notNull(okHttpClient, "'okHttpClient' must not be null"); + client = okHttpClient; + locallyBuilt = false; + } + + @Override + public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) { + return new OkHttpClientHttpRequest(client, uri, httpMethod); + } + + @Override + public AsyncClientHttpRequest createAsyncRequest(URI uri, HttpMethod httpMethod) { + return new OkHttpAsyncClientHttpRequest(client, uri, httpMethod); + } + + @Override + public void destroy() throws Exception { + if (locallyBuilt) { + 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..715425fe5570 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/client/OkHttpClientHttpResponse.java @@ -0,0 +1,70 @@ +/* + * 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.Response; +import org.springframework.http.HttpHeaders; + +import java.io.IOException; +import java.io.InputStream; + +/** + * @author Luciano Leggieri + */ +public final class OkHttpClientHttpResponse extends AbstractClientHttpResponse { + + private final Response response; + private final HttpHeaders httpHeaders; + + public OkHttpClientHttpResponse(Response r) { + response = r; + httpHeaders = new HttpHeaders(); + for (String key : response.headers().names()) { + for (String value : response.headers(key)) { + httpHeaders.add(key, value); + } + } + } + + @Override + public int getRawStatusCode() { + return response.code(); + } + + @Override + public String getStatusText() { + return response.message(); + } + + @Override + public void close() { + try { + response.body().close(); + } catch (IOException ignored) { + } + } + + @Override + public InputStream getBody() { + return response.body().byteStream(); + } + + @Override + public HttpHeaders getHeaders() { + return httpHeaders; + } +} diff --git a/spring-web/src/main/java/org/springframework/http/client/OkHttpRequestBody.java b/spring-web/src/main/java/org/springframework/http/client/OkHttpRequestBody.java new file mode 100644 index 000000000000..6d5d65ac3286 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/client/OkHttpRequestBody.java @@ -0,0 +1,64 @@ +/* + * 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.MediaType; +import com.squareup.okhttp.RequestBody; +import okio.Buffer; +import okio.BufferedSink; +import org.springframework.http.StreamingHttpOutputMessage.Body; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author Luciano Leggieri + */ +public final class OkHttpRequestBody extends RequestBody { + private final AtomicReference body = new AtomicReference(); + private final Buffer buffer = new Buffer(); + + public OutputStream getAsBuffer() { + return buffer.outputStream(); + } + + public void setBody(Body b) { + body.set(b); + buffer.clear(); + } + + @Override + public final MediaType contentType() { + return null; + } + + @Override + public void writeTo(BufferedSink sink) throws IOException { + Body b = body.getAndSet(null); + if (b != null) { + b.writeTo(sink.outputStream()); + } else { + sink.writeAll(buffer); + buffer.clear(); + } + } + + public boolean isEmpty() { + return (body.get() == null && buffer.size() == 0); + } +} diff --git a/spring-web/src/main/java/org/springframework/http/client/OkHttpRequestHelper.java b/spring-web/src/main/java/org/springframework/http/client/OkHttpRequestHelper.java new file mode 100644 index 000000000000..c33d86c9d006 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/http/client/OkHttpRequestHelper.java @@ -0,0 +1,49 @@ +/* + * 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.Request; +import com.squareup.okhttp.RequestBody; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; + +import java.net.MalformedURLException; +import java.net.URI; +import java.util.List; +import java.util.Map; + +/** + * @author Luciano Leggieri + */ +final class OkHttpRequestHelper { + public OkHttpRequestHelper() {} + + static Request buildRequest(HttpMethod method, URI uri, HttpHeaders headers, RequestBody body) + throws MalformedURLException { + Request.Builder builder = new Request.Builder().method(method.name(), body); + for (Map.Entry> entry : headers.entrySet()) { + for (String value : entry.getValue()) { + builder.addHeader(entry.getKey(), value); + } + } + return builder.url(uri.toURL()).build(); + } + + static OkHttpRequestBody getRequestBody(OkHttpRequestBody body) { + return body.isEmpty()?null:body; + } +} 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..b524d028130e 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..f586c46a2dc2 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..72e9a7b55fae 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..7b86e20ffa32 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..f1dcdddd4b76 --- /dev/null +++ b/spring-web/src/test/java/org/springframework/http/client/OkHttpAsyncClientHttpRequestFactoryTests.java @@ -0,0 +1,41 @@ +/* + * 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 { + + private final OkHttpClient client = new OkHttpClient(); + + @Override + protected AsyncClientHttpRequestFactory createRequestFactory() { + return new OkHttpClientHttpRequestFactory(client); + } + + @Override + @Test + public void httpMethods() throws Exception { + 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..0e927b8a3378 --- /dev/null +++ b/spring-web/src/test/java/org/springframework/http/client/OkHttpClientHttpRequestFactoryTests.java @@ -0,0 +1,41 @@ +/* + * 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 { + + private final OkHttpClient client = new OkHttpClient(); + + @Override + protected ClientHttpRequestFactory createRequestFactory() { + return new OkHttpClientHttpRequestFactory(client); + } + + @Override + @Test + public void httpMethods() throws Exception { + assertHttpMethod("patch", HttpMethod.PATCH); + } + +}