Fix HttpContentEncoder does not handle multiple Accept-Encoding (#9557)
Motivation: At the current moment HttpContentEncoder handle only first value of multiple accept-encoding headers. Modification: Join multiple accept-encoding headers to one separated by comma. Result: Fixes #9553
This commit is contained in:
parent
3e3a748eb6
commit
bcb0d02248
@ -22,11 +22,14 @@ import io.netty.channel.embedded.EmbeddedChannel;
|
|||||||
import io.netty.handler.codec.DecoderResult;
|
import io.netty.handler.codec.DecoderResult;
|
||||||
import io.netty.handler.codec.MessageToMessageCodec;
|
import io.netty.handler.codec.MessageToMessageCodec;
|
||||||
import io.netty.util.ReferenceCountUtil;
|
import io.netty.util.ReferenceCountUtil;
|
||||||
|
import io.netty.util.internal.StringUtil;
|
||||||
|
|
||||||
import java.util.ArrayDeque;
|
import java.util.ArrayDeque;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
|
|
||||||
|
import static io.netty.handler.codec.http.HttpHeaderNames.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Encodes the content of the outbound {@link HttpResponse} and {@link HttpContent}.
|
* Encodes the content of the outbound {@link HttpResponse} and {@link HttpContent}.
|
||||||
* The original content is replaced with the new content encoded by the
|
* The original content is replaced with the new content encoded by the
|
||||||
@ -71,21 +74,30 @@ public abstract class HttpContentEncoder extends MessageToMessageCodec<HttpReque
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void decode(ChannelHandlerContext ctx, HttpRequest msg, List<Object> out)
|
protected void decode(ChannelHandlerContext ctx, HttpRequest msg, List<Object> out) throws Exception {
|
||||||
throws Exception {
|
CharSequence acceptEncoding;
|
||||||
CharSequence acceptedEncoding = msg.headers().get(HttpHeaderNames.ACCEPT_ENCODING);
|
List<String> acceptEncodingHeaders = msg.headers().getAll(ACCEPT_ENCODING);
|
||||||
if (acceptedEncoding == null) {
|
switch (acceptEncodingHeaders.size()) {
|
||||||
acceptedEncoding = HttpContentDecoder.IDENTITY;
|
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();
|
HttpMethod method = msg.method();
|
||||||
if (HttpMethod.HEAD.equals(method)) {
|
if (HttpMethod.HEAD.equals(method)) {
|
||||||
acceptedEncoding = ZERO_LENGTH_HEAD;
|
acceptEncoding = ZERO_LENGTH_HEAD;
|
||||||
} else if (HttpMethod.CONNECT.equals(method)) {
|
} 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));
|
out.add(ReferenceCountUtil.retain(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -500,6 +500,39 @@ public class HttpContentCompressorTest {
|
|||||||
assertTrue(ch.finishAndReleaseAll());
|
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() {
|
private static FullHttpRequest newRequest() {
|
||||||
FullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/");
|
FullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/");
|
||||||
req.headers().set(HttpHeaderNames.ACCEPT_ENCODING, "gzip");
|
req.headers().set(HttpHeaderNames.ACCEPT_ENCODING, "gzip");
|
||||||
|
@ -17,6 +17,7 @@ package io.netty.util.internal;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static io.netty.util.internal.ObjectUtil.*;
|
import static io.netty.util.internal.ObjectUtil.*;
|
||||||
@ -597,6 +598,36 @@ public final class StringUtil {
|
|||||||
return start == 0 && end == length - 1 ? value : value.subSequence(start, end + 1);
|
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<? extends CharSequence> elements) {
|
||||||
|
ObjectUtil.checkNotNull(separator, "separator");
|
||||||
|
ObjectUtil.checkNotNull(elements, "elements");
|
||||||
|
|
||||||
|
Iterator<? extends CharSequence> 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.
|
* @return {@code length} if no OWS is found.
|
||||||
*/
|
*/
|
||||||
@ -622,4 +653,5 @@ public final class StringUtil {
|
|||||||
private static boolean isOws(char c) {
|
private static boolean isOws(char c) {
|
||||||
return c == SPACE || c == TAB;
|
return c == SPACE || c == TAB;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -534,4 +534,19 @@ public class StringUtilTest {
|
|||||||
assertEquals("", StringUtil.trimOws("\t ").toString());
|
assertEquals("", StringUtil.trimOws("\t ").toString());
|
||||||
assertEquals("a b", StringUtil.trimOws("\ta b \t").toString());
|
assertEquals("a b", StringUtil.trimOws("\ta b \t").toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testJoin() {
|
||||||
|
assertEquals("",
|
||||||
|
StringUtil.join(",", Collections.<CharSequence>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());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user