Netty ProxyHandler writeAndFlush没有写回应到服务器

问题描述:

我试图在Netty中实现一个NTLMProxyHandler,它可以执行NTLM消息交换并使用Web代理验证客户端。Netty ProxyHandler writeAndFlush没有写回应到服务器

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

NTLMProxyHandler在客户端读取此响应并准备新的NTLM Type1Message并将响应再次写回服务器。我面临的问题是,尽管未来的通道成功处理程序被调用,但此请求永远不会发送到我的代理服务器。

我已经在日志中启用Netty包,但无法弄清楚为什么只有第二次从ntlm代理处理程序写入的响应丢失。

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

是否有人使用channelHandlerCtx将回复写回服务器并执行类似于此的消息交换?

  1. 为什么从ntlm代理处理程序 - >服务器的初始请求成功,但不是从此ntlm代理处理程序写入的连续响应。
  2. 我也在调试时看到,即使我在写入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 

      } 
     } 
    } 
+0

您是否将ChannelFutureListener附加到由writeAndFlush(...)调用返回的ChannelFuture?这将允许您查看写入是否由于某种原因失败。 –

+0

@NormanMaurer是的,当我在listener中通知operationComplete()时,我看到了io.netty.util.concurrent.DefaultPromise#SUCCESS。事实上,我扩展的Netty的ProxyHandler也在sendtoProxyServer期间附加了一个writeListener,并且它也成功了。 – ram

我终于想通了这个问题。响应代理消息时,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); 
+0

这应该导致失败。奇怪 –