Netty ProxyHandler writeAndFlush未将响应写入服务器

Netty ProxyHandler writeAndFlush未将响应写入服务器,proxy,netty,handler,ntlmv2,Proxy,Netty,Handler,Ntlmv2,我正在尝试在Netty中实现一个NTLMProxyHandler,它可以执行NTLM消息交换,并使用web代理对客户端进行身份验证 NTLMProxyHandler扩展了Netty的ProxyHandler类。因此,代理处理程序会触发一个初始HTTP请求,并到达我创建的模拟代理服务器。代理服务器读取此请求并以407代理身份验证请求响应 NTLMProxyHandler在客户端读取此响应,并准备新的NTLM Type1Message,然后再次将响应写回服务器。我面临的问题是,尽管调用了channe

我正在尝试在Netty中实现一个NTLMProxyHandler,它可以执行NTLM消息交换,并使用web代理对客户端进行身份验证

NTLMProxyHandler扩展了Netty的ProxyHandler类。因此,代理处理程序会触发一个初始HTTP请求,并到达我创建的模拟代理服务器。代理服务器读取此请求并以407代理身份验证请求响应

NTLMProxyHandler在客户端读取此响应,并准备新的NTLM Type1Message,然后再次将响应写回服务器。我面临的问题是,尽管调用了channel future的成功处理程序,但该请求从未发送到我的代理服务器

我在日志记录中启用了Netty包,但无法理解为什么只有从ntlm代理处理程序第二次写入的响应丢失

我尝试过使用Netty ProxyHandler的sendToProxyServer(msg)以及从channelRead()传递的channelHandlerCtx。在这两种情况下,writeAndFlush都已完成,但响应从未到达服务器,并且服务器超时

是否有人使用channelHandlerCtx将响应写回服务器并执行类似的消息交换

  • 为什么来自ntlm代理处理程序->服务器的初始请求成功,但没有从此ntlm代理处理程序写入连续的响应
  • 在调试过程中,我还看到,即使在编写NTLMMessage1时关闭了代理服务器,writeAndFlush的未来仍然是成功的。为什么writeAndFlush在这种情况下会成功
  • 任何指针都会非常有用。谢谢

    NTLMProxyHandler.java

        import io.netty.buffer.ByteBuf;
        import io.netty.buffer.Unpooled;
        import io.netty.channel.ChannelHandlerContext;
        import io.netty.channel.ChannelPipeline;
        import io.netty.handler.codec.http.DefaultFullHttpRequest;
        import io.netty.handler.codec.http.DefaultFullHttpResponse;
        import io.netty.handler.codec.http.FullHttpResponse;
        import io.netty.handler.codec.http.HttpClientCodec;
        import io.netty.handler.codec.http.HttpContent;
        import io.netty.handler.codec.http.HttpHeaderNames;
        import io.netty.handler.codec.http.HttpHeaders;
        import io.netty.handler.codec.http.HttpMethod;
        import io.netty.handler.codec.http.HttpResponse;
        import io.netty.handler.codec.http.HttpResponseStatus;
        import io.netty.handler.codec.http.HttpVersion;
        import io.netty.handler.codec.http.LastHttpContent;
        import io.netty.handler.proxy.ProxyConnectException;
        import jcifs.ntlmssp.Type1Message;
        import jcifs.ntlmssp.Type2Message;
        import jcifs.ntlmssp.Type3Message;
        import jcifs.smb.NtlmContext;
        import jcifs.smb.NtlmPasswordAuthentication;
        import jcifs.util.Base64;
    
        import org.slf4j.Logger;
        import org.slf4j.LoggerFactory;
    
        import java.net.InetSocketAddress;
        import java.net.SocketAddress;
    
    
        public class NTLMProxyHandler extends AbstractProxyHandler {
    
            private String userName;
            private String password;
            private final static String DOMAIN      = "CORP";
            public static final String NTLM_Prefix = "NTLM";
    
            private static final Logger logger = LoggerFactory.getLogger(NTLMProxyHandler.class);
    
            private static int NTLMV2_FLAGS_TYPE3 = 0xa2888205;
            private HttpResponseStatus status;
            private HttpResponse response;
    
            private NtlmPasswordAuthentication ntlmPasswordAuthentication;
            private NtlmContext ntlmContext;
            private final HttpClientCodec codec = new HttpClientCodec();
    
            public NTLMProxyHandler(SocketAddress proxyAddress) {
                super(proxyAddress);
            }
    
            public NTLMProxyHandler(SocketAddress proxyAddress, String domain, String username, String password) {
                super(proxyAddress);
                setConnectTimeoutMillis(50000);
                this.userName = username;
                this.password = password;
                ntlmPasswordAuthentication = new NtlmPasswordAuthentication(DOMAIN, username, password);
                ntlmContext = new NtlmContext(ntlmPasswordAuthentication, true);
            }
    
            @Override
            public String protocol() {
                return "http";
            }
    
            @Override
            public String authScheme() {
                return "ntlm";
            }
    
            protected void addCodec(ChannelHandlerContext ctx) throws Exception {
                ChannelPipeline p = ctx.pipeline();
                String name = ctx.name();
                p.addBefore(name, (String)null, this.codec);
            }
    
            protected void removeEncoder(ChannelHandlerContext ctx) throws Exception {
                this.codec.removeOutboundHandler();
            }
    
            protected void removeDecoder(ChannelHandlerContext ctx) throws Exception {
                this.codec.removeInboundHandler();
            }
    
            @Override
            protected Object newInitialMessage(ChannelHandlerContext channelHandlerContext) throws Exception {
                InetSocketAddress raddr = this.destinationAddress();
                String rhost;
                if(raddr.isUnresolved()) {
                    rhost = raddr.getHostString();
                } else {
                    rhost = raddr.getAddress().getHostAddress();
                }
    
                String host = rhost + ':' + raddr.getPort();
                DefaultFullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.CONNECT, host, Unpooled.EMPTY_BUFFER, false);
                req.headers().set(HttpHeaderNames.HOST, host);
                req.headers().set("connection", "keep-alive");
    
    // This initial request successfully reaches the server !
                return req;
            }
    
            @Override
            protected boolean handleResponse(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
    
                if (o instanceof HttpResponse) {
                     response = (HttpResponse) o;
    
                }
                boolean finished = o instanceof LastHttpContent;
    
                if(finished) {
                    status = response.status();
                    logger.info("Status: " + status);
    
                    if (!response.headers().isEmpty()) {
                        for (String name: response.headers().names()) {
                            for (String value: response.headers().getAll(name)) {
                                logger.debug("Header: " + name + " = " + value);
                            }
                        }
                    }
                    if(status.code() == 407) {
                        negotiate(channelHandlerContext, response);
                    }
                    else if(status.code() == 200){
                        logger.info("Client: NTLM exchange complete. Authenticated !");
                    }
                    else {
                        throw new ProxyConnectException(this.exceptionMessage("status: " + this.status));
                    }
                }
    
                return finished;
            }
    
            private void negotiate(ChannelHandlerContext channelHandlerContext, HttpResponse msg) throws Exception{
                String ntlmHeader = msg.headers().get(HttpHeaderNames.PROXY_AUTHENTICATE);
    
                if(ntlmHeader.equalsIgnoreCase("NTLM")){
                    logger.info("Client: Creating NTLM Type1Message");
                    //Send Type1Message
                    byte[] rawType1Message = ntlmContext.initSecContext(new byte[]{}, 0, 0);
                    Type1Message type1Message = new Type1Message(rawType1Message);
    
                    FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
                    String proxyAuthHeader = Base64.encode(type1Message.toByteArray());
                    logger.info("Setting proxyAuthHeader = " + proxyAuthHeader);
                    response.headers().set(HttpHeaders.Names.PROXY_AUTHORIZATION, proxyAuthHeader);
    
                    ByteBuf byteBuf = Unpooled.buffer(rawType1Message.length);
                    byteBuf.writeBytes(response.content());
    
    //This is where the response is lost and never reaches the proxy server
                    sendToProxyServer(byteBuf);
                    // channelHandlerContext.writeAndFlush(response.content));
    
                } else if (ntlmHeader.contains(NTLM_Prefix)) {
                    logger.info("Client: Creating NTLM Type3Message");
                    //Send Type3 Message
    
                }
            }
        }
    

    我终于解决了这个问题。NTLM代理处理程序在响应代理的消息时发送的是FullHTTPResponse而不是FullHTTPRequest。看起来Netty的管道正在丢弃作为响应写入的数据,而日志中没有指出这一点

    DefaultFullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.CONNECT, host, Unpooled.EMPTY_BUFFER, false);
    req.headers().set(HttpHeaderNames.HOST, host);
    req.headers().set(HttpHeaders.Names.PROXY_AUTHORIZATION, "type3message");
    
    sendToProxyServer(req);
    

    您是否将ChannelFutureListener附加到writeAndFlush(…)调用返回的ChannelFuture?这将允许您查看写入是否由于某种原因而失败。@NormanMaurer是的,我这样做了,当侦听器在operationComplete()中收到通知时,我看到io.netty.util.concurrent.DefaultPromise#SUCCESS。事实上,我扩展的Netty的ProxyHandler在sendtoProxyServer期间也附加了一个writeListener,它也成功了。这应该会导致失败。奇怪的