diff --git a/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentEncoder.java b/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentEncoder.java index a6ccf28af8..1af4972318 100644 --- a/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentEncoder.java +++ b/codec-http/src/main/java/io/netty/handler/codec/http/HttpContentEncoder.java @@ -24,11 +24,14 @@ import io.netty.channel.embedded.EmbeddedChannel; import io.netty.handler.codec.DecoderResult; import io.netty.handler.codec.MessageToMessageCodec; import io.netty.util.ReferenceCountUtil; +import io.netty.util.internal.StringUtil; import java.util.ArrayDeque; import java.util.List; import java.util.Queue; +import static io.netty.handler.codec.http.HttpHeaderNames.*; + /** * Encodes the content of the outbound {@link HttpResponse} and {@link HttpContent}. * The original content is replaced with the new content encoded by the @@ -73,21 +76,30 @@ public abstract class HttpContentEncoder extends MessageToMessageCodec out) - throws Exception { - CharSequence acceptedEncoding = msg.headers().get(HttpHeaderNames.ACCEPT_ENCODING); - if (acceptedEncoding == null) { - acceptedEncoding = HttpContentDecoder.IDENTITY; + protected void decode(ChannelHandlerContext ctx, HttpRequest msg, List out) throws Exception { + CharSequence acceptEncoding; + List acceptEncodingHeaders = msg.headers().getAll(ACCEPT_ENCODING); + switch (acceptEncodingHeaders.size()) { + case 0: + acceptEncoding = HttpContentDecoder.IDENTITY; + break; + case 1: + acceptEncoding = acceptEncodingHeaders.get(0); + break; + default: + // Multiple message-header fields https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2 + acceptEncoding = StringUtil.join(",", acceptEncodingHeaders); + break; } HttpMethod method = msg.method(); if (HttpMethod.HEAD.equals(method)) { - acceptedEncoding = ZERO_LENGTH_HEAD; + acceptEncoding = ZERO_LENGTH_HEAD; } else if (HttpMethod.CONNECT.equals(method)) { - acceptedEncoding = ZERO_LENGTH_CONNECT; + acceptEncoding = ZERO_LENGTH_CONNECT; } - acceptEncodingQueue.add(acceptedEncoding); + acceptEncodingQueue.add(acceptEncoding); out.add(ReferenceCountUtil.retain(msg)); } diff --git a/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentCompressorTest.java b/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentCompressorTest.java index a31bc5beb8..d0676fd907 100644 --- a/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentCompressorTest.java +++ b/codec-http/src/test/java/io/netty/handler/codec/http/HttpContentCompressorTest.java @@ -500,6 +500,39 @@ public class HttpContentCompressorTest { assertTrue(ch.finishAndReleaseAll()); } + @Test + public void testMultipleAcceptEncodingHeaders() { + FullHttpRequest request = newRequest(); + request.headers().set(HttpHeaderNames.ACCEPT_ENCODING, "unknown; q=1.0") + .add(HttpHeaderNames.ACCEPT_ENCODING, "gzip; q=0.5") + .add(HttpHeaderNames.ACCEPT_ENCODING, "deflate; q=0"); + + EmbeddedChannel ch = new EmbeddedChannel(new HttpContentCompressor()); + + assertTrue(ch.writeInbound(request)); + + FullHttpResponse res = new DefaultFullHttpResponse( + HttpVersion.HTTP_1_1, HttpResponseStatus.OK, + Unpooled.copiedBuffer("Gzip Win", CharsetUtil.US_ASCII)); + assertTrue(ch.writeOutbound(res)); + + assertEncodedResponse(ch); + HttpContent c = ch.readOutbound(); + assertThat(ByteBufUtil.hexDump(c.content()), is("1f8b080000000000000072afca2c5008cfcc03000000ffff")); + c.release(); + + c = ch.readOutbound(); + assertThat(ByteBufUtil.hexDump(c.content()), is("03001f2ebf0f08000000")); + c.release(); + + LastHttpContent last = ch.readOutbound(); + assertThat(last.content().readableBytes(), is(0)); + last.release(); + + assertThat(ch.readOutbound(), is(nullValue())); + assertTrue(ch.finishAndReleaseAll()); + } + private static FullHttpRequest newRequest() { FullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/"); req.headers().set(HttpHeaderNames.ACCEPT_ENCODING, "gzip"); diff --git a/common/src/main/java/io/netty/util/internal/StringUtil.java b/common/src/main/java/io/netty/util/internal/StringUtil.java index a039a288f5..769713f8f0 100644 --- a/common/src/main/java/io/netty/util/internal/StringUtil.java +++ b/common/src/main/java/io/netty/util/internal/StringUtil.java @@ -17,6 +17,7 @@ package io.netty.util.internal; import java.io.IOException; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import static java.util.Objects.requireNonNull; @@ -597,6 +598,36 @@ public final class StringUtil { return start == 0 && end == length - 1 ? value : value.subSequence(start, end + 1); } + /** + * Returns a char sequence that contains all {@code elements} joined by a given separator. + * + * @param separator for each element + * @param elements to join together + * + * @return a char sequence joined by a given separator. + */ + public static CharSequence join(CharSequence separator, Iterable elements) { + ObjectUtil.checkNotNull(separator, "separator"); + ObjectUtil.checkNotNull(elements, "elements"); + + Iterator iterator = elements.iterator(); + if (!iterator.hasNext()) { + return EMPTY_STRING; + } + + CharSequence firstElement = iterator.next(); + if (!iterator.hasNext()) { + return firstElement; + } + + StringBuilder builder = new StringBuilder(firstElement); + do { + builder.append(separator).append(iterator.next()); + } while (iterator.hasNext()); + + return builder; + } + /** * @return {@code length} if no OWS is found. */ @@ -622,4 +653,5 @@ public final class StringUtil { private static boolean isOws(char c) { return c == SPACE || c == TAB; } + } diff --git a/common/src/test/java/io/netty/util/internal/StringUtilTest.java b/common/src/test/java/io/netty/util/internal/StringUtilTest.java index 0d27a74fea..7dd06e7bac 100644 --- a/common/src/test/java/io/netty/util/internal/StringUtilTest.java +++ b/common/src/test/java/io/netty/util/internal/StringUtilTest.java @@ -534,4 +534,19 @@ public class StringUtilTest { assertEquals("", StringUtil.trimOws("\t ").toString()); assertEquals("a b", StringUtil.trimOws("\ta b \t").toString()); } + + @Test + public void testJoin() { + assertEquals("", + StringUtil.join(",", Collections.emptyList()).toString()); + assertEquals("a", + StringUtil.join(",", Collections.singletonList("a")).toString()); + assertEquals("a,b", + StringUtil.join(",", Arrays.asList("a", "b")).toString()); + assertEquals("a,b,c", + StringUtil.join(",", Arrays.asList("a", "b", "c")).toString()); + assertEquals("a,b,c,null,d", + StringUtil.join(",", Arrays.asList("a", "b", "c", null, "d")).toString()); + } + }