簡介
愛因斯坦說過:所有的偉大,都產生於簡單的細節中。netty為我們提供瞭如此強大的eventloop、channel通過對這些簡單東西的有效利用,可以得到非常強大的應用程式,比如今天要講的代理。
代理和反向代理
相信只要是程式設計師應該都聽過nginx伺服器了,這個超級優秀nginx一個很重要的功能就是做反向代理。那麼有小夥伴要問了,有反向代理肯定就有正向代理,那麼他們兩個有什麼區別呢?
先講一下正向代理,舉個例子,最近流量明星備受打擊,雖然被打壓,但是明星就是明星,一般人是見不到的,如果有人需要跟明星對話的話,需要首先經過明星的經紀人,有經紀人將話轉達給明星。這個經紀人就是正向代理。我們通過正向代理來訪問要訪問的物件。
那麼什麼是反向代理呢?比如現在出現了很多人工智慧,假如我們跟智慧機器人A對話,然後A把我們之間的對話轉給了後面的藏著的人,這個人用他的智慧,回答了我們的對話,交由智慧機器人A輸出,最終實現了人工智慧。這個過程就叫做反向代理。
netty實現代理的原理
那麼在netty中怎麼實現這個代理伺服器呢?
首選我們首先代理伺服器是一個伺服器,所以我們需要在netty中使用ServerBootstrap建立一個伺服器:
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new SimpleDumpProxyInitializer(REMOTE_HOST, REMOTE_PORT))
.childOption(ChannelOption.AUTO_READ, false)
.bind(LOCAL_PORT).sync().channel().closeFuture().sync();
在這個local伺服器中,我們傳入ProxyInitializer。在這個handler初始化器中,我們傳入自定義的handler:
public void initChannel(SocketChannel ch) {
ch.pipeline().addLast(
new LoggingHandler(LogLevel.INFO),
new SimpleDumpProxyInboundHandler(remoteHost, remotePort));
}
在自定義的handler中,我們使用Bootstrap建立一個client,用來連線遠端要代理的伺服器,我們將這個client端的建立放在channelActive方法中:
// 開啟outbound連線
Bootstrap b = new Bootstrap();
b.group(inboundChannel.eventLoop())
.channel(ctx.channel().getClass())
.handler(new SimpleDumpProxyOutboundHandler(inboundChannel))
.option(ChannelOption.AUTO_READ, false);
ChannelFuture f = b.connect(remoteHost, remotePort);
然後在client建立好連線之後,就可以從inboundChannel中讀取資料了:
outboundChannel = f.channel();
f.addListener(future -> {
if (future.isSuccess()) {
// 連線建立完畢,讀取inbound資料
inboundChannel.read();
} else {
// 關閉inbound channel
inboundChannel.close();
}
});
因為是代理服務,所以需要將inboundChannel讀取的資料,轉發給outboundChannel,所以在channelRead中我們需要這樣寫:
public void channelRead(final ChannelHandlerContext ctx, Object msg) {
// 將inboundChannel中的訊息讀取,並寫入到outboundChannel
if (outboundChannel.isActive()) {
outboundChannel.writeAndFlush(msg).addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
// flush成功,讀取下一個訊息
ctx.channel().read();
} else {
future.channel().close();
}
});
}
}
當outboundChannel寫成功之後,再繼續inboundChannel的讀取工作。
同樣對於client的outboundChannel來說,也有一個handler,在這個handler中,我們需要將outboundChannel讀取到的資料反寫會inboundChannel中:
public void channelRead(final ChannelHandlerContext ctx, Object msg) {
// 將outboundChannel中的訊息讀取,並寫入到inboundChannel中
inboundChannel.writeAndFlush(msg).addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
ctx.channel().read();
} else {
future.channel().close();
}
});
}
當inboundChannel寫成功之後,再繼續outboundChannel的讀取工作。
如此一個簡單的代理伺服器就完成了。
實戰
如果我們將本地的8000埠,代理到www.163.com的80埠,會發生什麼情況呢?執行我們的程式,訪問http://localhost:8000, 我們會看到下面的頁面:
為什麼沒有如我們想象的那樣展示正常的頁面呢?那是因為我們代理過去之後的域名是localhost,而不是正常的www.163.com, 所以伺服器端不認識我們的請求,從而報錯。
總結
本文的代理伺服器之間簡單的轉發請求,並不能夠處理上述的場景,那麼該怎麼解決上面的問題呢? 敬請期待我的後續文章!
本文的例子可以參考:learn-netty4
本文已收錄於 http://www.flydean.com/35-netty-simple-proxy/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!