-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
closes #134
- Loading branch information
Adrian Cole
committed
Jan 26, 2015
1 parent
4fb242c
commit a6724a4
Showing
13 changed files
with
299 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
OkHttp | ||
=================== | ||
|
||
This module directs Feign's http requests to [OkHttp](http://square.github.io/okhttp/), which enables SPDY and better network control. | ||
|
||
To use OkHttp with Feign, add the OkHttp module to your classpath. Then, configure Feign to use the OkHttpClient: | ||
|
||
```java | ||
GitHub github = Feign.builder() | ||
.client(new OkHttpClient()) | ||
.target(GitHub.class, "https://api.github.com"); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
apply plugin: 'java' | ||
|
||
sourceCompatibility = 1.6 | ||
|
||
dependencies { | ||
compile project(':feign-core') | ||
compile 'com.squareup.okhttp:okhttp:2.2.0' | ||
testCompile 'junit:junit:4.12' | ||
testCompile 'org.assertj:assertj-core:1.7.1' | ||
testCompile 'com.squareup.okhttp:mockwebserver:2.2.0' | ||
testCompile project(':feign-core').sourceSets.test.output // for assertions | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
/* | ||
* Copyright 2015 Netflix, Inc. | ||
* | ||
* 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 feign.okhttp; | ||
|
||
import com.squareup.okhttp.Headers; | ||
import com.squareup.okhttp.MediaType; | ||
import com.squareup.okhttp.Request; | ||
import com.squareup.okhttp.RequestBody; | ||
import com.squareup.okhttp.Response; | ||
import com.squareup.okhttp.ResponseBody; | ||
import feign.Client; | ||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.io.Reader; | ||
import java.util.Collection; | ||
import java.util.LinkedHashMap; | ||
import java.util.Map; | ||
import java.util.concurrent.TimeUnit; | ||
|
||
/** | ||
* This module directs Feign's http requests to <a href="http://square.github.io/okhttp/">OkHttp</a>, which enables | ||
* SPDY and better network control. | ||
* Ex. | ||
* <pre> | ||
* GitHub github = Feign.builder().client(new OkHttpClient()).target(GitHub.class, "https://api.github.com"); | ||
*/ | ||
public final class OkHttpClient implements Client { | ||
private final com.squareup.okhttp.OkHttpClient delegate; | ||
|
||
public OkHttpClient() { | ||
this(new com.squareup.okhttp.OkHttpClient()); | ||
} | ||
|
||
public OkHttpClient(com.squareup.okhttp.OkHttpClient delegate) { | ||
this.delegate = delegate; | ||
} | ||
|
||
@Override | ||
public feign.Response execute(feign.Request input, feign.Request.Options options) | ||
throws IOException { | ||
com.squareup.okhttp.OkHttpClient requestScoped; | ||
if (delegate.getConnectTimeout() != options.connectTimeoutMillis() | ||
|| delegate.getReadTimeout() != options.readTimeoutMillis()) { | ||
requestScoped = delegate.clone(); | ||
requestScoped.setConnectTimeout(options.connectTimeoutMillis(), TimeUnit.MILLISECONDS); | ||
requestScoped.setReadTimeout(options.readTimeoutMillis(), TimeUnit.MILLISECONDS); | ||
} else { | ||
requestScoped = delegate; | ||
} | ||
Request request = toOkHttpRequest(input); | ||
Response response = requestScoped.newCall(request).execute(); | ||
return toFeignResponse(response); | ||
} | ||
|
||
static Request toOkHttpRequest(feign.Request input) { | ||
Request.Builder requestBuilder = new Request.Builder(); | ||
requestBuilder.url(input.url()); | ||
|
||
MediaType mediaType = null; | ||
for (String field : input.headers().keySet()) { | ||
for (String value : input.headers().get(field)) { | ||
if (field.equalsIgnoreCase("Content-Type")) { | ||
mediaType = MediaType.parse(value); | ||
if (input.charset() != null) mediaType.charset(input.charset()); | ||
} else { | ||
requestBuilder.addHeader(field, value); | ||
} | ||
} | ||
} | ||
RequestBody body = input.body() != null ? RequestBody.create(mediaType, input.body()) : null; | ||
requestBuilder.method(input.method(), body); | ||
return requestBuilder.build(); | ||
} | ||
|
||
private static feign.Response toFeignResponse(Response input) { | ||
return feign.Response.create( | ||
input.code(), input.message(), toMap(input.headers()), toBody(input.body())); | ||
} | ||
|
||
private static Map<String, Collection<String>> toMap(Headers headers) { | ||
Map<String, Collection<String>> result = | ||
new LinkedHashMap<String, Collection<String>>(headers.size()); | ||
for (String name : headers.names()) { | ||
// TODO: this is very inefficient as headers.values iterate case insensitively. | ||
result.put(name, headers.values(name)); | ||
} | ||
return result; | ||
} | ||
|
||
private static feign.Response.Body toBody(final ResponseBody input) { | ||
if (input == null || input.contentLength() == 0) { | ||
return null; | ||
} | ||
if (input.contentLength() > Integer.MAX_VALUE) { | ||
throw new UnsupportedOperationException("Length too long " + input.contentLength()); | ||
} | ||
final Integer length = input.contentLength() != -1 ? (int) input.contentLength() : null; | ||
|
||
return new feign.Response.Body() { | ||
|
||
@Override | ||
public void close() throws IOException { | ||
input.close(); | ||
} | ||
|
||
@Override | ||
public Integer length() { | ||
return length; | ||
} | ||
|
||
@Override | ||
public boolean isRepeatable() { | ||
return false; | ||
} | ||
|
||
@Override | ||
public InputStream asInputStream() throws IOException { | ||
return input.byteStream(); | ||
} | ||
|
||
@Override | ||
public Reader asReader() throws IOException { | ||
return input.charStream(); | ||
} | ||
}; | ||
} | ||
} |
107 changes: 107 additions & 0 deletions
107
okhttp/src/test/java/feign/okhttp/OkHttpClientTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
/* | ||
* Copyright 2015 Netflix, Inc. | ||
* | ||
* 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 feign.okhttp; | ||
|
||
import static feign.Util.UTF_8; | ||
import static feign.assertj.MockWebServerAssertions.assertThat; | ||
import static java.util.Arrays.asList; | ||
import static org.junit.Assert.assertEquals; | ||
|
||
import com.squareup.okhttp.mockwebserver.MockResponse; | ||
import com.squareup.okhttp.mockwebserver.rule.MockWebServerRule; | ||
import feign.Feign; | ||
import feign.FeignException; | ||
import feign.Headers; | ||
import feign.RequestLine; | ||
import feign.Response; | ||
import java.io.ByteArrayInputStream; | ||
import java.io.IOException; | ||
import org.junit.Rule; | ||
import org.junit.Test; | ||
import org.junit.rules.ExpectedException; | ||
|
||
public class OkHttpClientTest { | ||
@Rule public final ExpectedException thrown = ExpectedException.none(); | ||
@Rule public final MockWebServerRule server = new MockWebServerRule(); | ||
|
||
interface TestInterface { | ||
@RequestLine("POST /?foo=bar&foo=baz&qux=") | ||
@Headers({"Foo: Bar", "Foo: Baz", "Qux: ", "Content-Type: text/plain"}) | ||
Response post(String body); | ||
|
||
@RequestLine("PATCH /") | ||
String patch(); | ||
} | ||
|
||
@Test | ||
public void parsesRequestAndResponse() throws IOException, InterruptedException { | ||
server.enqueue(new MockResponse().setBody("foo").addHeader("Foo: Bar")); | ||
|
||
TestInterface api = | ||
Feign.builder() | ||
.client(new OkHttpClient()) | ||
.target(TestInterface.class, "http://localhost:" + server.getPort()); | ||
|
||
Response response = api.post("foo"); | ||
|
||
assertThat(response.status()).isEqualTo(200); | ||
assertThat(response.reason()).isEqualTo("OK"); | ||
assertThat(response.headers()) | ||
.containsEntry("Content-Length", asList("3")) | ||
.containsEntry("Foo", asList("Bar")); | ||
assertThat(response.body().asInputStream()) | ||
.hasContentEqualTo(new ByteArrayInputStream("foo".getBytes(UTF_8))); | ||
|
||
assertThat(server.takeRequest()) | ||
.hasMethod("POST") | ||
.hasPath("/?foo=bar&foo=baz&qux=") | ||
.hasHeaders("Foo: Bar", "Foo: Baz", "Qux: ", "Content-Length: 3") | ||
.hasBody("foo"); | ||
} | ||
|
||
@Test | ||
public void parsesErrorResponse() throws IOException, InterruptedException { | ||
thrown.expect(FeignException.class); | ||
thrown.expectMessage("status 500 reading TestInterface#post(String); content:\n" + "ARGHH"); | ||
|
||
server.enqueue(new MockResponse().setResponseCode(500).setBody("ARGHH")); | ||
|
||
TestInterface api = | ||
Feign.builder() | ||
.client(new OkHttpClient()) | ||
.target(TestInterface.class, "http://localhost:" + server.getPort()); | ||
|
||
api.post("foo"); | ||
} | ||
|
||
@Test | ||
public void patch() throws IOException, InterruptedException { | ||
server.enqueue(new MockResponse().setBody("foo")); | ||
server.enqueue(new MockResponse()); | ||
|
||
TestInterface api = | ||
Feign.builder() | ||
.client(new OkHttpClient()) | ||
.target(TestInterface.class, "http://localhost:" + server.getPort()); | ||
|
||
assertEquals("foo", api.patch()); | ||
|
||
assertThat(server.takeRequest()) | ||
.hasHeaders("Content-Length: 0") // Note: OkHttp adds content length. | ||
.hasNoHeaderNamed("Content-Type") | ||
.hasMethod("PATCH"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters