Skip to content

Commit

Permalink
Introduced java 11 module with http2 client (#806)
Browse files Browse the repository at this point in the history
* Introduced java 11 module with http2 client
  • Loading branch information
velo committed Oct 13, 2018
1 parent 07a41b0 commit 6acbe94
Show file tree
Hide file tree
Showing 6 changed files with 333 additions and 1 deletion.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,7 @@ public class Example {
}
}
```

### OkHttp
[OkHttpClient](./okhttp) directs Feign's http requests to [OkHttp](http://square.github.io/okhttp/), which enables SPDY and better network control.

Expand Down Expand Up @@ -317,6 +318,17 @@ public class Example {
}
```

### Java 11 Http2
[Http2Client](./java11) directs Feign's http requests to Java11 [New HTTP/2 Client](http://www.javamagazine.mozaicreader.com/JulyAug2017#&pageSet=39&page=0) that implements HTTP/2.

To use New HTTP/2 Client with Feign, use Java SDK 11. Then, configure Feign to use the Http2Client:

```java
GitHub github = Feign.builder()
.client(new Http2Client())
.target(GitHub.class, "https://api.github.com");
```

### Hystrix
[HystrixFeign](./hystrix) configures circuit breaker support provided by [Hystrix](https://github.com/Netflix/Hystrix).

Expand Down
11 changes: 11 additions & 0 deletions java11/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# feign-java11

This module directs Feign's http requests to Java11 [New HTTP/2 Client](http://www.javamagazine.mozaicreader.com/JulyAug2017#&pageSet=39&page=0) that implements HTTP/2.

To use New HTTP/2 Client with Feign, use Java SDK 11. Then, configure Feign to use the Http2Client:

```java
GitHub github = Feign.builder()
.client(new Http2Client())
.target(GitHub.class, "https://api.github.com");
```
87 changes: 87 additions & 0 deletions java11/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright 2012-2018 The Feign 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>io.github.openfeign</groupId>
<artifactId>parent</artifactId>
<version>10.0.2-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>feign-java11</artifactId>
<name>Feign Java 11</name>
<description>Feign Java 11</description>

<properties>
<!-- override default bytecode version for src/main from parent pom -->
<main.java.version>11</main.java.version>
<main.signature.artifact>java18</main.signature.artifact>
<main.basedir>${project.basedir}/..</main.basedir>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>feign-core</artifactId>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>feign-jackson</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-core</artifactId>
<version>${project.version}</version>
<classifier>tests</classifier>
<type>jar</type>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>animal-sniffer-maven-plugin</artifactId>
<configuration>
<!-- skipping execution, as plugin is not able to handle java 11 -->
<skip>true</skip>
</configuration>
</plugin>

</plugins>
</build>
</project>
125 changes: 125 additions & 0 deletions java11/src/main/java/feign/httpclient/Http2Client.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/**
* Copyright 2012-2018 The Feign 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 feign.httpclient;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.http.HttpClient;
import java.net.http.HttpClient.Redirect;
import java.net.http.HttpRequest;
import java.net.http.HttpRequest.BodyPublisher;
import java.net.http.HttpRequest.BodyPublishers;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
import feign.Client;
import feign.Request;
import feign.Request.Options;
import feign.Response;

public class Http2Client implements Client {

@Override
public Response execute(Request request, Options options) throws IOException {
final HttpClient client = HttpClient.newBuilder()
.followRedirects(Redirect.ALWAYS)
.build();

URI uri;
try {
uri = new URI(request.url());
} catch (final URISyntaxException e) {
throw new IOException("Invalid uri " + request.url(), e);
}

final BodyPublisher body;
if (request.body() == null) {
body = BodyPublishers.noBody();
} else {
body = BodyPublishers.ofByteArray(request.body());
}

final HttpRequest httpRequest = HttpRequest.newBuilder()
.uri(uri)
.method(request.method(), body)
.headers(asString(filterRestrictedHeaders(request.headers())))
.build();

HttpResponse<byte[]> httpResponse;
try {
httpResponse = client.send(httpRequest, BodyHandlers.ofByteArray());
} catch (final InterruptedException e) {
throw new IOException("Invalid uri " + request.url(), e);
}

System.out.println(httpResponse.headers().map());

final OptionalLong length = httpResponse.headers().firstValueAsLong("Content-Length");

final Response response = Response.builder()
.body(new ByteArrayInputStream(httpResponse.body()),
length.isPresent() ? (int) length.getAsLong() : null)
.reason(httpResponse.headers().firstValue("Reason-Phrase").orElse(null))
.request(request)
.status(httpResponse.statusCode())
.headers(castMapCollectType(httpResponse.headers().map()))
.build();
return response;
}

/**
* There is a bunch o headers that the http2 client do not allow to be set.
*
* @see jdk.internal.net.http.common.Utils.DISALLOWED_HEADERS_SET
*/
private static final Set<String> DISALLOWED_HEADERS_SET;

static {
// A case insensitive TreeSet of strings.
final TreeSet<String> treeSet = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
treeSet.addAll(Set.of("connection", "content-length", "date", "expect", "from", "host",
"origin", "referer", "upgrade", "via", "warning"));
DISALLOWED_HEADERS_SET = Collections.unmodifiableSet(treeSet);
}

private Map<String, Collection<String>> filterRestrictedHeaders(Map<String, Collection<String>> headers) {
return headers.keySet()
.stream()
.filter(headerName -> !DISALLOWED_HEADERS_SET.contains(headerName))
.collect(Collectors.toMap(
Function.identity(),
headers::get));
}

private Map<String, Collection<String>> castMapCollectType(Map<String, List<String>> map) {
final Map<String, Collection<String>> result = new HashMap<>();
map.forEach((key, value) -> result.put(key, new HashSet<>(value)));
return result;
}

private String[] asString(Map<String, Collection<String>> headers) {
return headers.entrySet().stream()
.flatMap(entry -> entry.getValue()
.stream()
.map(value -> Arrays.asList(entry.getKey(), value))
.flatMap(List::stream))
.collect(Collectors.toList())
.toArray(new String[0]);
}

}
66 changes: 66 additions & 0 deletions java11/src/test/java/feign/httpclient/test/Http2ClientTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/**
* Copyright 2012-2018 The Feign 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 feign.httpclient.test;

import feign.*;
import feign.httpclient.Http2Client;
import org.assertj.core.api.Assertions;
import org.junit.Assert;
import org.junit.Test;

/**
* Tests client-specific behavior, such as ensuring Content-Length is sent when specified.
*/
public class Http2ClientTest {

public interface TestInterface {
@RequestLine("POST /?foo=bar&foo=baz&qux=")
@Headers({"Foo: Bar", "Foo: Baz", "Qux: ", "Content-Type: text/plain"})
Response post(String var1);

@RequestLine("POST /path/{to}/resource")
@Headers({"Accept: text/plain"})
Response post(@Param("to") String var1, String var2);

@RequestLine("GET /")
@Headers({"Accept: text/plain"})
String get();

@RequestLine("PATCH /patch")
@Headers({"Accept: text/plain"})
String patch(String var1);

@RequestLine("POST")
String noPostBody();

@RequestLine("PUT")
String noPutBody();

@RequestLine("POST /?foo=bar&foo=baz&qux=")
@Headers({"Foo: Bar", "Foo: Baz", "Qux: ", "Content-Type: {contentType}"})
Response postWithContentType(String var1, @Param("contentType") String var2);
}

@Test
public void testPatch() throws Exception {
TestInterface api = newBuilder().target(TestInterface.class, "https://nghttp2.org/httpbin/");
Assertions.assertThat(api.patch(""))
.contains("https://nghttp2.org/httpbin/patch");
}

public Feign.Builder newBuilder() {
return Feign.builder().client(new Http2Client());
}

}
33 changes: 32 additions & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@
<license-maven-plugin.version>3.0</license-maven-plugin.version>
<maven-jar-plugin.version>3.1.0</maven-jar-plugin.version>
<maven-release-plugin.version>2.5.3</maven-release-plugin.version>
<maven-bundle-plugin.version>3.2.0</maven-bundle-plugin.version>
<maven-bundle-plugin.version>4.0.0</maven-bundle-plugin.version>
<centralsync-maven-plugin.version>0.1.0</centralsync-maven-plugin.version>
<maven-surefire-plugin.version>2.22.0</maven-surefire-plugin.version>
</properties>
Expand Down Expand Up @@ -318,6 +318,14 @@
<redirectTestOutputToFile>true</redirectTestOutputToFile>
<trimStackTrace>false</trimStackTrace>
</configuration>
<dependencies>
<dependency>
<!-- surefire uses ASM to do some bytecode magic... need to bump version to be java 11 compatible -->
<groupId>org.ow2.asm</groupId>
<artifactId>asm</artifactId>
<version>7.0-beta</version>
</dependency>
</dependencies>
</plugin>
</plugins>
</pluginManagement>
Expand Down Expand Up @@ -475,6 +483,29 @@
</build>

<profiles>
<profile>
<id>java11</id>
<activation>
<jdk>11</jdk>
</activation>

<modules>
<module>java11</module>
</modules>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-release-plugin</artifactId>
<configuration>
<!-- so far we don't have a way to release using java 11 -->
<dryRun>true</dryRun>
</configuration>
</plugin>
</plugins>
</build>
</profile>

<profile>
<id>validateCodeFormat</id>
Expand Down

0 comments on commit 6acbe94

Please sign in to comment.