HTTP2 server should not send preface string
Motivation: See: https://github.com/netty/netty/issues/2402 See: https://tools.ietf.org/html/draft-ietf-httpbis-http2-10#section-3.5 Only the client should send the preface string, the server should not. "The server connection header consists of just a SETTINGS frame (Section 6.5) that MUST be the first frame the server sends in the HTTP/2 connection." Modifications: Split out Http2ClientPrefaceWriter and Http2ServerPrefaceReader from Http2FrameEncoder and Http2FrameDecoder. The new channel handlers are added to the pipeline and remove themselves after the preface is written/read. Result: HTTP2 client sends preface, server does not, in compliance with spec.
This commit is contained in:
parent
c66aae3539
commit
ee3f3661f0
@ -25,6 +25,7 @@ import io.netty.handler.codec.http.HttpRequestDecoder;
|
||||
import io.netty.handler.codec.http.HttpResponseEncoder;
|
||||
import io.netty.handler.codec.http2.draft10.connection.Http2ConnectionHandler;
|
||||
import io.netty.handler.codec.http2.draft10.frame.Http2FrameCodec;
|
||||
import io.netty.handler.codec.http2.draft10.frame.decoder.Http2ServerPrefaceReader;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
@ -129,6 +130,7 @@ public abstract class Http2OrHttpChooser extends ByteToMessageDecoder {
|
||||
*/
|
||||
protected void addHttp2Handlers(ChannelHandlerContext ctx) {
|
||||
ChannelPipeline pipeline = ctx.pipeline();
|
||||
pipeline.addLast("http2ServerPrefaceReader", new Http2ServerPrefaceReader());
|
||||
pipeline.addLast("http2FrameCodec", new Http2FrameCodec());
|
||||
pipeline.addLast("http2ConnectionHandler", new Http2ConnectionHandler(true));
|
||||
pipeline.addLast("http2RequestHandler", createHttp2RequestHandler());
|
||||
|
@ -15,16 +15,6 @@
|
||||
|
||||
package io.netty.handler.codec.http2.draft10.connection;
|
||||
|
||||
import static io.netty.handler.codec.http2.draft10.Http2Error.PROTOCOL_ERROR;
|
||||
import static io.netty.handler.codec.http2.draft10.Http2Error.STREAM_CLOSED;
|
||||
import static io.netty.handler.codec.http2.draft10.Http2Exception.format;
|
||||
import static io.netty.handler.codec.http2.draft10.Http2Exception.protocolError;
|
||||
import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.toHttp2Exception;
|
||||
import static io.netty.handler.codec.http2.draft10.connection.Http2Stream.State.HALF_CLOSED_LOCAL;
|
||||
import static io.netty.handler.codec.http2.draft10.connection.Http2Stream.State.HALF_CLOSED_REMOTE;
|
||||
import static io.netty.handler.codec.http2.draft10.connection.Http2Stream.State.OPEN;
|
||||
import static io.netty.handler.codec.http2.draft10.connection.Http2Stream.State.RESERVED_LOCAL;
|
||||
import static io.netty.handler.codec.http2.draft10.connection.Http2Stream.State.RESERVED_REMOTE;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerAdapter;
|
||||
@ -48,6 +38,11 @@ import io.netty.handler.codec.http2.draft10.frame.Http2StreamFrame;
|
||||
import io.netty.handler.codec.http2.draft10.frame.Http2WindowUpdateFrame;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
|
||||
import static io.netty.handler.codec.http2.draft10.Http2Error.*;
|
||||
import static io.netty.handler.codec.http2.draft10.Http2Exception.*;
|
||||
import static io.netty.handler.codec.http2.draft10.connection.Http2ConnectionUtil.*;
|
||||
import static io.netty.handler.codec.http2.draft10.connection.Http2Stream.State.*;
|
||||
|
||||
/**
|
||||
* Handler for HTTP/2 connection state. Manages inbound and outbound flow control for data frames.
|
||||
* Handles error conditions as defined by the HTTP/2 spec and controls appropriate shutdown of the
|
||||
@ -153,7 +148,7 @@ public class Http2ConnectionHandler extends ChannelHandlerAdapter {
|
||||
processHttp2Exception(ctx, (Http2Exception) cause);
|
||||
}
|
||||
|
||||
ctx.fireExceptionCaught(cause);
|
||||
super.exceptionCaught(ctx, cause);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -15,12 +15,6 @@
|
||||
|
||||
package io.netty.handler.codec.http2.draft10.frame.decoder;
|
||||
|
||||
import static io.netty.handler.codec.http2.draft10.Http2Error.PROTOCOL_ERROR;
|
||||
import static io.netty.handler.codec.http2.draft10.Http2Exception.format;
|
||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_HEADER_LENGTH;
|
||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.FRAME_LENGTH_MASK;
|
||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.connectionPrefaceBuf;
|
||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.readUnsignedInt;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
@ -31,6 +25,8 @@ import io.netty.handler.codec.http2.draft10.frame.Http2FrameHeader;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.*;
|
||||
|
||||
/**
|
||||
* Decodes {@link Http2Frame} objects from an input {@link ByteBuf}. The frames that this handler
|
||||
* emits can be configured by providing a {@link Http2FrameUnmarshaller}. By default, the
|
||||
@ -42,14 +38,12 @@ import java.util.List;
|
||||
public class Http2FrameDecoder extends ByteToMessageDecoder {
|
||||
|
||||
private enum State {
|
||||
PREFACE,
|
||||
FRAME_HEADER,
|
||||
FRAME_PAYLOAD,
|
||||
ERROR
|
||||
}
|
||||
|
||||
private final Http2FrameUnmarshaller frameUnmarshaller;
|
||||
private final ByteBuf preface;
|
||||
private State state;
|
||||
private int payloadLength;
|
||||
|
||||
@ -62,23 +56,13 @@ public class Http2FrameDecoder extends ByteToMessageDecoder {
|
||||
throw new NullPointerException("frameUnmarshaller");
|
||||
}
|
||||
this.frameUnmarshaller = frameUnmarshaller;
|
||||
preface = connectionPrefaceBuf();
|
||||
state = State.PREFACE;
|
||||
state = State.FRAME_HEADER;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
try {
|
||||
switch (state) {
|
||||
case PREFACE:
|
||||
processHttp2Preface(ctx, in);
|
||||
if (state == State.PREFACE) {
|
||||
// Still processing the preface.
|
||||
break;
|
||||
}
|
||||
|
||||
// Successfully processed the HTTP2 preface.
|
||||
|
||||
case FRAME_HEADER:
|
||||
processFrameHeader(in);
|
||||
if (state == State.FRAME_HEADER) {
|
||||
@ -104,30 +88,6 @@ public class Http2FrameDecoder extends ByteToMessageDecoder {
|
||||
}
|
||||
}
|
||||
|
||||
private void processHttp2Preface(ChannelHandlerContext ctx, ByteBuf in) throws Http2Exception {
|
||||
int prefaceRemaining = preface.readableBytes();
|
||||
int bytesRead = Math.min(in.readableBytes(), prefaceRemaining);
|
||||
|
||||
// Read the portion of the input up to the length of the preface, if reached.
|
||||
ByteBuf sourceSlice = in.readSlice(bytesRead);
|
||||
|
||||
// Read the same number of bytes from the preface buffer.
|
||||
ByteBuf prefaceSlice = preface.readSlice(bytesRead);
|
||||
|
||||
// If the input so far doesn't match the preface, break the connection.
|
||||
if (bytesRead == 0 || !prefaceSlice.equals(sourceSlice)) {
|
||||
throw format(PROTOCOL_ERROR, "Invalid HTTP2 preface");
|
||||
}
|
||||
|
||||
if ((prefaceRemaining - bytesRead) > 0) {
|
||||
// Wait until the entire preface has arrived.
|
||||
return;
|
||||
}
|
||||
|
||||
// Start processing the first header.
|
||||
state = State.FRAME_HEADER;
|
||||
}
|
||||
|
||||
private void processFrameHeader(ByteBuf in) throws Http2Exception {
|
||||
if (in.readableBytes() < FRAME_HEADER_LENGTH) {
|
||||
// Wait until the entire frame header has been read.
|
||||
|
@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License, version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License. You may obtain a
|
||||
* copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package io.netty.handler.codec.http2.draft10.frame.decoder;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerAdapter;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.http2.draft10.Http2Exception;
|
||||
|
||||
import static io.netty.handler.codec.http2.draft10.Http2Error.*;
|
||||
import static io.netty.handler.codec.http2.draft10.Http2Exception.*;
|
||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.*;
|
||||
|
||||
/**
|
||||
* Reads the initial client preface, then removes itself from the pipeline.
|
||||
* Only the server pipeline should do this.
|
||||
*
|
||||
* https://tools.ietf.org/html/draft-ietf-httpbis-http2-10#section-3.5
|
||||
*/
|
||||
public class Http2ServerPrefaceReader extends ChannelHandlerAdapter {
|
||||
|
||||
private final ByteBuf preface = connectionPrefaceBuf();
|
||||
|
||||
@Override
|
||||
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||
if (preface.isReadable() && msg instanceof ByteBuf) {
|
||||
ByteBuf buf = (ByteBuf) msg;
|
||||
processHttp2Preface(ctx, buf);
|
||||
if (preface.isReadable()) {
|
||||
// More preface left to process.
|
||||
buf.release();
|
||||
return;
|
||||
}
|
||||
}
|
||||
super.channelRead(ctx, msg);
|
||||
}
|
||||
|
||||
private void processHttp2Preface(ChannelHandlerContext ctx, ByteBuf in) throws Http2Exception {
|
||||
int prefaceRemaining = preface.readableBytes();
|
||||
int bytesRead = Math.min(in.readableBytes(), prefaceRemaining);
|
||||
|
||||
// Read the portion of the input up to the length of the preface, if reached.
|
||||
ByteBuf sourceSlice = in.readSlice(bytesRead);
|
||||
|
||||
// Read the same number of bytes from the preface buffer.
|
||||
ByteBuf prefaceSlice = preface.readSlice(bytesRead);
|
||||
|
||||
// If the input so far doesn't match the preface, break the connection.
|
||||
if (bytesRead == 0 || !prefaceSlice.equals(sourceSlice)) {
|
||||
throw format(PROTOCOL_ERROR, "Invalid HTTP2 preface");
|
||||
}
|
||||
|
||||
if (!preface.isReadable()) {
|
||||
// Entire preface has been read, remove ourselves from the pipeline.
|
||||
ctx.pipeline().remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Copyright 2014 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License, version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance with the License. You may obtain a
|
||||
* copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||
* or implied. See the License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package io.netty.handler.codec.http2.draft10.frame.encoder;
|
||||
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerAdapter;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
|
||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.*;
|
||||
|
||||
/**
|
||||
* Sends the initial client preface, then removes itself from the pipeline.
|
||||
* Only the client pipeline should do this.
|
||||
*
|
||||
* https://tools.ietf.org/html/draft-ietf-httpbis-http2-10#section-3.5
|
||||
*/
|
||||
public class Http2ClientPrefaceWriter extends ChannelHandlerAdapter {
|
||||
|
||||
private boolean prefaceWritten;
|
||||
|
||||
public Http2ClientPrefaceWriter() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
// The channel just became active - send the HTTP2 connection preface to the remote
|
||||
// endpoint.
|
||||
sendPreface(ctx);
|
||||
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
||||
// This handler was just added to the context. In case it was handled after
|
||||
// the connection became active, send the HTTP2 connection preface now.
|
||||
sendPreface(ctx);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the HTTP2 connection preface to the remote endpoint, if not already sent.
|
||||
*/
|
||||
private void sendPreface(final ChannelHandlerContext ctx) {
|
||||
if (!prefaceWritten && ctx.channel().isActive()) {
|
||||
prefaceWritten = true;
|
||||
ctx.writeAndFlush(connectionPrefaceBuf()).addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (!future.isSuccess() && ctx.channel().isOpen()) {
|
||||
// The write failed, close the connection.
|
||||
ctx.close();
|
||||
} else {
|
||||
ctx.pipeline().remove(Http2ClientPrefaceWriter.this);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -15,10 +15,7 @@
|
||||
|
||||
package io.netty.handler.codec.http2.draft10.frame.encoder;
|
||||
|
||||
import static io.netty.handler.codec.http2.draft10.frame.Http2FrameCodecUtil.connectionPrefaceBuf;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
|
||||
@ -33,7 +30,6 @@ import io.netty.handler.codec.http2.draft10.frame.Http2Frame;
|
||||
public class Http2FrameEncoder extends MessageToByteEncoder<Http2Frame> {
|
||||
|
||||
private final Http2FrameMarshaller frameMarshaller;
|
||||
private boolean prefaceWritten;
|
||||
|
||||
public Http2FrameEncoder() {
|
||||
this(new Http2StandardFrameMarshaller());
|
||||
@ -46,22 +42,6 @@ public class Http2FrameEncoder extends MessageToByteEncoder<Http2Frame> {
|
||||
this.frameMarshaller = frameMarshaller;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||
// The channel just became active - send the HTTP2 connection preface to the remote
|
||||
// endpoint.
|
||||
sendPreface(ctx);
|
||||
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
||||
// This handler was just added to the context. In case it was handled after
|
||||
// the connection became active, send the HTTP2 connection preface now.
|
||||
sendPreface(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, Http2Frame frame, ByteBuf out)
|
||||
throws Exception {
|
||||
@ -71,22 +51,4 @@ public class Http2FrameEncoder extends MessageToByteEncoder<Http2Frame> {
|
||||
ctx.fireExceptionCaught(t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the HTTP2 connection preface to the remote endpoint, if not already sent.
|
||||
*/
|
||||
private void sendPreface(final ChannelHandlerContext ctx) {
|
||||
if (!prefaceWritten && ctx.channel().isActive()) {
|
||||
prefaceWritten = true;
|
||||
ctx.writeAndFlush(connectionPrefaceBuf()).addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (!future.isSuccess() && ctx.channel().isOpen()) {
|
||||
// The write failed, close the connection.
|
||||
ctx.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -15,7 +15,6 @@
|
||||
*/
|
||||
package io.netty.example.http2.client;
|
||||
|
||||
import static io.netty.util.internal.logging.InternalLogLevel.INFO;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
@ -24,11 +23,14 @@ import io.netty.example.securechat.SecureChatSslContextFactory;
|
||||
import io.netty.handler.codec.http2.draft10.connection.Http2ConnectionHandler;
|
||||
import io.netty.handler.codec.http2.draft10.frame.Http2DataFrame;
|
||||
import io.netty.handler.codec.http2.draft10.frame.Http2FrameCodec;
|
||||
import io.netty.handler.codec.http2.draft10.frame.encoder.Http2ClientPrefaceWriter;
|
||||
import io.netty.handler.ssl.SslHandler;
|
||||
import org.eclipse.jetty.npn.NextProtoNego;
|
||||
|
||||
import javax.net.ssl.SSLEngine;
|
||||
|
||||
import static io.netty.util.internal.logging.InternalLogLevel.*;
|
||||
|
||||
/**
|
||||
* Configures the client pipeline to support HTTP/2 frames.
|
||||
*/
|
||||
@ -50,6 +52,7 @@ public class Http2ClientInitializer extends ChannelInitializer<SocketChannel> {
|
||||
ChannelPipeline pipeline = ch.pipeline();
|
||||
|
||||
pipeline.addLast("ssl", new SslHandler(engine));
|
||||
pipeline.addLast("http2ClientPrefaceWriter", new Http2ClientPrefaceWriter());
|
||||
pipeline.addLast("http2FrameCodec", new Http2FrameCodec());
|
||||
pipeline.addLast("http2FrameLogger", new Http2FrameLogger(INFO));
|
||||
pipeline.addLast("http2ConnectionHandler", new Http2ConnectionHandler(false));
|
||||
|
Loading…
Reference in New Issue
Block a user