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

codec-http2: Improve h1 to h2 header conversion #7399

Closed
wants to merge 1 commit into from

Conversation

mosesn
Copy link
Contributor

@mosesn mosesn commented Nov 10, 2017

Motivation:

Netty could handle "connection" or "te" headers more gently when
converting from http/1.1 to http/2 headers. Http/2 headers don't
support single-hop headers, so when we convert from http/1.1 to http/2,
we should drop all single-hop headers. This includes headers like
"transfer-encoding" and "connection", but also the headers that
"connection" points to, since "connection" can be used to designate
other headers as single-hop headers. For the "te" header, we can more
permissively convert it by just dropping non-conforming headers (ie
non-"trailers" headers) which is what we do for all other headers when
we convert.

Modifications:

Add a new blacklist to the http/1.1 to http/2 conversion, which is
constructed from the values of the "connection" header, and stop
throwing an exception when a "te" header is passed with a non-"trailers"
value. Instead, drop all values except for "trailers". Add unit tests
for "connection" and "te" headers when converting from http/1.1 to http/2.

Result:

This will improve the h2c upgrade request, and also conversions from
http/1.1 to http/2. This will simplify implementing spec-compliant
http/2 servers that want to share code between their http/1.1 and http/2
implementations.

[Fixes #7355]

@@ -410,19 +412,30 @@ public static Http2Headers toHttp2Headers(HttpHeaders inHeaders, boolean validat
return out;
}

private static CharSequenceMap<AsciiString> toLowercaseMap(List<String> values) {
CharSequenceMap<AsciiString> result = new CharSequenceMap<AsciiString>();
Copy link
Member

Choose a reason for hiding this comment

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

should we use the constructor that takes the arraySizeHint based on values.size() ?

@@ -410,19 +412,30 @@ public static Http2Headers toHttp2Headers(HttpHeaders inHeaders, boolean validat
return out;
}

private static CharSequenceMap<AsciiString> toLowercaseMap(List<String> values) {
CharSequenceMap<AsciiString> result = new CharSequenceMap<AsciiString>();
for (CharSequence value : values) {
Copy link
Member

Choose a reason for hiding this comment

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

consider using an "old for" loop if the List implements RandomAccess to reduce GC pressure (as the foreach thingy you use here will always create an Iterator)

Copy link
Member

Choose a reason for hiding this comment

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

While it creates an iterator, it can be allocated on the stack. (Whether it actually is allocated on the stack is a different discussion.)

Copy link
Member

Choose a reason for hiding this comment

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

@ejona86 we saw GC issues caused by this in the past so better safe then sorry ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@@ -410,19 +412,30 @@ public static Http2Headers toHttp2Headers(HttpHeaders inHeaders, boolean validat
return out;
}

private static CharSequenceMap<AsciiString> toLowercaseMap(List<String> values) {
CharSequenceMap<AsciiString> result = new CharSequenceMap<AsciiString>();
for (CharSequence value : values) {
Copy link
Member

Choose a reason for hiding this comment

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

Don't you need also need to split value on , and trim whitespace?

@mosesn
Copy link
Contributor Author

mosesn commented Nov 13, 2017

@ejona86 @normanmaurer PTAL

}
result.add(lowerCased.subSequence(start, lowerCased.length(), false).trim(), EMPTY_STRING);
} else {
result.add(lowerCased, EMPTY_STRING);
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to worry about trimming this? HTTP parsers will generally naturally remove whitespace (but aren't guaranteed to), but that doesn't happen in HTTP/2 (from what I've seen; maybe Netty does).

start = index + 1;
} while (start < lowerCased.length() &&
(index = lowerCased.forEachByte(start, value.length() - start, FIND_COMMA)) != -1);
if (start >= lowerCased.length()) {
Copy link
Member

Choose a reason for hiding this comment

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

It doesn't seem like this should be special-cased or that all empty strings should be handled (by ignoring them), assuming that's its purpose.

From what I can tell, it is only possible for start == lowerCased.length() which is "safe" for subSequence(). That would happen for a string like "trailing,comma," which produces an extra empty string. But if we're concerned about that, then it seems ",leading,comma" and "inner,,comma" should also be disallowed.

While RFC 7230 §7 says:

In any production that uses the list construct, a sender MUST NOT
generate empty list elements.

It also says:

For compatibility with legacy list rules, a recipient MUST parse and
ignore a reasonable number of empty list elements

You may consider using StringUtil.unescapeCsvFields for this. It supports quotes, which is unnecessary here, but shouldn't hurt anything other than performance.

Copy link
Member

Choose a reason for hiding this comment

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

:( It seems StringUtil.unescapeCsvFields doesn't do the trimming, even though it is used by CombinedHttpHeaders.

@mosesn
Copy link
Contributor Author

mosesn commented Nov 15, 2017

@ejona86 PTAL

Copy link
Member

@ejona86 ejona86 left a comment

Choose a reason for hiding this comment

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

LGTM. Thank you. Sorry for being pedantic.

@mosesn
Copy link
Contributor Author

mosesn commented Nov 16, 2017

No worries, I'll squash it, thanks for the reviews.

Motivation:

Netty could handle "connection" or "te" headers more gently when
converting from http/1.1 to http/2 headers.  Http/2 headers don't
support single-hop headers, so when we convert from http/1.1 to http/2,
we should drop all single-hop headers.  This includes headers like
"transfer-encoding" and "connection", but also the headers that
"connection" points to, since "connection" can be used to designate
other headers as single-hop headers.  For the "te" header, we can more
permissively convert it by just dropping non-conforming headers (ie
non-"trailers" headers) which is what we do for all other headers when
we convert.

Modifications:

Add a new blacklist to the http/1.1 to http/2 conversion, which is
constructed from the values of the "connection" header, and stop
throwing an exception when a "te" header is passed with a non-"trailers"
value.  Instead, drop all values except for "trailers".  Add unit tests
for "connection" and "te" headers when converting from http/1.1 to http/2.

Result:

This will improve the h2c upgrade request, and also conversions from
http/1.1 to http/2.  This will simplify implementing spec-compliant
http/2 servers that want to share code between their http/1.1 and http/2
implementations.

[Fixes netty#7355]
@normanmaurer
Copy link
Member

Let me pull this in now.. @Scottmitch feel free to open a follow-up PR if you want any changes and had time to review

@normanmaurer normanmaurer added this to the 4.1.18.Final milestone Nov 17, 2017
@normanmaurer
Copy link
Member

Cherry-picked into 4.1 (d976dc1)

@normanmaurer
Copy link
Member

@mosesn thanks!

public static void toHttp2Headers(HttpHeaders inHeaders, Http2Headers out) {
Iterator<Entry<CharSequence, CharSequence>> iter = inHeaders.iteratorCharSequence();
new CharSequenceMap<AsciiString>();

Choose a reason for hiding this comment

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

Can we remove this allocation?

Copy link
Member

Choose a reason for hiding this comment

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

@imasahiro if you think we can easily please submit a PR :) Happy to review and merge

Copy link
Member

Choose a reason for hiding this comment

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

Isn't this a trash line? As in it does nothing? It doesn't seem like the object is used.

Copy link
Member

Choose a reason for hiding this comment

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

lol doh! ... I totally missed it. Removed in 78522cf

Choose a reason for hiding this comment

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

@normanmaurer Ah, you already pushed the comment! Thanks anyways :-)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh whoops, sorry!

@Scottmitch
Copy link
Member

Scottmitch commented Nov 20, 2017

thanks @mosesn ! generally lgtm .. few followup PRs coming up:

#7421
#7422

@mosesn
Copy link
Contributor Author

mosesn commented Nov 20, 2017

Thanks @Scottmitch!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants