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将回复写回服务器并执行类似于此的消息交换?
- 为什么从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是的,当我在listener中通知operationComplete()时,我看到了io.netty.util.concurrent.DefaultPromise#SUCCESS。事实上,我扩展的Netty的ProxyHandler也在sendtoProxyServer期间附加了一个writeListener,并且它也成功了。 – ram