-
Notifications
You must be signed in to change notification settings - Fork 285
feat: Add HTTP/2 enabled transport as default transport #979
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 11 commits
e0f3e21
93016eb
1541ad0
d78632c
81246d9
a86c325
ff1e4fb
36927d9
ed88849
21bfa8a
f9e8576
3368eb2
5797b48
0c37ba4
1881f28
74c57b7
ceac56c
18365a7
92d3112
73e1355
165ff59
11aab96
be9e869
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
/* | ||
* Copyright 2024 Google 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 com.google.firebase.internal; | ||
|
||
import com.google.api.client.util.StreamingContent; | ||
import com.google.common.annotations.VisibleForTesting; | ||
|
||
import java.io.ByteArrayOutputStream; | ||
import java.io.IOException; | ||
import java.nio.ByteBuffer; | ||
import java.util.Set; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.concurrent.atomic.AtomicReference; | ||
|
||
import org.apache.hc.core5.http.ContentType; | ||
import org.apache.hc.core5.http.nio.AsyncEntityProducer; | ||
import org.apache.hc.core5.http.nio.DataStreamChannel; | ||
|
||
@SuppressWarnings("deprecation") | ||
public class ApacheHttp2AsyncEntityProducer implements AsyncEntityProducer { | ||
private ByteBuffer bytebuf; | ||
private ByteArrayOutputStream baos; | ||
private final StreamingContent content; | ||
private final ContentType contentType; | ||
private final long contentLength; | ||
private final String contentEncoding; | ||
private final CompletableFuture<Void> writeFuture; | ||
private final AtomicReference<Exception> exception; | ||
|
||
public ApacheHttp2AsyncEntityProducer(StreamingContent content, ContentType contentType, | ||
String contentEncoding, long contentLength, CompletableFuture<Void> writeFuture) { | ||
this.content = content; | ||
this.contentType = contentType; | ||
this.contentEncoding = contentEncoding; | ||
this.contentLength = contentLength; | ||
this.writeFuture = writeFuture; | ||
this.bytebuf = null; | ||
|
||
this.baos = new ByteArrayOutputStream((int) (contentLength < 0 ? 0 : contentLength)); | ||
this.exception = new AtomicReference<>(); | ||
} | ||
|
||
public ApacheHttp2AsyncEntityProducer(ApacheHttp2Request request, | ||
CompletableFuture<Void> writeFuture) { | ||
this( | ||
request.getStreamingContent(), | ||
ContentType.parse(request.getContentType()), | ||
request.getContentEncoding(), | ||
request.getContentLength(), | ||
writeFuture); | ||
} | ||
|
||
@Override | ||
public boolean isRepeatable() { | ||
return true; | ||
} | ||
|
||
@Override | ||
public String getContentType() { | ||
return contentType != null ? contentType.toString() : null; | ||
} | ||
|
||
@Override | ||
public long getContentLength() { | ||
return contentLength; | ||
} | ||
|
||
@Override | ||
public int available() { | ||
return Integer.MAX_VALUE; | ||
} | ||
|
||
@Override | ||
public String getContentEncoding() { | ||
return contentEncoding; | ||
} | ||
|
||
@Override | ||
public boolean isChunked() { | ||
return contentLength == -1; | ||
} | ||
|
||
@Override | ||
public Set<String> getTrailerNames() { | ||
return null; | ||
} | ||
|
||
@Override | ||
public void produce(DataStreamChannel channel) throws IOException { | ||
if (bytebuf == null) { | ||
if (content != null) { | ||
try { | ||
content.writeTo(baos); | ||
} catch (IOException e) { | ||
writeFuture.completeExceptionally(e); | ||
// failed(e); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: can we remove this now? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This should have been uncommented. Fixed that and fixed an issue where exceptions were not being re-thrown. |
||
} | ||
} | ||
|
||
this.bytebuf = ByteBuffer.wrap(baos.toByteArray()); | ||
} | ||
|
||
if (bytebuf.hasRemaining()) { | ||
channel.write(bytebuf); | ||
} | ||
|
||
if (!bytebuf.hasRemaining()) { | ||
channel.endStream(); | ||
writeFuture.complete(null); | ||
releaseResources(); | ||
} | ||
} | ||
|
||
@Override | ||
public void failed(Exception cause) { | ||
if (exception.compareAndSet(null, cause)) { | ||
releaseResources(); | ||
writeFuture.completeExceptionally(cause); | ||
} | ||
} | ||
|
||
public final Exception getException() { | ||
return exception.get(); | ||
} | ||
|
||
@Override | ||
public void releaseResources() { | ||
bytebuf.clear(); | ||
} | ||
|
||
@VisibleForTesting | ||
ByteBuffer getBytebuf() { | ||
return bytebuf; | ||
} | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: new line There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done! |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
/* | ||
* Copyright 2024 Google 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 com.google.firebase.internal; | ||
|
||
import com.google.api.client.http.LowLevelHttpRequest; | ||
import com.google.api.client.http.LowLevelHttpResponse; | ||
import com.google.common.annotations.VisibleForTesting; | ||
|
||
import java.io.IOException; | ||
import java.util.concurrent.CancellationException; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.concurrent.ExecutionException; | ||
import java.util.concurrent.Future; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.concurrent.TimeoutException; | ||
|
||
import org.apache.hc.client5.http.ConnectTimeoutException; | ||
import org.apache.hc.client5.http.async.methods.SimpleHttpRequest; | ||
import org.apache.hc.client5.http.async.methods.SimpleHttpResponse; | ||
import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder; | ||
import org.apache.hc.client5.http.async.methods.SimpleResponseConsumer; | ||
import org.apache.hc.client5.http.config.RequestConfig; | ||
import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; | ||
import org.apache.hc.core5.concurrent.FutureCallback; | ||
import org.apache.hc.core5.http.nio.support.BasicRequestProducer; | ||
import org.apache.hc.core5.http2.H2StreamResetException; | ||
import org.apache.hc.core5.util.Timeout; | ||
|
||
final class ApacheHttp2Request extends LowLevelHttpRequest { | ||
private final CloseableHttpAsyncClient httpAsyncClient; | ||
private final SimpleRequestBuilder requestBuilder; | ||
private SimpleHttpRequest request; | ||
private final RequestConfig.Builder requestConfig; | ||
private int writeTimeout; | ||
private ApacheHttp2AsyncEntityProducer entityProducer; | ||
|
||
ApacheHttp2Request( | ||
CloseableHttpAsyncClient httpAsyncClient, SimpleRequestBuilder requestBuilder) { | ||
this.httpAsyncClient = httpAsyncClient; | ||
this.requestBuilder = requestBuilder; | ||
this.writeTimeout = 0; | ||
|
||
this.requestConfig = RequestConfig.custom() | ||
.setRedirectsEnabled(false); | ||
} | ||
|
||
@Override | ||
public void addHeader(String name, String value) { | ||
requestBuilder.addHeader(name, value); | ||
} | ||
|
||
@Override | ||
public void setTimeout(int connectionTimeout, int readTimeout) throws IOException { | ||
requestConfig | ||
.setConnectTimeout(Timeout.ofMilliseconds(connectionTimeout)) | ||
.setResponseTimeout(Timeout.ofMilliseconds(readTimeout)); | ||
} | ||
|
||
@Override | ||
public void setWriteTimeout(int writeTimeout) throws IOException { | ||
this.writeTimeout = writeTimeout; | ||
} | ||
|
||
@Override | ||
public LowLevelHttpResponse execute() throws IOException { | ||
// Set request configs | ||
requestBuilder.setRequestConfig(requestConfig.build()); | ||
|
||
// Build request | ||
request = requestBuilder.build(); | ||
|
||
// Make Producer | ||
CompletableFuture<Void> writeFuture = new CompletableFuture<>(); | ||
entityProducer = new ApacheHttp2AsyncEntityProducer(this, writeFuture); | ||
|
||
// Execute | ||
final Future<SimpleHttpResponse> responseFuture = httpAsyncClient.execute( | ||
new BasicRequestProducer(request, entityProducer), | ||
SimpleResponseConsumer.create(), | ||
new FutureCallback<SimpleHttpResponse>() { | ||
@Override | ||
public void completed(final SimpleHttpResponse response) { | ||
} | ||
|
||
@Override | ||
public void failed(final Exception exception) { | ||
} | ||
|
||
@Override | ||
public void cancelled() { | ||
} | ||
}); | ||
|
||
// Wait for write | ||
try { | ||
if (writeTimeout != 0) { | ||
writeFuture.get(writeTimeout, TimeUnit.MILLISECONDS); | ||
} | ||
} catch (TimeoutException e) { | ||
throw new IOException("Write Timeout", e.getCause()); | ||
} catch (Exception e) { | ||
throw new IOException("Exception in write", e.getCause()); | ||
} | ||
|
||
// Wait for response | ||
try { | ||
final SimpleHttpResponse response = responseFuture.get(); | ||
return new ApacheHttp2Response(response); | ||
} catch (ExecutionException e) { | ||
if (e.getCause() instanceof ConnectTimeoutException) { | ||
throw new IOException("Connection Timeout", e.getCause()); | ||
} else if (e.getCause() instanceof H2StreamResetException) { | ||
throw new IOException("Stream exception in request", e.getCause()); | ||
} else { | ||
throw new IOException("Exception in request", e); | ||
} | ||
} catch (InterruptedException e) { | ||
throw new IOException("Request Interrupted", e); | ||
} catch (CancellationException e) { | ||
throw new IOException("Request Cancelled", e); | ||
} | ||
} | ||
|
||
@VisibleForTesting | ||
ApacheHttp2AsyncEntityProducer getEntityProducer() { | ||
return entityProducer; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which deprecation is this? Maybe we should add a note
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is related to the google api client's
StreamingContent
. Removed the suppression to match other files where we don't suppress this warning.