Skip to content
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

More efficient and correct H1C-to-H2C upgrade #107

Merged
merged 1 commit into from
Feb 16, 2016

Conversation

trustin
Copy link
Member

@trustin trustin commented Feb 4, 2016

Motivation:

The current HTTP/1-to-2 upgrade has the following slightly inter-related
issues:

  1. When a user specifies an explicit session protocol (H1C/H1/H2C/H2)
    instead of HTTP/HTTPS, the session creation must fail when the
    negotiated protocol does not match exactly. For example, when a user
    specified H2C, a rejected upgrade request should fail session creation.
  2. When connecting to a host whose name is registered in /etc/hosts, the
    'Host' header of the HEAD upgrade request is not set to the host name
    but the host address.
  3. When a user specifies HTTP/HTTPS as session protocol and the remote
    host does not support H2C/H2, the client currently keeps sending the
    HEAD upgrade request. We could cache the list of the hosts with no
    HTTP/2 support, so we do not send upgrade requests unnecessarily.
  4. Some servers send 'Connection: close' header when rejecting a upgrade
    request, resulting disconnection. When this happens and a user specified
    HTTP as session protocol, the client should retry the connection attempt
    silently, so that a user does not notice the upgrade failure, because
    it's not really a failure.

Modifications:

  • (2) Fork DefaultHostsFileEntryResolver and HostsFileParser so that
    they do not omit the host name in the resolved InetAddress, where
    the host name is picked up from by Armeria
  • (1) Add SessionProtocolNegotiationException
    • Mark the session creation as failure when protocol upgrade fails or
      the protocol is different from what user expected
      • See HttpConfigurator and HttpSessionChannelFactory
  • (3) Add SessionProtocolNegotiationCache
    • Update the cache when protocol upgrade is rejected by remote host in
      HttpConfigurator
  • (4) HttpSessionChannelFactory now retries the connection attempt when
    HttpConfigurator finds the upgrade response contains 'Connection:
    close' header.
  • Add the test cases for (1, 3, 4) to ThriftOverHttpClientTServletIntegrationTest
  • Miscellaneous:
    • When HTTP codec fails to decode a response, use the cause of the
      decoder failure as the invocation result, which is much more
      informative.
    • Add another variant of Exceptions.logIfUnexpected()
    • Add Exceptions.clearTrace() to remove the stack trace of an
      exception easily
    • Fix a bug where HttpServerHandler sends content in a response to a
      HEAD request. A response content of a HEAD request must be empty.

Result:

  • Invocation now fails when the negotiated protocol does not match the
    desired protocol.
  • The 'Host' header in a upgrade request now contains host name even if
    the host address was resolved via the /etc/hosts file
  • HTTP/1-to-2 upgrade request is sent only when necessary.
  • Http/1-to-2 upgrade deals better with 'Connection: close' headers.

@trustin trustin added this to the 0.9.0.Final milestone Feb 4, 2016
@trustin trustin force-pushed the h2c_upgrade_efficiency branch from e8293fe to a822681 Compare February 5, 2016 06:49
@trustin trustin changed the title [WIP] Do not send HEAD upgrade requests when the remote host is incapable of HTTP/2 More efficient and correct H1C-to-H2C upgrade Feb 5, 2016
@trustin
Copy link
Member Author

trustin commented Feb 5, 2016

@anuraaga @inch772 PTAL

synchronized (cache) {
CacheEntry e = cache.get(key);
if (e == null) {
e = new CacheEntry();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return cache.computeIfAbsent(unused -> new CacheEntry())

@trustin trustin force-pushed the h2c_upgrade_efficiency branch from 2c5a431 to f8272d5 Compare February 11, 2016 04:05
}
}

public static void clear() {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be only for testing? If so let's document that (especially since it's public).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh, forgot to document the public methods properly. Will fix next week.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not only for testing though. I guess a user might want to clear the cache explicitly on a certain occasion such as the case where a remote host previously had no HTTP/2 support gets software upgrade and is now HTTP/2 capable.

@trustin trustin force-pushed the h2c_upgrade_efficiency branch 2 times, most recently from ac6b49c to 89ad8a0 Compare February 15, 2016 02:46
@trustin
Copy link
Member Author

trustin commented Feb 15, 2016

@anuraaga Updated the PR with StampedLock. It's my first time using it, so please review carefully.

@trustin trustin force-pushed the h2c_upgrade_efficiency branch from 89ad8a0 to 93ade69 Compare February 15, 2016 02:50
}

final long writeStamp = lock.tryConvertToWriteLock(stamp);
if (writeStamp == 0L) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I wonder what use case there is for the SDK not doing this for us... Anyways, can you define a private method convertToWriteLock to share the code?

Motivation:

The current HTTP/1-to-2 upgrade has the following slightly inter-related
issues:

1. When a user specifies an explicit session protocol (H1C/H1/H2C/H2)
instead of HTTP/HTTPS, the session creation must fail when the
negotiated protocol does not match exactly. For example, when a user
specified H2C, a rejected upgrade request should fail session creation.
2. When connecting to a host whose name is registered in /etc/hosts, the
'Host' header of the HEAD upgrade request is not set to the host name
but the host address.
3. When a user specifies HTTP/HTTPS as session protocol and the remote
host does not support H2C/H2, the client currently keeps sending the
HEAD upgrade request. We could cache the list of the hosts with no
HTTP/2 support, so we do not send upgrade requests unnecessarily.
4. Some servers send 'Connection: close' header when rejecting a upgrade
request, resulting disconnection. When this happens and a user specified
HTTP as session protocol, the client should retry the connection attempt
silently, so that a user does not notice the upgrade failure, because
it's not really a failure.

Modifications:

- (2) Fork DefaultHostsFileEntryResolver and HostsFileParser so that
  they do not omit the host name in the resolved InetAddress, where
  the host name is picked up from by Armeria
- (1) Add SessionProtocolNegotiationException
  - Mark the session creation as failure when protocol upgrade fails or
    the protocol is different from what user expected
    - See HttpConfigurator and HttpSessionChannelFactory
- (3) Add SessionProtocolNegotiationCache
  - Update the cache when protocol upgrade is rejected by remote host in
    HttpConfigurator
- (4) HttpSessionChannelFactory now retries the connection attempt when
  HttpConfigurator finds the upgrade response contains 'Connection:
  close' header.
- Add the test cases for (1, 3, 4) to ThriftOverHttpClientTServletIntegrationTest
- Miscellaneous:
  - When HTTP codec fails to decode a response, use the cause of the
    decoder failure as the invocation result, which is much more
    informative.
  - Add another variant of Exceptions.logIfUnexpected()
  - Add Exceptions.clearTrace() to remove the stack trace of an
    exception easily
  - Fix a bug where HttpServerHandler sends content in a response to a
    HEAD request. A response content of a HEAD request must be empty.
  - Upgrade jetty-alpn-agent to 1.0.1.Final to support JDK 8u71/72

Result:

- Invocation now fails when the negotiated protocol does not match the
  desired protocol.
- The 'Host' header in a upgrade request now contains host name even if
  the host address was resolved via the /etc/hosts file
- HTTP/1-to-2 upgrade request is sent only when necessary.
- Http/1-to-2 upgrade deals better with 'Connection: close' headers.
@trustin trustin force-pushed the h2c_upgrade_efficiency branch from 93ade69 to b807c04 Compare February 15, 2016 05:47
@anuraaga
Copy link
Collaborator

LGTM

@inch772
Copy link
Contributor

inch772 commented Feb 15, 2016

LGTM2 👍

trustin added a commit that referenced this pull request Feb 16, 2016
More efficient and correct H1C-to-H2C upgrade
@trustin trustin merged commit f89c933 into line:master Feb 16, 2016
@trustin trustin deleted the h2c_upgrade_efficiency branch February 16, 2016 02:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants