Skip to content

Commit e0f3e21

Browse files
committed
Added HTTP/2 enabled transport and made it default.
1 parent 07bbb98 commit e0f3e21

File tree

6 files changed

+386
-1
lines changed

6 files changed

+386
-1
lines changed

pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -455,6 +455,11 @@
455455
<artifactId>netty-transport</artifactId>
456456
<version>${netty.version}</version>
457457
</dependency>
458+
<dependency>
459+
<groupId>org.apache.httpcomponents.client5</groupId>
460+
<artifactId>httpclient5</artifactId>
461+
<version>5.3.1</version>
462+
</dependency>
458463

459464
<!-- Test Dependencies -->
460465
<dependency>
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package com.google.firebase.internal;
2+
3+
import java.io.ByteArrayOutputStream;
4+
import java.io.IOException;
5+
import java.nio.ByteBuffer;
6+
import java.util.Set;
7+
import java.util.concurrent.CompletableFuture;
8+
import java.util.concurrent.atomic.AtomicReference;
9+
10+
import org.apache.hc.core5.http.ContentType;
11+
import org.apache.hc.core5.http.nio.AsyncEntityProducer;
12+
import org.apache.hc.core5.http.nio.DataStreamChannel;
13+
14+
import com.google.api.client.util.StreamingContent;
15+
16+
@SuppressWarnings("deprecation")
17+
public class ApacheHttp2AsyncEntityProducer implements AsyncEntityProducer {
18+
private final ByteBuffer bytebuf;
19+
private ByteArrayOutputStream baos = new ByteArrayOutputStream();
20+
private final ContentType contentType;
21+
private final long contentLength;
22+
private final String contentEncoding;
23+
private final CompletableFuture<Void> writeFuture;
24+
private final AtomicReference<Exception> exception;
25+
26+
public ApacheHttp2AsyncEntityProducer(final StreamingContent content, final ContentType contentType, String contentEncoding, long contentLength, CompletableFuture<Void> writeFuture) {
27+
this.writeFuture = writeFuture;
28+
29+
try {
30+
content.writeTo(baos);
31+
} catch (IOException e) {
32+
writeFuture.completeExceptionally(e);
33+
}
34+
this.bytebuf = ByteBuffer.wrap(baos.toByteArray());
35+
this.contentType = contentType;
36+
this.contentLength = contentLength;
37+
this.contentEncoding = contentEncoding;
38+
this.exception = new AtomicReference<>();
39+
}
40+
41+
public ApacheHttp2AsyncEntityProducer(ApacheHttp2Request request, CompletableFuture<Void> writeFuture) {
42+
this(
43+
request.getStreamingContent(),
44+
ContentType.parse(request.getContentType()),
45+
request.getContentEncoding(),
46+
request.getContentLength(),
47+
writeFuture
48+
);
49+
}
50+
51+
@Override
52+
public boolean isRepeatable() {
53+
return false;
54+
}
55+
56+
@Override
57+
public String getContentType() {
58+
return contentType != null ? contentType.toString() : null;
59+
}
60+
61+
@Override
62+
public long getContentLength() {
63+
return contentLength;
64+
}
65+
66+
@Override
67+
public int available() {
68+
return Integer.MAX_VALUE;
69+
}
70+
71+
@Override
72+
public String getContentEncoding() {
73+
return contentEncoding;
74+
}
75+
76+
@Override
77+
public boolean isChunked() {
78+
return false;
79+
}
80+
81+
@Override
82+
public Set<String> getTrailerNames() {
83+
return null;
84+
}
85+
86+
@Override
87+
public void produce(DataStreamChannel channel) throws IOException {
88+
if (bytebuf.hasRemaining()) {
89+
channel.write(bytebuf);
90+
}
91+
if (!bytebuf.hasRemaining()) {
92+
channel.endStream();
93+
writeFuture.complete(null);
94+
}
95+
}
96+
97+
@Override
98+
public void failed(Exception cause) {
99+
if (exception.compareAndSet(null, cause)) {
100+
releaseResources();
101+
writeFuture.completeExceptionally(cause);
102+
}
103+
}
104+
105+
public final Exception getException() {
106+
return exception.get();
107+
}
108+
109+
@Override
110+
public void releaseResources() {
111+
bytebuf.clear();
112+
}
113+
}
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
package com.google.firebase.internal;
2+
3+
import com.google.api.client.http.LowLevelHttpRequest;
4+
import com.google.api.client.http.LowLevelHttpResponse;
5+
6+
import java.io.IOException;
7+
import java.util.concurrent.CompletableFuture;
8+
import java.util.concurrent.ExecutionException;
9+
import java.util.concurrent.TimeUnit;
10+
import java.util.concurrent.TimeoutException;
11+
12+
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
13+
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
14+
import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
15+
import org.apache.hc.client5.http.async.methods.SimpleResponseConsumer;
16+
import org.apache.hc.client5.http.config.RequestConfig;
17+
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
18+
import org.apache.hc.core5.concurrent.FutureCallback;
19+
import org.apache.hc.core5.http.nio.support.BasicRequestProducer;
20+
import org.apache.hc.core5.util.Timeout;
21+
22+
@SuppressWarnings("deprecation")
23+
final class ApacheHttp2Request extends LowLevelHttpRequest {
24+
private final CloseableHttpAsyncClient httpAsyncClient;
25+
private final SimpleRequestBuilder requestBuilder;
26+
private SimpleHttpRequest request;
27+
private final RequestConfig.Builder requestConfig;
28+
private int writeTimeout;
29+
30+
ApacheHttp2Request(
31+
CloseableHttpAsyncClient httpAsyncClient, SimpleRequestBuilder requestBuilder) {
32+
this.httpAsyncClient = httpAsyncClient;
33+
this.requestBuilder = requestBuilder;
34+
this.writeTimeout = 0;
35+
36+
this.requestConfig = RequestConfig.custom()
37+
.setRedirectsEnabled(false);
38+
}
39+
40+
@Override
41+
public void addHeader(String name, String value) {
42+
requestBuilder.addHeader(name, value);
43+
}
44+
45+
@Override
46+
public void setTimeout(int connectionTimeout, int readTimeout) throws IOException {
47+
requestConfig
48+
.setConnectTimeout(Timeout.ofMilliseconds(connectionTimeout))
49+
.setResponseTimeout(Timeout.ofMilliseconds(readTimeout));
50+
}
51+
52+
@Override
53+
public void setWriteTimeout(int writeTimeout) throws IOException {
54+
this.writeTimeout = writeTimeout;
55+
}
56+
57+
@Override
58+
public LowLevelHttpResponse execute() throws IOException {
59+
// Set request configs
60+
requestBuilder.setRequestConfig(requestConfig.build());
61+
62+
// Build request
63+
request = requestBuilder.build();
64+
65+
// Make Producer
66+
CompletableFuture<Void> writeFuture = new CompletableFuture<>();
67+
ApacheHttp2AsyncEntityProducer entityProducer = new ApacheHttp2AsyncEntityProducer(this, writeFuture);
68+
69+
// Execute
70+
final CompletableFuture<SimpleHttpResponse> responseFuture = new CompletableFuture<>();
71+
try {
72+
httpAsyncClient.execute(
73+
new BasicRequestProducer(request, entityProducer),
74+
SimpleResponseConsumer.create(),
75+
new FutureCallback<SimpleHttpResponse>() {
76+
@Override
77+
public void completed(final SimpleHttpResponse response) {
78+
responseFuture.complete(response);
79+
}
80+
81+
@Override
82+
public void failed(final Exception exception) {
83+
responseFuture.completeExceptionally(exception);
84+
}
85+
86+
@Override
87+
public void cancelled() {
88+
responseFuture.cancel(false);
89+
}
90+
});
91+
92+
if (writeTimeout != 0) {
93+
writeFuture.get(writeTimeout, TimeUnit.MILLISECONDS);
94+
} else {
95+
// writeFuture.get();
96+
}
97+
98+
final SimpleHttpResponse response = responseFuture.get();
99+
return new ApacheHttp2Response(request, response);
100+
} catch (InterruptedException e) {
101+
throw new IOException("Request Interrupted", e);
102+
} catch (ExecutionException e) {
103+
throw new IOException("Exception in request", e);
104+
} catch (TimeoutException e) {
105+
throw new IOException("Timed out", e);
106+
}
107+
}
108+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package com.google.firebase.internal;
2+
3+
import com.google.api.client.http.LowLevelHttpResponse;
4+
5+
import java.io.ByteArrayInputStream;
6+
import java.io.IOException;
7+
import java.io.InputStream;
8+
9+
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest;
10+
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
11+
import org.apache.hc.core5.http.Header;
12+
13+
public class ApacheHttp2Response extends LowLevelHttpResponse {
14+
15+
private final SimpleHttpResponse response;
16+
private final Header[] allHeaders;
17+
18+
ApacheHttp2Response(SimpleHttpRequest request, SimpleHttpResponse response) {
19+
this.response = response;
20+
allHeaders = response.getHeaders();
21+
}
22+
23+
@Override
24+
public int getStatusCode() {
25+
return response.getCode();
26+
}
27+
28+
@Override
29+
public InputStream getContent() throws IOException {
30+
return new ByteArrayInputStream(response.getBodyBytes());
31+
}
32+
33+
@Override
34+
public String getContentEncoding() {
35+
Header contentEncodingHeader = response.getFirstHeader("Content-Encoding");
36+
if (contentEncodingHeader == null) {
37+
return null;
38+
}
39+
return contentEncodingHeader.getValue();
40+
}
41+
42+
@Override
43+
public long getContentLength() {
44+
return response.getBodyText().length();
45+
}
46+
47+
@Override
48+
public String getContentType() {
49+
return response.getContentType().toString();
50+
}
51+
52+
@Override
53+
public String getReasonPhrase() {
54+
return response.getReasonPhrase();
55+
}
56+
57+
@Override
58+
public String getStatusLine() {
59+
return response.toString();
60+
}
61+
62+
public String getHeaderValue(String name) {
63+
Header header = response.getLastHeader(name);
64+
if (header == null) {
65+
return null;
66+
}
67+
return header.getValue();
68+
}
69+
70+
@Override
71+
public String getHeaderValue(int index) {
72+
return allHeaders[index].getValue();
73+
}
74+
75+
@Override
76+
public int getHeaderCount() {
77+
return allHeaders.length;
78+
}
79+
80+
@Override
81+
public String getHeaderName(int index) {
82+
return allHeaders[index].getName();
83+
}
84+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package com.google.firebase.internal;
2+
3+
import com.google.api.client.http.HttpTransport;
4+
5+
import java.io.IOException;
6+
import java.net.ProxySelector;
7+
import java.util.concurrent.TimeUnit;
8+
9+
import org.apache.hc.client5.http.async.HttpAsyncClient;
10+
import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
11+
import org.apache.hc.client5.http.config.TlsConfig;
12+
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
13+
import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder;
14+
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManager;
15+
import org.apache.hc.client5.http.impl.routing.SystemDefaultRoutePlanner;
16+
import org.apache.hc.core5.http.config.Http1Config;
17+
import org.apache.hc.core5.http2.HttpVersionPolicy;
18+
import org.apache.hc.core5.http2.config.H2Config;
19+
import org.apache.hc.core5.util.TimeValue;
20+
21+
public final class ApacheHttp2Transport extends HttpTransport {
22+
23+
public final CloseableHttpAsyncClient httpAsyncClient;
24+
25+
public ApacheHttp2Transport() {
26+
this(newDefaultHttpAsyncClient());
27+
}
28+
29+
public ApacheHttp2Transport(CloseableHttpAsyncClient httpAsyncClient) {
30+
this.httpAsyncClient = httpAsyncClient;
31+
httpAsyncClient.start();
32+
}
33+
34+
public static CloseableHttpAsyncClient newDefaultHttpAsyncClient() {
35+
return defaultHttpAsyncClientBuilder().build();
36+
}
37+
38+
public static HttpAsyncClientBuilder defaultHttpAsyncClientBuilder() {
39+
PoolingAsyncClientConnectionManager connectionManager =
40+
new PoolingAsyncClientConnectionManager();
41+
connectionManager.setMaxTotal(100);
42+
connectionManager.setDefaultMaxPerRoute(100);
43+
connectionManager.closeIdle(TimeValue.of(30, TimeUnit.SECONDS));
44+
connectionManager.setDefaultTlsConfig(
45+
TlsConfig.custom().setVersionPolicy(HttpVersionPolicy.NEGOTIATE).build());
46+
47+
return HttpAsyncClientBuilder.create()
48+
.setH2Config(H2Config.DEFAULT)
49+
.setHttp1Config(Http1Config.DEFAULT)
50+
.setConnectionManager(connectionManager)
51+
.setRoutePlanner(new SystemDefaultRoutePlanner(ProxySelector.getDefault()))
52+
.disableRedirectHandling()
53+
.disableAutomaticRetries();
54+
}
55+
56+
@Override
57+
public boolean supportsMethod(String method) {
58+
return true;
59+
}
60+
61+
@Override
62+
protected ApacheHttp2Request buildRequest(String method, String url) {
63+
SimpleRequestBuilder requestBuilder = SimpleRequestBuilder.create(method).setUri(url);
64+
return new ApacheHttp2Request(httpAsyncClient, requestBuilder);
65+
}
66+
67+
@Override
68+
public void shutdown() throws IOException {
69+
httpAsyncClient.close();
70+
}
71+
72+
public HttpAsyncClient getHttpClient() {
73+
return httpAsyncClient;
74+
}
75+
}

0 commit comments

Comments
 (0)