Skip to content

Commit 49b700e

Browse files
author
Adrian Cole
committed
Adds OkHttp integration
closes OpenFeign#134
1 parent 194d82f commit 49b700e

13 files changed

Lines changed: 281 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
### Version 7.1
22
* Introduces feign.@Param to annotate template parameters. Users must migrate from `javax.inject.@Named` to `feign.@Param` before updating to Feign 8.0.
3+
* Adds OkHttp integration
34
* Allows multiple headers with the same name.
45

56
### Version 7.0

README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,17 @@ GitHub github = Feign.builder()
150150
.contract(new JAXRSModule.JAXRSContract())
151151
.target(GitHub.class, "https://api.github.com");
152152
```
153+
### OkHttp
154+
[OkHttpClient](https://github.com/Netflix/feign/tree/master/okhttp) directs Feign's http requests to [OkHttp](http://square.github.io/okhttp/), which enables SPDY and better network control.
155+
156+
To use OkHttp with Feign, add the OkHttp module to your classpath. Then, configure Feign to use the OkHttpClient:
157+
158+
```java
159+
GitHub github = Feign.builder()
160+
.client(new OkHttpClient())
161+
.target(GitHub.class, "https://api.github.com");
162+
```
163+
153164
### Ribbon
154165
[RibbonModule](https://github.com/Netflix/feign/tree/master/ribbon) overrides URL resolution of Feign's client, adding smart routing and resiliency capabilities provided by [Ribbon](https://github.com/Netflix/ribbon).
155166

core/src/main/java/feign/Response.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ public static Response create(int status, String reason, Map<String, Collection<
5858
return new Response(status, reason, headers, ByteArrayBody.orNull(text, charset));
5959
}
6060

61+
public static Response create(int status, String reason, Map<String, Collection<String>> headers, Body body) {
62+
return new Response(status, reason, headers, body);
63+
}
64+
6165
private Response(int status, String reason, Map<String, Collection<String>> headers, Body body) {
6266
checkState(status >= 200, "Invalid status code: %s", status);
6367
this.status = status;

core/src/test/java/feign/codec/DefaultDecoderTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,6 @@ private Response knownResponse() {
7070
}
7171

7272
private Response nullBodyResponse() {
73-
return Response.create(200, "OK", Collections.<String, Collection<String>>emptyMap(), null);
73+
return Response.create(200, "OK", Collections.<String, Collection<String>>emptyMap(), (byte[]) null);
7474
}
7575
}

core/src/test/java/feign/codec/DefaultErrorDecoderTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public class DefaultErrorDecoderTest {
3939
thrown.expect(FeignException.class);
4040
thrown.expectMessage("status 500 reading Service#foo()");
4141

42-
Response response = Response.create(500, "Internal server error", headers, null);
42+
Response response = Response.create(500, "Internal server error", headers, (byte[]) null);
4343

4444
throw errorDecoder.decode("Service#foo()", response);
4545
}
@@ -58,7 +58,7 @@ public class DefaultErrorDecoderTest {
5858
thrown.expectMessage("status 503 reading Service#foo()");
5959

6060
headers.put(RETRY_AFTER, Arrays.asList("Sat, 1 Jan 2000 00:00:00 GMT"));
61-
Response response = Response.create(503, "Service Unavailable", headers, null);
61+
Response response = Response.create(503, "Service Unavailable", headers, (byte[]) null);
6262

6363
throw errorDecoder.decode("Service#foo()", response);
6464
}

gson/src/test/java/feign/gson/GsonModuleTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ static class DecoderBindings {
141141
DecoderBindings bindings = new DecoderBindings();
142142
ObjectGraph.create(bindings).inject(bindings);
143143

144-
Response response = Response.create(204, "OK", Collections.<String, Collection<String>>emptyMap(), null);
144+
Response response = Response.create(204, "OK", Collections.<String, Collection<String>>emptyMap(), (byte[]) null);
145145
assertNull(bindings.decoder.decode(response, String.class));
146146
}
147147

jackson/src/test/java/feign/jackson/JacksonModuleTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ static class DecoderBindings {
128128
DecoderBindings bindings = new DecoderBindings();
129129
ObjectGraph.create(bindings).inject(bindings);
130130

131-
Response response = Response.create(204, "OK", Collections.<String, Collection<String>>emptyMap(), null);
131+
Response response = Response.create(204, "OK", Collections.<String, Collection<String>>emptyMap(), (byte[]) null);
132132
assertNull(bindings.decoder.decode(response, String.class));
133133
}
134134

okhttp/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
OkHttp
2+
===================
3+
4+
This module directs Feign's http requests to [OkHttp](http://square.github.io/okhttp/), which enables SPDY and better network control.
5+
6+
To use OkHttp with Feign, add the OkHttp module to your classpath. Then, configure Feign to use the OkHttpClient:
7+
8+
```java
9+
GitHub github = Feign.builder()
10+
.client(new OkHttpClient())
11+
.target(GitHub.class, "https://api.github.com");
12+
```

okhttp/build.gradle

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
apply plugin: 'java'
2+
3+
sourceCompatibility = 1.6
4+
5+
dependencies {
6+
compile project(':feign-core')
7+
compile 'com.squareup.okhttp:okhttp:2.2.0'
8+
testCompile 'junit:junit:4.12'
9+
testCompile 'org.assertj:assertj-core:1.7.1'
10+
testCompile 'com.squareup.okhttp:mockwebserver:2.2.0'
11+
testCompile project(':feign-core').sourceSets.test.output // for assertions
12+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/*
2+
* Copyright 2015 Netflix, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package feign.okhttp;
17+
18+
import com.squareup.okhttp.Headers;
19+
import com.squareup.okhttp.MediaType;
20+
import com.squareup.okhttp.Request;
21+
import com.squareup.okhttp.RequestBody;
22+
import com.squareup.okhttp.Response;
23+
import com.squareup.okhttp.ResponseBody;
24+
import feign.Client;
25+
import java.io.IOException;
26+
import java.io.InputStream;
27+
import java.io.Reader;
28+
import java.util.Collection;
29+
import java.util.LinkedHashMap;
30+
import java.util.Map;
31+
import java.util.concurrent.TimeUnit;
32+
33+
/**
34+
* This module directs Feign's http requests to <a href="http://square.github.io/okhttp/">OkHttp</a>, which enables
35+
* SPDY and better network control.
36+
* Ex.
37+
* <pre>
38+
* GitHub github = Feign.builder().client(new OkHttpClient()).target(GitHub.class, "https://api.github.com");
39+
*/
40+
public final class OkHttpClient implements Client {
41+
private final com.squareup.okhttp.OkHttpClient delegate;
42+
43+
public OkHttpClient() {
44+
this(new com.squareup.okhttp.OkHttpClient());
45+
}
46+
47+
public OkHttpClient(com.squareup.okhttp.OkHttpClient delegate) {
48+
this.delegate = delegate;
49+
}
50+
51+
@Override public feign.Response execute(feign.Request input, feign.Request.Options options) throws IOException {
52+
com.squareup.okhttp.OkHttpClient requestScoped;
53+
if (delegate.getConnectTimeout() != options.connectTimeoutMillis()
54+
|| delegate.getReadTimeout() != options.readTimeoutMillis()) {
55+
requestScoped = delegate.clone();
56+
requestScoped.setConnectTimeout(options.connectTimeoutMillis(), TimeUnit.MILLISECONDS);
57+
requestScoped.setReadTimeout(options.readTimeoutMillis(), TimeUnit.MILLISECONDS);
58+
} else {
59+
requestScoped = delegate;
60+
}
61+
Request request = toOkHttpRequest(input);
62+
Response response = requestScoped.newCall(request).execute();
63+
return toFeignResponse(response);
64+
}
65+
66+
static Request toOkHttpRequest(feign.Request input) {
67+
Request.Builder requestBuilder = new Request.Builder();
68+
requestBuilder.url(input.url());
69+
70+
MediaType mediaType = null;
71+
for (String field : input.headers().keySet()) {
72+
for (String value : input.headers().get(field)) {
73+
if (field.equalsIgnoreCase("Content-Type")) {
74+
mediaType = MediaType.parse(value);
75+
if (input.charset() != null) mediaType.charset(input.charset());
76+
} else {
77+
requestBuilder.addHeader(field, value);
78+
}
79+
}
80+
}
81+
RequestBody body = input.body() != null ? RequestBody.create(mediaType, input.body()) : null;
82+
requestBuilder.method(input.method(), body);
83+
return requestBuilder.build();
84+
}
85+
86+
private static feign.Response toFeignResponse(Response input) {
87+
return feign.Response.create(input.code(), input.message(), toMap(input.headers()), toBody(input.body()));
88+
}
89+
90+
private static Map<String, Collection<String>> toMap(Headers headers) {
91+
Map<String, Collection<String>> result = new LinkedHashMap<String, Collection<String>>(headers.size());
92+
for (String name : headers.names()) {
93+
// TODO: this is very inefficient as headers.values iterate case insensitively.
94+
result.put(name, headers.values(name));
95+
}
96+
return result;
97+
}
98+
99+
private static feign.Response.Body toBody(final ResponseBody input) {
100+
if (input == null || input.contentLength() == 0) {
101+
return null;
102+
}
103+
if (input.contentLength() > Integer.MAX_VALUE) {
104+
throw new UnsupportedOperationException("Length too long "+ input.contentLength());
105+
}
106+
final Integer length = input.contentLength() != -1 ? (int) input.contentLength() : null;
107+
108+
return new feign.Response.Body() {
109+
110+
@Override public void close() throws IOException {
111+
input.close();
112+
}
113+
114+
@Override public Integer length() {
115+
return length;
116+
}
117+
118+
@Override public boolean isRepeatable() {
119+
return false;
120+
}
121+
122+
@Override public InputStream asInputStream() throws IOException {
123+
return input.byteStream();
124+
}
125+
126+
@Override public Reader asReader() throws IOException {
127+
return input.charStream();
128+
}
129+
};
130+
}
131+
}

0 commit comments

Comments
 (0)