Java 使用Netty构建在同一端口上同时处理HTTP和HTTPS的MITM代理时出现问题

Java 使用Netty构建在同一端口上同时处理HTTP和HTTPS的MITM代理时出现问题,java,proxy,netty,Java,Proxy,Netty,我正在尝试学习如何使用Netty构建MITM代理。我的目标是让代理在同一端口上处理HTTP和HTTPS。为简单起见,对于任何入站消息,代理将只响应helloworld消息。我做了很多研究,但我仍然难以实现我的目标 下面是我写的相关代码 // HTTP/HTTPS proxy service handler public class InboundFrontHandler extends ByteToMessageDecoder { private static final HttpRes

我正在尝试学习如何使用Netty构建MITM代理。我的目标是让代理在同一端口上处理HTTP和HTTPS。为简单起见,对于任何入站消息,代理将只响应helloworld消息。我做了很多研究,但我仍然难以实现我的目标

下面是我写的相关代码

// HTTP/HTTPS proxy service handler
public class InboundFrontHandler extends ByteToMessageDecoder {
    private static final HttpResponse CONNECT_RESPONSE = new DefaultHttpResponse( HttpVersion.HTTP_1_1,
            new HttpResponseStatus( 200, "Connection established") );

    @Override
    protected void decode( ChannelHandlerContext context, ByteBuf in, List<Object> out ) {
        log.info("Decoding message.");
        // Will use the first five bytes to detect a protocol.
        if (in.readableBytes() < 5) {
            return;
        }

        log.info("Received message: {}", in.toString(StandardCharsets.UTF_8));

        ChannelPipeline pipeline = context.pipeline();

        if (SslHandler.isEncrypted( in ) ) {
            log.info("message is encrypted.");
            if( pipeline.get( SSL_HANDLER ) == null ) {   // SSL_HANDLER: String
                log.info("no ssl handler available.");
                if( pipeline.get( HTTP_CODEC_HANDLER ) == null ) { // HTTP_CODEC_HANDLER: String
                    log.info("no http codec available");
                    pipeline.addLast(SSL_HANDLER, getSslHandler());
                    pipeline.addLast( HTTP_CODEC_HANDLER, new HttpServerCodec());
                    pipeline.addLast( DEFLATER_HANDLER, new HttpContentCompressor()); // DEFLATER_HANDLER: String
                } else {
                    log.info("http codec available");
                    pipeline.addBefore( HTTP_CODEC_HANDLER, SSL_HANDLER, getSslHandler());
                }
            }
        } else {
            log.info("message is not encrypted");
            if( pipeline.get( HTTP_CODEC_HANDLER ) == null ) {
                pipeline.addLast( HTTP_CODEC_HANDLER, new HttpServerCodec());
                pipeline.addLast( DEFLATER_HANDLER, new HttpContentCompressor());
            }
            if( isConnect( in ) ) {
                context.write( CONNECT_RESPONSE );
                log.info("responded CONNECT method with {}", CONNECT_RESPONSE);
                return;
            }
        }

        if( pipeline.get(HW_HANDLER) == null) {  // HW_HANDLER: String
            pipeline.addLast( HW_HANDLER, new HttpHandler());
        }
    }

    private boolean isConnect(ByteBuf in) {
        int magic1 = in.getUnsignedByte(in.readerIndex());
        int magic2 = in.getUnsignedByte(in.readerIndex() + 1);
        return magic1 == 'C' && magic2 == 'O';
    }

    private SslHandler getSslHandler() { // get a SslHandler with a self signed certificate in keystore
        SSLEngine engine = null;
        try {
            engine = SslContextFactory.createServerSslContext().createSSLEngine();
            engine.setUseClientMode(false);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return new SslHandler(engine);
    }
}

// Dummy hello-world response generator
public class HttpHandler extends ChannelHandlerAdapter {
    private static final Logger log = LoggerFactory.getLogger( HttpHandler.class );

    @Override
    public void channelReadComplete( ChannelHandlerContext context ) {
        context.flush();
    }

    @Override
    public void channelRead( ChannelHandlerContext context, Object message ) {
        if( message instanceof HttpRequest ) {
            HttpRequest request = (HttpRequest) message;
            log.info(request.toString());

            HttpResponse response = helloWorldResponse();
            log.info( response.toString() );

            context.write( response );
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }

    private HttpResponse helloWorldResponse() {
        byte[] content = "Hello, World".getBytes(StandardCharsets.UTF_8);
        FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
                HttpResponseStatus.OK, Unpooled.wrappedBuffer( content ));
        response.headers().set( HttpHeaders.Names.CONTENT_TYPE, "text/plain");
        response.headers().set( HttpHeaders.Names.CONTENT_LENGTH, response.content().readableBytes());

        response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);

        return response;
    }
}

// Server bootstrap
public class HttpServer {
    final int port = 1119;
    public void run() throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap b = new ServerBootstrap();
            b.option(ChannelOption.SO_BACKLOG, 1024);
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new InboundFrontHandler());
                        }
                    });

            Channel ch = b.bind(port).sync().channel();
            ch.closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
有几件事我不明白。首先,从日志中可以看到,浏览器重试了
CONNECT
请求,表明它没有将代理返回的
CONNECT\u响应
视为其上次连接请求成功。其次,代码给出了以下异常消息:

Caused by: java.lang.IllegalArgumentException: Duplicate handler name: deflater
它是从以下块中抛出的:

        if( pipeline.get( HTTP_CODEC_HANDLER ) == null ) {
            pipeline.addLast( HTTP_CODEC_HANDLER, new HttpServerCodec());
            pipeline.addLast( DEFLATER_HANDLER, new HttpContentCompressor());  // <<< throws duplicate name exception after second CONNECT request
        }
if(pipeline.get(HTTP\u CODEC\u HANDLER)==null){
addLast(HTTP_CODEC_处理程序,新的HttpServerCodec());

pipeline.addLast(DEFLATER_HANDLER,new HttpContentCompressor());//实际上您是在前面有其他处理程序的情况下写入管道的。因此,如果您在
InboundFrontHandler

log.info(context.pipeline().toMap());
context.writeAndFlush(CONNECT_RESPONSE).addListener(new ChannelFutureListener() {
        @Override
        public void operationComplete(ChannelFuture future) throws Exception {
               log.info("WriteandFlush : " + future.isSuccess());
        }
});

the output would be 
INFO  test.InboundFrontHandler  - {InboundFrontHandler#0=test.InboundFrontHandler@1f060469, HttpRequestDecoder#0=io.netty.handler.codec.http.HttpRequestDecoder@1c3c34f6, HttpResponseEncoder#0=io.netty.handler.codec.http.HttpResponseEncoder@7333cf2a, DEFLATER_HANDLER=io.netty.handler.codec.http.HttpContentCompressor@3d7fa45b}
INFO  test.InboundFrontHandler  - WriteandFlush : false
我建议仔细阅读以下示例:

因为在管道、处理程序的使用方式以及写操作的执行方式方面存在很多错误。例如,如果没有刷新,数据将无法传输

Chrome发送了另一个连接请求,因为它没有收到http 200响应。另一个建议是使用
curl
来准确了解发送和接收的数据,这使得调试更加容易 样本:
curl-x127.0.0.1:1119“https://www.google.com/"--trace-

\u@jknair:实际上我自己解决了这个问题。但是你完全正确,我不应该把任何处理程序放在SSL处理程序之前。简而言之,应该发生的是管道最初应该有http处理程序。在收到连接请求时,响应200,然后在http处理程序之前添加SSL处理程序呃。
log.info(context.pipeline().toMap());
context.writeAndFlush(CONNECT_RESPONSE).addListener(new ChannelFutureListener() {
        @Override
        public void operationComplete(ChannelFuture future) throws Exception {
               log.info("WriteandFlush : " + future.isSuccess());
        }
});

the output would be 
INFO  test.InboundFrontHandler  - {InboundFrontHandler#0=test.InboundFrontHandler@1f060469, HttpRequestDecoder#0=io.netty.handler.codec.http.HttpRequestDecoder@1c3c34f6, HttpResponseEncoder#0=io.netty.handler.codec.http.HttpResponseEncoder@7333cf2a, DEFLATER_HANDLER=io.netty.handler.codec.http.HttpContentCompressor@3d7fa45b}
INFO  test.InboundFrontHandler  - WriteandFlush : false