DefaultHttp2Headers should throw exception of type Http2Exception
Motivation: The DefaultHttp2Headers code is throwing a IllegalArgumentException if an invalid character is detected. This is being ignored by the HTTP/2 codec instead of generating a GOAWAY. Modifications: - Throw a Http2Exception of type PROTOCOL_ERROR in accordance with https://tools.ietf.org/html/rfc7540#section-8.1.2.6 - Update examples which were building invalid headers Result: More compliant with https://tools.ietf.org/html/rfc7540#section-8.1.2.6
This commit is contained in:
parent
2a27d581a9
commit
1d4d5fe312
|
@ -14,6 +14,8 @@
|
|||
*/
|
||||
package io.netty.handler.codec.http2;
|
||||
|
||||
import static io.netty.handler.codec.http2.Http2Exception.connectionError;
|
||||
import static io.netty.handler.codec.http2.Http2Error.PROTOCOL_ERROR;
|
||||
import io.netty.handler.codec.ByteStringValueConverter;
|
||||
import io.netty.handler.codec.DefaultHeaders;
|
||||
import io.netty.handler.codec.Headers;
|
||||
|
@ -25,19 +27,26 @@ public class DefaultHttp2Headers extends DefaultHeaders<ByteString> implements H
|
|||
private static final ByteProcessor HTTP2_NAME_VALIDATOR_PROCESSOR = new ByteProcessor() {
|
||||
@Override
|
||||
public boolean process(byte value) throws Exception {
|
||||
if (value >= 'A' && value <= 'Z') {
|
||||
throw new IllegalArgumentException("name must be all lower case but found: " + (char) value);
|
||||
}
|
||||
return true;
|
||||
return value < 'A' || value > 'Z';
|
||||
}
|
||||
};
|
||||
private static final NameValidator<ByteString> HTTP2_NAME_VALIDATOR = new NameValidator<ByteString>() {
|
||||
@Override
|
||||
public void validateName(ByteString name) {
|
||||
final int index;
|
||||
try {
|
||||
name.forEachByte(HTTP2_NAME_VALIDATOR_PROCESSOR);
|
||||
} catch (Exception e) {
|
||||
index = name.forEachByte(HTTP2_NAME_VALIDATOR_PROCESSOR);
|
||||
} catch (Http2Exception e) {
|
||||
PlatformDependent.throwException(e);
|
||||
return;
|
||||
} catch (Throwable t) {
|
||||
PlatformDependent.throwException(connectionError(PROTOCOL_ERROR, t,
|
||||
"unexpected error. invalid header name [%s]", name));
|
||||
return;
|
||||
}
|
||||
|
||||
if (index != -1) {
|
||||
PlatformDependent.throwException(connectionError(PROTOCOL_ERROR, "invalid header name [%s]", name));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -65,7 +65,7 @@ public class DefaultHttp2HeadersTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
@Test(expected = Http2Exception.class)
|
||||
public void testHeaderNameValidation() {
|
||||
Http2Headers headers = newHeaders();
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ public final class Http2ExampleUtil {
|
|||
/**
|
||||
* Response header sent in response to the http->http2 cleartext upgrade request.
|
||||
*/
|
||||
public static final String UPGRADE_RESPONSE_HEADER = "Http-To-Http2-Upgrade";
|
||||
public static final String UPGRADE_RESPONSE_HEADER = "http-to-http2-upgrade";
|
||||
|
||||
/**
|
||||
* Size of the block to be read from the input stream.
|
||||
|
|
|
@ -25,7 +25,9 @@ import io.netty.handler.codec.http.DefaultFullHttpRequest;
|
|||
import io.netty.handler.codec.http.FullHttpRequest;
|
||||
import io.netty.handler.codec.http.HttpHeaderNames;
|
||||
import io.netty.handler.codec.http.HttpHeaderValues;
|
||||
import io.netty.handler.codec.http.HttpScheme;
|
||||
import io.netty.handler.codec.http2.Http2SecurityUtil;
|
||||
import io.netty.handler.codec.http2.HttpConversionUtil;
|
||||
import io.netty.handler.ssl.ApplicationProtocolConfig;
|
||||
import io.netty.handler.ssl.ApplicationProtocolConfig.Protocol;
|
||||
import io.netty.handler.ssl.ApplicationProtocolConfig.SelectedListenerFailureBehavior;
|
||||
|
@ -37,9 +39,9 @@ import io.netty.handler.ssl.SslContextBuilder;
|
|||
import io.netty.handler.ssl.SslProvider;
|
||||
import io.netty.handler.ssl.SupportedCipherSuiteFilter;
|
||||
import io.netty.handler.ssl.util.InsecureTrustManagerFactory;
|
||||
import io.netty.util.AsciiString;
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static io.netty.handler.codec.http.HttpMethod.GET;
|
||||
|
@ -106,16 +108,17 @@ public final class Http2Client {
|
|||
|
||||
HttpResponseHandler responseHandler = initializer.responseHandler();
|
||||
int streamId = 3;
|
||||
URI hostName = URI.create((SSL ? "https" : "http") + "://" + HOST + ':' + PORT);
|
||||
HttpScheme scheme = SSL ? HttpScheme.HTTPS : HttpScheme.HTTP;
|
||||
AsciiString hostName = new AsciiString(HOST + ':' + PORT);
|
||||
System.err.println("Sending request(s)...");
|
||||
if (URL != null) {
|
||||
// Create a simple GET request.
|
||||
FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, URL);
|
||||
request.headers().add(HttpHeaderNames.HOST, hostName);
|
||||
request.headers().add(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), scheme.name());
|
||||
request.headers().add(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.GZIP);
|
||||
request.headers().add(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.DEFLATE);
|
||||
channel.writeAndFlush(request);
|
||||
responseHandler.put(streamId, channel.newPromise());
|
||||
responseHandler.put(streamId, channel.writeAndFlush(request), channel.newPromise());
|
||||
streamId += 2;
|
||||
}
|
||||
if (URL2 != null) {
|
||||
|
@ -123,10 +126,10 @@ public final class Http2Client {
|
|||
FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, POST, URL2,
|
||||
Unpooled.copiedBuffer(URL2DATA.getBytes(CharsetUtil.UTF_8)));
|
||||
request.headers().add(HttpHeaderNames.HOST, hostName);
|
||||
request.headers().add(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), scheme.name());
|
||||
request.headers().add(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.GZIP);
|
||||
request.headers().add(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.DEFLATE);
|
||||
channel.writeAndFlush(request);
|
||||
responseHandler.put(streamId, channel.newPromise());
|
||||
responseHandler.put(streamId, channel.writeAndFlush(request), channel.newPromise());
|
||||
streamId += 2;
|
||||
}
|
||||
responseHandler.awaitResponses(5, TimeUnit.SECONDS);
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
package io.netty.example.http2.helloworld.client;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelPromise;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
|
@ -22,6 +23,7 @@ import io.netty.handler.codec.http.FullHttpResponse;
|
|||
import io.netty.handler.codec.http2.HttpConversionUtil;
|
||||
import io.netty.util.CharsetUtil;
|
||||
|
||||
import java.util.AbstractMap.SimpleEntry;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.SortedMap;
|
||||
|
@ -33,22 +35,23 @@ import java.util.concurrent.TimeUnit;
|
|||
*/
|
||||
public class HttpResponseHandler extends SimpleChannelInboundHandler<FullHttpResponse> {
|
||||
|
||||
private SortedMap<Integer, ChannelPromise> streamidPromiseMap;
|
||||
private SortedMap<Integer, Entry<ChannelFuture, ChannelPromise>> streamidPromiseMap;
|
||||
|
||||
public HttpResponseHandler() {
|
||||
streamidPromiseMap = new TreeMap<Integer, ChannelPromise>();
|
||||
streamidPromiseMap = new TreeMap<Integer, Entry<ChannelFuture, ChannelPromise>>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an association between an anticipated response stream id and a {@link io.netty.channel.ChannelPromise}
|
||||
*
|
||||
* @param streamId The stream for which a response is expected
|
||||
* @param writeFuture A future that represent the request write operation
|
||||
* @param promise The promise object that will be used to wait/notify events
|
||||
* @return The previous object associated with {@code streamId}
|
||||
* @see HttpResponseHandler#awaitResponses(long, java.util.concurrent.TimeUnit)
|
||||
*/
|
||||
public ChannelPromise put(int streamId, ChannelPromise promise) {
|
||||
return streamidPromiseMap.put(streamId, promise);
|
||||
public Entry<ChannelFuture, ChannelPromise> put(int streamId, ChannelFuture writeFuture, ChannelPromise promise) {
|
||||
return streamidPromiseMap.put(streamId, new SimpleEntry<ChannelFuture, ChannelPromise>(writeFuture, promise));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,10 +62,17 @@ public class HttpResponseHandler extends SimpleChannelInboundHandler<FullHttpRes
|
|||
* @see HttpResponseHandler#put(int, io.netty.channel.ChannelPromise)
|
||||
*/
|
||||
public void awaitResponses(long timeout, TimeUnit unit) {
|
||||
Iterator<Entry<Integer, ChannelPromise>> itr = streamidPromiseMap.entrySet().iterator();
|
||||
Iterator<Entry<Integer, Entry<ChannelFuture, ChannelPromise>>> itr = streamidPromiseMap.entrySet().iterator();
|
||||
while (itr.hasNext()) {
|
||||
Entry<Integer, ChannelPromise> entry = itr.next();
|
||||
ChannelPromise promise = entry.getValue();
|
||||
Entry<Integer, Entry<ChannelFuture, ChannelPromise>> entry = itr.next();
|
||||
ChannelFuture writeFuture = entry.getValue().getKey();
|
||||
if (!writeFuture.awaitUninterruptibly(timeout, unit)) {
|
||||
throw new IllegalStateException("Timed out waiting to write for stream id " + entry.getKey());
|
||||
}
|
||||
if (!writeFuture.isSuccess()) {
|
||||
throw new RuntimeException(writeFuture.cause());
|
||||
}
|
||||
ChannelPromise promise = entry.getValue().getValue();
|
||||
if (!promise.awaitUninterruptibly(timeout, unit)) {
|
||||
throw new IllegalStateException("Timed out waiting for response on stream id " + entry.getKey());
|
||||
}
|
||||
|
@ -82,8 +92,8 @@ public class HttpResponseHandler extends SimpleChannelInboundHandler<FullHttpRes
|
|||
return;
|
||||
}
|
||||
|
||||
ChannelPromise promise = streamidPromiseMap.get(streamId);
|
||||
if (promise == null) {
|
||||
Entry<ChannelFuture, ChannelPromise> entry = streamidPromiseMap.get(streamId);
|
||||
if (entry == null) {
|
||||
System.err.println("Message received for unknown stream id " + streamId);
|
||||
} else {
|
||||
// Do stuff with the message (for now just print it)
|
||||
|
@ -95,7 +105,7 @@ public class HttpResponseHandler extends SimpleChannelInboundHandler<FullHttpRes
|
|||
System.out.println(new String(arr, 0, contentLength, CharsetUtil.UTF_8));
|
||||
}
|
||||
|
||||
promise.setSuccess();
|
||||
entry.getValue().setSuccess();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user