Skip to content

Commit 6acbe94

Browse files
authored
Introduced java 11 module with http2 client (OpenFeign#806)
* Introduced java 11 module with http2 client
1 parent 07a41b0 commit 6acbe94

6 files changed

Lines changed: 333 additions & 1 deletion

File tree

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,7 @@ public class Example {
288288
}
289289
}
290290
```
291+
291292
### OkHttp
292293
[OkHttpClient](./okhttp) directs Feign's http requests to [OkHttp](http://square.github.io/okhttp/), which enables SPDY and better network control.
293294

@@ -317,6 +318,17 @@ public class Example {
317318
}
318319
```
319320

321+
### Java 11 Http2
322+
[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.
323+
324+
To use New HTTP/2 Client with Feign, use Java SDK 11. Then, configure Feign to use the Http2Client:
325+
326+
```java
327+
GitHub github = Feign.builder()
328+
.client(new Http2Client())
329+
.target(GitHub.class, "https://api.github.com");
330+
```
331+
320332
### Hystrix
321333
[HystrixFeign](./hystrix) configures circuit breaker support provided by [Hystrix](https://github.com/Netflix/Hystrix).
322334

java11/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# feign-java11
2+
3+
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.
4+
5+
To use New HTTP/2 Client with Feign, use Java SDK 11. Then, configure Feign to use the Http2Client:
6+
7+
```java
8+
GitHub github = Feign.builder()
9+
.client(new Http2Client())
10+
.target(GitHub.class, "https://api.github.com");
11+
```

java11/pom.xml

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
4+
Copyright 2012-2018 The Feign Authors
5+
6+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
7+
in compliance with the License. You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software distributed under the License
12+
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
13+
or implied. See the License for the specific language governing permissions and limitations under
14+
the License.
15+
16+
-->
17+
<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">
18+
<parent>
19+
<groupId>io.github.openfeign</groupId>
20+
<artifactId>parent</artifactId>
21+
<version>10.0.2-SNAPSHOT</version>
22+
</parent>
23+
<modelVersion>4.0.0</modelVersion>
24+
25+
<artifactId>feign-java11</artifactId>
26+
<name>Feign Java 11</name>
27+
<description>Feign Java 11</description>
28+
29+
<properties>
30+
<!-- override default bytecode version for src/main from parent pom -->
31+
<main.java.version>11</main.java.version>
32+
<main.signature.artifact>java18</main.signature.artifact>
33+
<main.basedir>${project.basedir}/..</main.basedir>
34+
<maven.compiler.source>11</maven.compiler.source>
35+
<maven.compiler.target>11</maven.compiler.target>
36+
</properties>
37+
38+
<dependencies>
39+
<dependency>
40+
<groupId>${project.groupId}</groupId>
41+
<artifactId>feign-core</artifactId>
42+
</dependency>
43+
<dependency>
44+
<groupId>${project.groupId}</groupId>
45+
<artifactId>feign-jackson</artifactId>
46+
<scope>test</scope>
47+
</dependency>
48+
<dependency>
49+
<groupId>com.squareup.okhttp3</groupId>
50+
<artifactId>mockwebserver</artifactId>
51+
<scope>test</scope>
52+
</dependency>
53+
54+
<dependency>
55+
<groupId>org.assertj</groupId>
56+
<artifactId>assertj-core</artifactId>
57+
<scope>test</scope>
58+
</dependency>
59+
<dependency>
60+
<groupId>junit</groupId>
61+
<artifactId>junit</artifactId>
62+
<scope>test</scope>
63+
</dependency>
64+
<dependency>
65+
<groupId>io.github.openfeign</groupId>
66+
<artifactId>feign-core</artifactId>
67+
<version>${project.version}</version>
68+
<classifier>tests</classifier>
69+
<type>jar</type>
70+
<scope>test</scope>
71+
</dependency>
72+
</dependencies>
73+
74+
<build>
75+
<plugins>
76+
<plugin>
77+
<groupId>org.codehaus.mojo</groupId>
78+
<artifactId>animal-sniffer-maven-plugin</artifactId>
79+
<configuration>
80+
<!-- skipping execution, as plugin is not able to handle java 11 -->
81+
<skip>true</skip>
82+
</configuration>
83+
</plugin>
84+
85+
</plugins>
86+
</build>
87+
</project>
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/**
2+
* Copyright 2012-2018 The Feign Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package feign.httpclient;
15+
16+
import java.io.ByteArrayInputStream;
17+
import java.io.IOException;
18+
import java.net.URI;
19+
import java.net.URISyntaxException;
20+
import java.net.http.HttpClient;
21+
import java.net.http.HttpClient.Redirect;
22+
import java.net.http.HttpRequest;
23+
import java.net.http.HttpRequest.BodyPublisher;
24+
import java.net.http.HttpRequest.BodyPublishers;
25+
import java.net.http.HttpResponse;
26+
import java.net.http.HttpResponse.BodyHandlers;
27+
import java.util.*;
28+
import java.util.function.Function;
29+
import java.util.stream.Collectors;
30+
import feign.Client;
31+
import feign.Request;
32+
import feign.Request.Options;
33+
import feign.Response;
34+
35+
public class Http2Client implements Client {
36+
37+
@Override
38+
public Response execute(Request request, Options options) throws IOException {
39+
final HttpClient client = HttpClient.newBuilder()
40+
.followRedirects(Redirect.ALWAYS)
41+
.build();
42+
43+
URI uri;
44+
try {
45+
uri = new URI(request.url());
46+
} catch (final URISyntaxException e) {
47+
throw new IOException("Invalid uri " + request.url(), e);
48+
}
49+
50+
final BodyPublisher body;
51+
if (request.body() == null) {
52+
body = BodyPublishers.noBody();
53+
} else {
54+
body = BodyPublishers.ofByteArray(request.body());
55+
}
56+
57+
final HttpRequest httpRequest = HttpRequest.newBuilder()
58+
.uri(uri)
59+
.method(request.method(), body)
60+
.headers(asString(filterRestrictedHeaders(request.headers())))
61+
.build();
62+
63+
HttpResponse<byte[]> httpResponse;
64+
try {
65+
httpResponse = client.send(httpRequest, BodyHandlers.ofByteArray());
66+
} catch (final InterruptedException e) {
67+
throw new IOException("Invalid uri " + request.url(), e);
68+
}
69+
70+
System.out.println(httpResponse.headers().map());
71+
72+
final OptionalLong length = httpResponse.headers().firstValueAsLong("Content-Length");
73+
74+
final Response response = Response.builder()
75+
.body(new ByteArrayInputStream(httpResponse.body()),
76+
length.isPresent() ? (int) length.getAsLong() : null)
77+
.reason(httpResponse.headers().firstValue("Reason-Phrase").orElse(null))
78+
.request(request)
79+
.status(httpResponse.statusCode())
80+
.headers(castMapCollectType(httpResponse.headers().map()))
81+
.build();
82+
return response;
83+
}
84+
85+
/**
86+
* There is a bunch o headers that the http2 client do not allow to be set.
87+
*
88+
* @see jdk.internal.net.http.common.Utils.DISALLOWED_HEADERS_SET
89+
*/
90+
private static final Set<String> DISALLOWED_HEADERS_SET;
91+
92+
static {
93+
// A case insensitive TreeSet of strings.
94+
final TreeSet<String> treeSet = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
95+
treeSet.addAll(Set.of("connection", "content-length", "date", "expect", "from", "host",
96+
"origin", "referer", "upgrade", "via", "warning"));
97+
DISALLOWED_HEADERS_SET = Collections.unmodifiableSet(treeSet);
98+
}
99+
100+
private Map<String, Collection<String>> filterRestrictedHeaders(Map<String, Collection<String>> headers) {
101+
return headers.keySet()
102+
.stream()
103+
.filter(headerName -> !DISALLOWED_HEADERS_SET.contains(headerName))
104+
.collect(Collectors.toMap(
105+
Function.identity(),
106+
headers::get));
107+
}
108+
109+
private Map<String, Collection<String>> castMapCollectType(Map<String, List<String>> map) {
110+
final Map<String, Collection<String>> result = new HashMap<>();
111+
map.forEach((key, value) -> result.put(key, new HashSet<>(value)));
112+
return result;
113+
}
114+
115+
private String[] asString(Map<String, Collection<String>> headers) {
116+
return headers.entrySet().stream()
117+
.flatMap(entry -> entry.getValue()
118+
.stream()
119+
.map(value -> Arrays.asList(entry.getKey(), value))
120+
.flatMap(List::stream))
121+
.collect(Collectors.toList())
122+
.toArray(new String[0]);
123+
}
124+
125+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/**
2+
* Copyright 2012-2018 The Feign Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5+
* in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the License
10+
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11+
* or implied. See the License for the specific language governing permissions and limitations under
12+
* the License.
13+
*/
14+
package feign.httpclient.test;
15+
16+
import feign.*;
17+
import feign.httpclient.Http2Client;
18+
import org.assertj.core.api.Assertions;
19+
import org.junit.Assert;
20+
import org.junit.Test;
21+
22+
/**
23+
* Tests client-specific behavior, such as ensuring Content-Length is sent when specified.
24+
*/
25+
public class Http2ClientTest {
26+
27+
public interface TestInterface {
28+
@RequestLine("POST /?foo=bar&foo=baz&qux=")
29+
@Headers({"Foo: Bar", "Foo: Baz", "Qux: ", "Content-Type: text/plain"})
30+
Response post(String var1);
31+
32+
@RequestLine("POST /path/{to}/resource")
33+
@Headers({"Accept: text/plain"})
34+
Response post(@Param("to") String var1, String var2);
35+
36+
@RequestLine("GET /")
37+
@Headers({"Accept: text/plain"})
38+
String get();
39+
40+
@RequestLine("PATCH /patch")
41+
@Headers({"Accept: text/plain"})
42+
String patch(String var1);
43+
44+
@RequestLine("POST")
45+
String noPostBody();
46+
47+
@RequestLine("PUT")
48+
String noPutBody();
49+
50+
@RequestLine("POST /?foo=bar&foo=baz&qux=")
51+
@Headers({"Foo: Bar", "Foo: Baz", "Qux: ", "Content-Type: {contentType}"})
52+
Response postWithContentType(String var1, @Param("contentType") String var2);
53+
}
54+
55+
@Test
56+
public void testPatch() throws Exception {
57+
TestInterface api = newBuilder().target(TestInterface.class, "https://nghttp2.org/httpbin/");
58+
Assertions.assertThat(api.patch(""))
59+
.contains("https://nghttp2.org/httpbin/patch");
60+
}
61+
62+
public Feign.Builder newBuilder() {
63+
return Feign.builder().client(new Http2Client());
64+
}
65+
66+
}

pom.xml

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@
7474
<license-maven-plugin.version>3.0</license-maven-plugin.version>
7575
<maven-jar-plugin.version>3.1.0</maven-jar-plugin.version>
7676
<maven-release-plugin.version>2.5.3</maven-release-plugin.version>
77-
<maven-bundle-plugin.version>3.2.0</maven-bundle-plugin.version>
77+
<maven-bundle-plugin.version>4.0.0</maven-bundle-plugin.version>
7878
<centralsync-maven-plugin.version>0.1.0</centralsync-maven-plugin.version>
7979
<maven-surefire-plugin.version>2.22.0</maven-surefire-plugin.version>
8080
</properties>
@@ -318,6 +318,14 @@
318318
<redirectTestOutputToFile>true</redirectTestOutputToFile>
319319
<trimStackTrace>false</trimStackTrace>
320320
</configuration>
321+
<dependencies>
322+
<dependency>
323+
<!-- surefire uses ASM to do some bytecode magic... need to bump version to be java 11 compatible -->
324+
<groupId>org.ow2.asm</groupId>
325+
<artifactId>asm</artifactId>
326+
<version>7.0-beta</version>
327+
</dependency>
328+
</dependencies>
321329
</plugin>
322330
</plugins>
323331
</pluginManagement>
@@ -475,6 +483,29 @@
475483
</build>
476484

477485
<profiles>
486+
<profile>
487+
<id>java11</id>
488+
<activation>
489+
<jdk>11</jdk>
490+
</activation>
491+
492+
<modules>
493+
<module>java11</module>
494+
</modules>
495+
496+
<build>
497+
<plugins>
498+
<plugin>
499+
<groupId>org.apache.maven.plugins</groupId>
500+
<artifactId>maven-release-plugin</artifactId>
501+
<configuration>
502+
<!-- so far we don't have a way to release using java 11 -->
503+
<dryRun>true</dryRun>
504+
</configuration>
505+
</plugin>
506+
</plugins>
507+
</build>
508+
</profile>
478509

479510
<profile>
480511
<id>validateCodeFormat</id>

0 commit comments

Comments
 (0)