Browse Source
This commit introduces support for OkHttp (http://square.github.io/okhttp/) as a backing implementation for ClientHttpRequestFactory and AsyncClientHttpRequestFactory. Issue: SPR-12893pull/773/merge
11 changed files with 433 additions and 0 deletions
@ -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(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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) { |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue