我想用netty编写一个简单的程序来代理浏览器发送的http请求。 我认为它可以分为3个步骤
问题:
这是我学习netty的第一天,所以请尽量轻松回答。非常感谢你。
public class Server {
public static void main(String[] args) throws InterruptedException {
final int port = 8888;
// copy from https://github.com/netty/netty/wiki/User-guide-for-4.x
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new HttpRequestDecoder(), new HttpServerRequestHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
// Bind and start to accept incoming connections.
ChannelFuture f = b.bind(port).sync();
// Wait until the server socket is closed.
// In this example, this does not happen, but you can do that to gracefully
// shut down your server.
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
}
public class HttpServerRequestHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// step 1 get data from browser
if (msg instanceof LastHttpContent) {
ctx.close();
return;
}
DefaultHttpRequest httpMessage = (DefaultHttpRequest) msg;
System.out.println("浏览器请求====================");
System.out.println(msg);
System.out.println();
doWork(ctx, httpMessage);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
private void doWork(ChannelHandlerContext ctx, final DefaultHttpRequest msg) {
// step 2 send data to website
// translate url into host and port
String host = msg.uri();
int port = 80;
if (host.startsWith("https://")) {
host = host.replaceFirst("https://", "");
port = 443;
} else if (host.startsWith("http://")) {
host = host.replaceFirst("http://", "");
port = 80;
}
if (host.contains(":443")) {
host = host.replace(":443", "");
port = 443;
}
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(workerGroup);
b.channel(NioSocketChannel.class);
//b.option(ChannelOption.AUTO_READ, true);
b.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new HttpServerResponseHandler(msg), new HttpRequestEncoder());
}
});
// question 1
ChannelFuture f = b.connect(host, port).sync();
//ChannelFuture f = b.connect("www.baidu.com", 443).sync();
f.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
}
}
}
public class HttpServerResponseHandler extends ChannelOutboundHandlerAdapter {
private Object httpMessage;
public HttpServerResponseHandler(Object o) {
this.httpMessage = o;
}
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
System.out.println("网页请求结果=========================");
System.out.println(httpMessage);
System.out.println();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (4)
cause.printStackTrace();
ctx.close();
}
@Override
public void connect(ChannelHandlerContext ctx, SocketAddress remoteAddress,
SocketAddress localAddress, ChannelPromise promise) throws Exception {
System.out.println("connect !!!!!!!!!!!");
// question 2
ctx.writeAndFlush(httpMessage);
}
}
答案 0 :(得分:2)
巧合的是,我也一直在研究Netty代理服务器以便学习。我已经在GitHub找到了一个完整的代码,但我会在这里回答您的问题。 Netty还有一个官方代理服务器示例here,但与我的代码不同,他们没有单元测试。
(仅供参考,我的代码在Kotlin中)。
核心理念:
创建代理服务器时,您需要一台服务器来接受客户端请求,以及远程客户端的代理服务器。 您创建了服务器,但没有创建客户端。最好重用服务器创建的EventLoop
,而不是为客户端创建新的Channel
。每个事件循环都在专用线程上运行,因此创建更多线程会产生额外的线程,在接受的Channel
和客户端HttpMessage
之间交换数据时需要进行上下文切换。
如何将网址翻译成主机和端口
为了简单起见,我使用HttpObjectAggregator将HttpContents
及其后续FullHttpRequest
汇总到一个FullHttpResponse
或SocketAddress
(取决于它是否用于处理请求或响应)。设置URL很简单:只需拨打FullHttpRequest.setUri即可。
要获取主机和端口,请在客户端通道上调用Channel.remoteAddress()并将生成的InetSocketAddress
强制转换为Host
,您可以从中获取主机和端口。如果存在,请不要忘记同样重置{{1}}标题。
如何获取响应数据
建立客户端频道(您已经缺失)后,您需要在该频道上发出请求。客户端通道具有一个处理程序,其中包含对原始服务器通道的引用。处理程序收到响应后,会将其写入服务器通道。