Skip to content

Support OkHttp as (Async)ClientHttpRequestFactory #800

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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}")
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>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<ClientHttpResponse> 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<String, List<String>> 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<ClientHttpResponse> {

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

}
Original file line number Diff line number Diff line change
@@ -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
* <a href="http://square.github.io/okhttp/">OkHttp</a> 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();
}
}
}
Original file line number Diff line number Diff line change
@@ -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) {
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ protected AsyncClientHttpRequestFactory createRequestFactory() {
@Override
@Test
public void httpMethods() throws Exception {
super.httpMethods();
try {
assertHttpMethod("patch", HttpMethod.PATCH);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ protected AsyncClientHttpRequestFactory createRequestFactory() {
@Override
@Test
public void httpMethods() throws Exception {
super.httpMethods();
assertHttpMethod("patch", HttpMethod.PATCH);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ protected ClientHttpRequestFactory createRequestFactory() {
@Override
@Test
public void httpMethods() throws Exception {
super.httpMethods();
assertHttpMethod("patch", HttpMethod.PATCH);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ protected AsyncClientHttpRequestFactory createRequestFactory() {
@Override
@Test
public void httpMethods() throws Exception {
super.httpMethods();
assertHttpMethod("patch", HttpMethod.PATCH);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ protected ClientHttpRequestFactory createRequestFactory() {
@Override
@Test
public void httpMethods() throws Exception {
super.httpMethods();
assertHttpMethod("patch", HttpMethod.PATCH);
}

Expand Down
Loading