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
Conversation
f3f1347
to
dac6ee9
Compare
@@ -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>(); |
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.
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) { |
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.
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)
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.
While it creates an iterator, it can be allocated on the stack. (Whether it actually is allocated on the stack is a different discussion.)
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.
@ejona86 we saw GC issues caused by this in the past so better safe then sorry ;)
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.
looks like it's a linked list: https://github.com/netty/netty/blob/4.1/codec/src/main/java/io/netty/handler/codec/DefaultHeaders.java#L163
@@ -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) { |
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.
Don't you need also need to split value on ,
and trim whitespace?
9246b77
to
4b41e52
Compare
@ejona86 @normanmaurer PTAL |
} | ||
result.add(lowerCased.subSequence(start, lowerCased.length(), false).trim(), EMPTY_STRING); | ||
} else { | ||
result.add(lowerCased, EMPTY_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.
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()) { |
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 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.
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 StringUtil.unescapeCsvFields
doesn't do the trimming, even though it is used by CombinedHttpHeaders
.
@ejona86 PTAL |
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.
LGTM. Thank you. Sorry for being pedantic.
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]
babf555
to
2831e52
Compare
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 |
Cherry-picked into 4.1 (d976dc1) |
@mosesn thanks! |
public static void toHttp2Headers(HttpHeaders inHeaders, Http2Headers out) { | ||
Iterator<Entry<CharSequence, CharSequence>> iter = inHeaders.iteratorCharSequence(); | ||
new CharSequenceMap<AsciiString>(); |
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.
Can we remove this allocation?
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.
@imasahiro if you think we can easily please submit a PR :) Happy to review and merge
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.
Isn't this a trash line? As in it does nothing? It doesn't seem like the object is used.
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.
lol doh! ... I totally missed it. Removed in 78522cf
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.
@normanmaurer Ah, you already pushed the comment! Thanks anyways :-)
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.
oh whoops, sorry!
Thanks @Scottmitch! |
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]