-
Notifications
You must be signed in to change notification settings - Fork 6.1k
8349910: Implement JEP 517: HTTP/3 for the HTTP Client API #24751
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
base: master
Are you sure you want to change the base?
Conversation
…oesn't support HTTP/3, the request version is HTTP_3, and HTTP_ONLY is not specified
…roposed to mainline)
…ending a new request
… intermittent failures in H3ErrorHandlingTest.java
…ending the packet
…maxStreamLimitTimeout=0 too
* @param peerHost The peer hostname or IP address. Can be null. | ||
* @param peerPort The peer port, can be -1 if the port is unknown |
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.
Would that be the hostname in the URI, or in the AltService?
Maybe we could add an @apiNote
here to clarify it.
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.
Well the javadoc here was written to match the one on SSLContext#createSSLEngine. The peer information is used for caching, but it's also used in the SNI extension, so ideally users should use the URI address, not the alt service one.
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.
OK. Maybe that would deserve a note - since with HTTP/3 we have potentially two addresses and two ports.
import java.util.Optional; | ||
|
||
/** | ||
* Represents the Quic versions defined in their corresponding RFCs |
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.
Maybe add a note here to warn that new enum constants may be added to this enum in the future.
QUIC_V1(1), // RFC-9000 | ||
QUIC_V2(0x6b3343cf); // RFC 9369 | ||
|
||
private final int versionNumber; |
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.
Could add a comment here to say that the version is defined as a 32bits unsigned number, and we chose a simple int to represent it.
if (shc.sslConfig.isQuic) { | ||
QuicTLSEngineImpl engine = | ||
(QuicTLSEngineImpl) shc.conContext.transport; | ||
engine.deriveOneRTTKeys(); |
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.
We should not derive the server's 1RTT read keys before processing the client's Finished message.
Also, we could skip calculating the SSL WriteCipher when QUIC is in use. Also, we're calculating the baseWriteSecret twice (deriveOneRTTKeys calculates the same secret)
@@ -46,7 +46,7 @@ final class KeyShareExtension { | |||
new CHKeyShareProducer(); | |||
static final ExtensionConsumer chOnLoadConsumer = | |||
new CHKeyShareConsumer(); | |||
static final HandshakeAbsence chOnTradAbsence = | |||
static final HandshakeAbsence chOnTradeAbsence = |
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.
Could we fix that one in mainline to remove this file from the PR?
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.
// apply to QuicEngine only | ||
byte[] getHandshakeMessage() { | ||
throw new UnsupportedOperationException(); | ||
} | ||
|
||
// apply to QuicEngine only | ||
QuicTLSEngine.KeySpace getHandshakeMessageKeySpace() { | ||
throw new UnsupportedOperationException(); | ||
} | ||
|
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.
Do we need those two methods here? Or could we just have them on QuicEngineOutputRecord
and do the appropriate cast at the point they are called?
Or do we have that at this level for performance reasons?
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 was done to match the surrounding code. There's a bunch of methods in OutputRecord that throw UOE and are overridden by one class only. I suppose it would be cleaner to just do the cast, but "when in Rome, do as the Romans do"
@@ -58,7 +58,7 @@ final class PreSharedKeyExtension { | |||
new CHPreSharedKeyOnLoadAbsence(); | |||
static final HandshakeConsumer chOnTradeConsumer = | |||
new CHPreSharedKeyUpdate(); | |||
static final HandshakeAbsence chOnTradAbsence = | |||
static final HandshakeAbsence chOnTradeAbsence = |
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.
Could we bring this change to mainline to remove this file from this PR?
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.
throws GeneralSecurityException { | ||
super(cipherSuite, secret, new T13AESHPCipher(hp), keyPhase); | ||
this.key = key; | ||
this.iv = iv; |
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.iv = iv; | |
this.iv = iv.clone(); |
final int keyPhase) throws GeneralSecurityException { | ||
super(cipherSuite, secret, new T13AESHPCipher(hp), keyPhase); | ||
this.key = key; | ||
this.iv = iv; |
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.iv = iv; | |
this.iv = iv.clone(); |
throws GeneralSecurityException { | ||
super(cipherSuite, secret, new T13CC20HPCipher(hp), keyPhase); | ||
this.key = key; | ||
this.iv = iv; |
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.iv = iv; | |
this.iv = iv.clone(); |
* QuicTransportParametersExtension is an implementation of RFC 9000. | ||
* It is only used by QUIC connections. |
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.
I believe referencing RFC 9001 Section 8.2 would be more appropriate here.
The QUIC transport parameters themselves are defined in RFC 9000, but the use of the quic_transport_parameters extension in the ClientHello and the EncryptedExtensions messages is described in RFC 9001.
Or else the purpose and description of this class could be clarified.
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.
Most of the extensions have a generic JavaDoc, I will update this one to match the others.
@@ -458,6 +458,28 @@ enum SSLExtension implements SSLStringizer { | |||
null, null, null, null, | |||
KeyShareExtension.hrrStringizer), | |||
|
|||
// Extension defined in RFC 9000 |
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.
Shouldn't this be RFC 9001 here too?
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.
Will be updated in the next refresh.
} | ||
} | ||
} | ||
} | ||
|
||
// true if previous attempt resulted in streamLimitReached | ||
public boolean hasReachedStreamLimit(Version version) { return streamLimitReached == version; } |
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.
it seems that streamLimitReached
will either be equal to version
or null
. If that's the case, let's use a boolean instead.
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.
That's a good point.
@@ -111,8 +120,12 @@ HttpClientImpl client() { | |||
|
|||
// Keeps track of the underlying connection when establishing an HTTP/2 |
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.
since this is now used with HTTP/3, should we update this comment?
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.
I have verified the other comments in this class and updated to mention or HTTP/3 in a few places in this class, in my local repo. Will appear the next time I sync this PR.
@@ -47,7 +47,7 @@ final class SupportedGroupsExtension { | |||
new CHSupportedGroupsProducer(); | |||
static final ExtensionConsumer chOnLoadConsumer = | |||
new CHSupportedGroupsConsumer(); | |||
static final HandshakeAbsence chOnTradAbsence = | |||
static final HandshakeAbsence chOnTradeAbsence = |
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.
Another one that could disappear if we fixed that typo in mainline first.
@djelinski would you be willing to do that?
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.
// TODO in future, when QuicTLSEngine might become a public exported interface | ||
// we should have a method on javax.net.ssl.X509ExtendedKeyManager that | ||
// takes QuicTLSEngine. |
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.
// TODO in future, when QuicTLSEngine might become a public exported interface | |
// we should have a method on javax.net.ssl.X509ExtendedKeyManager that | |
// takes QuicTLSEngine. | |
// TODO in future, if QuicTLSEngine ever becomes a public exported interface | |
// we might have a method on javax.net.ssl.X509ExtendedKeyManager that | |
// takes QuicTLSEngine. |
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.
Maybe we could stop guessing the future, and use this instead:
// TODO in future, when QuicTLSEngine might become a public exported interface | |
// we should have a method on javax.net.ssl.X509ExtendedKeyManager that | |
// takes QuicTLSEngine. | |
// TODO add a method on javax.net.ssl.X509ExtendedKeyManager that | |
// takes QuicTLSEngine. |
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.
Removed speculations about the future
// TODO in future, when QuicTLSEngine might become a public exported interface | ||
// we should have a method on javax.net.ssl.X509ExtendedKeyManager that | ||
// takes QuicTLSEngine. |
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.
// TODO in future, when QuicTLSEngine might become a public exported interface | |
// we should have a method on javax.net.ssl.X509ExtendedKeyManager that | |
// takes QuicTLSEngine. | |
// TODO in future, if QuicTLSEngine ever becomes a public exported interface | |
// we might have a method on javax.net.ssl.X509ExtendedKeyManager that | |
// takes QuicTLSEngine. |
* <a href="https://tools.ietf.org/html/rfc9114">Hypertext Transfer Protocol | ||
* Version 3 (HTTP/3)</a>, the <a href="https://tools.ietf.org/html/rfc7540"> | ||
* Hypertext Transfer Protocol Version 2 (HTTP/2)</a>, the | ||
* <a href="https://tools.ietf.org/html/rfc2616"> |
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.
It is strange to link to a different place here than in the @spec
tags below. I will fix that.
static Origin of(final String scheme, final InetSocketAddress addr) | ||
throws IllegalArgumentException { | ||
// we use getHostString(), since the address could be unresolved in the case where | ||
// proxy is configured | ||
final String originHost = addr.getHostString(); | ||
return new Origin(scheme, originHost, addr.getPort()); | ||
} | ||
} |
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.
I don't think it's a good idea to rely on InetSocketAddress::getHostString()
as this can change depending on how the InetSocketAddress
was constructed and whether getHost()
has been called.
I believe it would be more reliable to add a new URI origin
parameter to the HttpConnection
constructor. We could build that origin from the request URI for which the connection was created. Probably origin
should be null
for PlainProxyConnection
.
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.
I've created https://bugs.openjdk.org/browse/JDK-8361060 to track this.
@@ -111,8 +120,12 @@ HttpClientImpl client() { | |||
|
|||
// Keeps track of the underlying connection when establishing an HTTP/2 | |||
// exchange so that it can be aborted/timed out mid setup. | |||
static final class ConnectionAborter { | |||
final class ConnectionAborter { | |||
// In case of HTTP/3 direct connection we may have |
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.
"direct" is usually used to refer to connections that don't use a proxy; since H3 connections never use a proxy, can we drop the "direct" here?
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.
Good point. I will rephrase. We should be speaking of HTTP/3 requests (and not connection) here anyway.
// there's nothing more left to parse | ||
return new ParsedHeaderValue(altValue, alpnName, hostPort.host(), hostPort.port(), Map.of()); | ||
} | ||
// parse the semi-colon delimited parameters out of the rest of the remaining string |
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.
// parse the semi-colon delimited parameters out of the rest of the remaining string | |
// parse the semicolon delimited parameters out of the rest of the remaining string |
@@ -79,6 +81,13 @@ final class Exchange<T> { | |||
|
|||
final AtomicInteger nonFinalResponses = new AtomicInteger(); | |||
|
|||
// This will be set to true only when it is guaranteed that the server hasn't processed | |||
// the request. Typically this happens when the server explicitly states (through a GOAWAY frame |
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.
// the request. Typically this happens when the server explicitly states (through a GOAWAY frame | |
// the request. Typically, this happens when the server explicitly states (through a GOAWAY frame |
@@ -111,8 +120,12 @@ HttpClientImpl client() { | |||
|
|||
// Keeps track of the underlying connection when establishing an HTTP/2 | |||
// exchange so that it can be aborted/timed out mid setup. | |||
static final class ConnectionAborter { | |||
final class ConnectionAborter { | |||
// In case of HTTP/3 direct connection we may have |
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.
Good point. I will rephrase. We should be speaking of HTTP/3 requests (and not connection) here anyway.
* {@return the current partial frame, if any} | ||
* This method doesn't try to do any decoding. | ||
*/ | ||
public Http3Frame peek() { |
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 method is unused. I'll remove it.
private volatile QuicStreamWriter localWriter; | ||
private final CompletableFuture<QuicStreamWriter> streamWriterCF; | ||
// a queue of ByteBuffers submitted for writing. | ||
// might be null if not used. Only used in QueuingStreamPair. |
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.
It seems to be true. I'll see if I can move it to QueuingStreamPair.
SequentialScheduler.lockingScheduler(this::peerReaderLoop); | ||
// The QuicStreamReader for the peer control stream | ||
volatile QuicStreamReader peerReader; | ||
private final CompletableFuture<QuicStreamReader> streamReaderCF; |
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.
Unused
* to the receiver part of this stream pair after the remote HTTP/3 stream | ||
* type has been read off the remote initiated stream} | ||
*/ | ||
public final CompletableFuture<QuicStreamReader> futureReceiverStreamReader() { |
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.
Unused
Hi,
Please find here a PR for the implementation of JEP 517: HTTP/3 for the HTTP Client API.
The CSR can be viewed at JDK-8350588: Implement JEP 517: HTTP/3 for the HTTP Client API
This JEP proposes to enhance the HttpClient implementation to support HTTP/3.
It adds a non-exposed / non-exported internal implementation of the QUIC protocol based on DatagramChannel and the SunJSSE SSLContext provider.
Progress
Issues
Contributors
<[email protected]>
<[email protected]>
<[email protected]>
<[email protected]>
<[email protected]>
<[email protected]>
<[email protected]>
<[email protected]>
<[email protected]>
<[email protected]>
<[email protected]>
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/24751/head:pull/24751
$ git checkout pull/24751
Update a local copy of the PR:
$ git checkout pull/24751
$ git pull https://git.openjdk.org/jdk.git pull/24751/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 24751
View PR using the GUI difftool:
$ git pr show -t 24751
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/24751.diff
Using Webrev
Link to Webrev Comment