codec-http2: Lazily translate cookies for HTTP/1 (#9251)
Motivation: For HTTP/2 messages with multiple cookies HttpConversionUtil.addHttp2ToHttpHeaders spends a good portion of time creating throwaway StringBuilders. Modification: Handle cookies lazily by using a ThreadLocal StringBuilder and then converting it to the H1 header at the end. Result: Less allocations.
This commit is contained in:
parent
e3be64fd9c
commit
750bd9f7b7
@ -33,6 +33,7 @@ import io.netty.handler.codec.http.HttpResponseStatus;
|
|||||||
import io.netty.handler.codec.http.HttpUtil;
|
import io.netty.handler.codec.http.HttpUtil;
|
||||||
import io.netty.handler.codec.http.HttpVersion;
|
import io.netty.handler.codec.http.HttpVersion;
|
||||||
import io.netty.util.AsciiString;
|
import io.netty.util.AsciiString;
|
||||||
|
import io.netty.util.internal.InternalThreadLocalMap;
|
||||||
import io.netty.util.internal.UnstableApi;
|
import io.netty.util.internal.UnstableApi;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
@ -360,9 +361,7 @@ public final class HttpConversionUtil {
|
|||||||
HttpVersion httpVersion, boolean isTrailer, boolean isRequest) throws Http2Exception {
|
HttpVersion httpVersion, boolean isTrailer, boolean isRequest) throws Http2Exception {
|
||||||
Http2ToHttpHeaderTranslator translator = new Http2ToHttpHeaderTranslator(streamId, outputHeaders, isRequest);
|
Http2ToHttpHeaderTranslator translator = new Http2ToHttpHeaderTranslator(streamId, outputHeaders, isRequest);
|
||||||
try {
|
try {
|
||||||
for (Entry<CharSequence, CharSequence> entry : inputHeaders) {
|
translator.translateHeaders(inputHeaders);
|
||||||
translator.translate(entry);
|
|
||||||
}
|
|
||||||
} catch (Http2Exception ex) {
|
} catch (Http2Exception ex) {
|
||||||
throw ex;
|
throw ex;
|
||||||
} catch (Throwable t) {
|
} catch (Throwable t) {
|
||||||
@ -620,29 +619,43 @@ public final class HttpConversionUtil {
|
|||||||
translations = request ? REQUEST_HEADER_TRANSLATIONS : RESPONSE_HEADER_TRANSLATIONS;
|
translations = request ? REQUEST_HEADER_TRANSLATIONS : RESPONSE_HEADER_TRANSLATIONS;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void translate(Entry<CharSequence, CharSequence> entry) throws Http2Exception {
|
public void translateHeaders(Iterable<Entry<CharSequence, CharSequence>> inputHeaders) throws Http2Exception {
|
||||||
final CharSequence name = entry.getKey();
|
// lazily created as needed
|
||||||
final CharSequence value = entry.getValue();
|
StringBuilder cookies = null;
|
||||||
AsciiString translatedName = translations.get(name);
|
|
||||||
if (translatedName != null) {
|
for (Entry<CharSequence, CharSequence> entry : inputHeaders) {
|
||||||
output.add(translatedName, AsciiString.of(value));
|
final CharSequence name = entry.getKey();
|
||||||
} else if (!Http2Headers.PseudoHeaderName.isPseudoHeader(name)) {
|
final CharSequence value = entry.getValue();
|
||||||
// https://tools.ietf.org/html/rfc7540#section-8.1.2.3
|
AsciiString translatedName = translations.get(name);
|
||||||
// All headers that start with ':' are only valid in HTTP/2 context
|
if (translatedName != null) {
|
||||||
if (name.length() == 0 || name.charAt(0) == ':') {
|
output.add(translatedName, AsciiString.of(value));
|
||||||
throw streamError(streamId, PROTOCOL_ERROR,
|
} else if (!Http2Headers.PseudoHeaderName.isPseudoHeader(name)) {
|
||||||
"Invalid HTTP/2 header '%s' encountered in translation to HTTP/1.x", name);
|
// https://tools.ietf.org/html/rfc7540#section-8.1.2.3
|
||||||
}
|
// All headers that start with ':' are only valid in HTTP/2 context
|
||||||
if (COOKIE.equals(name)) {
|
if (name.length() == 0 || name.charAt(0) == ':') {
|
||||||
// combine the cookie values into 1 header entry.
|
throw streamError(streamId, PROTOCOL_ERROR,
|
||||||
// https://tools.ietf.org/html/rfc7540#section-8.1.2.5
|
"Invalid HTTP/2 header '%s' encountered in translation to HTTP/1.x", name);
|
||||||
String existingCookie = output.get(COOKIE);
|
}
|
||||||
output.set(COOKIE,
|
if (COOKIE.equals(name)) {
|
||||||
(existingCookie != null) ? (existingCookie + "; " + value) : value);
|
// combine the cookie values into 1 header entry.
|
||||||
} else {
|
// https://tools.ietf.org/html/rfc7540#section-8.1.2.5
|
||||||
output.add(name, value);
|
if (cookies == null) {
|
||||||
|
cookies = InternalThreadLocalMap.get().stringBuilder();
|
||||||
|
} else if (cookies.length() > 0) {
|
||||||
|
cookies.append("; ");
|
||||||
|
}
|
||||||
|
cookies.append(value);
|
||||||
|
} else {
|
||||||
|
output.add(name, value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (cookies != null) {
|
||||||
|
output.add(COOKIE, cookies.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void translateHeader(Entry<CharSequence, CharSequence> entry) throws Http2Exception {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,10 +17,12 @@ package io.netty.handler.codec.http2;
|
|||||||
|
|
||||||
import io.netty.handler.codec.http.DefaultHttpHeaders;
|
import io.netty.handler.codec.http.DefaultHttpHeaders;
|
||||||
import io.netty.handler.codec.http.HttpHeaders;
|
import io.netty.handler.codec.http.HttpHeaders;
|
||||||
|
import io.netty.handler.codec.http.HttpVersion;
|
||||||
import io.netty.util.AsciiString;
|
import io.netty.util.AsciiString;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
|
import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION;
|
||||||
|
import static io.netty.handler.codec.http.HttpHeaderNames.COOKIE;
|
||||||
import static io.netty.handler.codec.http.HttpHeaderNames.TE;
|
import static io.netty.handler.codec.http.HttpHeaderNames.TE;
|
||||||
import static io.netty.handler.codec.http.HttpHeaderValues.GZIP;
|
import static io.netty.handler.codec.http.HttpHeaderValues.GZIP;
|
||||||
import static io.netty.handler.codec.http.HttpHeaderValues.TRAILERS;
|
import static io.netty.handler.codec.http.HttpHeaderValues.TRAILERS;
|
||||||
@ -143,4 +145,18 @@ public class HttpConversionUtilTest {
|
|||||||
assertEquals(1, out.size());
|
assertEquals(1, out.size());
|
||||||
assertSame("world", out.get("hello"));
|
assertSame("world", out.get("hello"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addHttp2ToHttpHeadersCombinesCookies() throws Http2Exception {
|
||||||
|
Http2Headers inHeaders = new DefaultHttp2Headers();
|
||||||
|
inHeaders.add("yes", "no");
|
||||||
|
inHeaders.add(COOKIE, "foo=bar");
|
||||||
|
inHeaders.add(COOKIE, "bax=baz");
|
||||||
|
|
||||||
|
HttpHeaders outHeaders = new DefaultHttpHeaders();
|
||||||
|
|
||||||
|
HttpConversionUtil.addHttp2ToHttpHeaders(5, inHeaders, outHeaders, HttpVersion.HTTP_1_1, false, false);
|
||||||
|
assertEquals("no", outHeaders.get("yes"));
|
||||||
|
assertEquals("foo=bar; bax=baz", outHeaders.get(COOKIE.toString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user