承接前两篇用java原生nio和bio写的https代理服务器,这篇是用netty实现https代理服务器。上篇用nio实现代理服务器时需要自己去控制消息发送和接收的次序,我们就用了一个selector,这个次序是所有注册在我们所有的io channel注册在这个统一的selector上就绪后的次序,那么你就必须自己严格控制客户端到代理服务器的channel和代理服务器到目标服务器的channel的channel读取信息以及写入信息的时机。为什么这么说,因为,在nio中需要你自己去控制读写buffer的起始读取位置及写入位置,这也就需要你去严格控制读取和写入流程及buffer位置的严格把控;而且在使用nio时会发现对channel的准备事件监听不好控制,为什么这么说,因为在很多的情况下,channel都是可写入的如果你一直将selector置为对channel的可写入感兴趣,那么计算机cpu就会占用很高,那么我们就需要自己去在合适的时候去启动selector对channel的可写入感兴趣操作,使之写入内容;当然,在上篇博文中我全程使用的是单线程,如果你要是使用多线程,初始化时生成多个selector,让客户端到代理服务器的channel和代理服务器到目标服务器的channel注册到两个不同的selector上时,那么你还需要去严格控制这两个channel的并发,以防止buffer数据的混乱。

这篇博文就来用netty实现下http(s)代理服务器,大家可以对比下两者使用(这里没有使用netty对http的封装的组件的,使用的还是比较底层的buffer,这里就方便理解netty和对比两者编程的优劣)。代码中我会给我认为比较重要的地方写上注释,然后,再挑一些出来讲讲,我认为你弄懂这些之后会少走很多弯路并且你能够去使用netty去实现http(s)代理服务器了。

public class NettyProxyHttpServer {public static void main(String[] s) {System.out.println("<<<<<<<<<<<<<<<<<");NioEventLoopGroup bossGroup = new NioEventLoopGroup(8);NioEventLoopGroup workGroup = new NioEventLoopGroup(8);ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<Channel>() {@Overrideprotected void initChannel(Channel ch) throws Exception {ch.pipeline().addLast(new NettyProxyServerHandler());}}).option(ChannelOption.SO_BACKLOG, 128)         .childOption(ChannelOption.SO_KEEPALIVE, true);try {b.bind(11111).sync();} catch (InterruptedException e) {e.printStackTrace();}}
}class NettyProxyServerHandler extends ChannelInboundHandlerAdapter{private Map<Channel,Channel> channelMap = new HashMap<>();private Map<Channel,ByteBuf> msgMap = new HashMap<Channel,ByteBuf>();//之所以保留这个map是担心,第一次建立连接时一次性无法获取客户端发来的全部信息NioEventLoopGroup toServerGroup = new NioEventLoopGroup();private Bootstrap bootstrap = new Bootstrap();@SuppressWarnings("rawtypes")public NettyProxyServerHandler() {//原来在这里我是想用serverbootstrap 下的childGroup去注册我自己直接新建的Channel的,结果发现根本注册不了,需要新生成一个bootstrapbootstrap.group(toServerGroup).channel(NioSocketChannel.class).option(ChannelOption.SO_KEEPALIVE, true).handler(new ChannelInitializer() {@Overrideprotected void initChannel(Channel ch) throws Exception {ch.pipeline().addLast("toServer handler", new ToServerHandler(channelMap));}});}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();if(channelMap.containsKey(ctx.channel())) {channelMap.remove(ctx.channel());}}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {Channel channel = ctx.channel();if(channelMap.containsKey(channel)&&channelMap.get(channel) !=null) {Channel toChannel = channelMap.get(channel);toChannel.writeAndFlush(msg);}else {ByteBuf buffer = null;if(msgMap.containsKey(channel)&&msgMap.get(channel)!=null) {buffer = msgMap.get(channel);}else {buffer = ctx.alloc().buffer(1024*2);}buffer.writeBytes((ByteBuf) msg);buffer.retain();msgMap.put(channel, buffer);}}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {final Channel channel = ctx.channel();if(!(channelMap.containsKey(channel)&&channelMap.get(channel)!=null)) {//如果是还没建立连接,需要对目标host和port进行解析if(msgMap.get(channel)!=null) {byte[] b =new byte[msgMap.get(channel).readableBytes()];msgMap.get(channel).getBytes(0, b);String header = new String(b);String[] lineStrs = header.split("\\n");String host="";int port = 80;int type=0;                   //默认是http方式String hostTemp="";for(int i=0 ; i<lineStrs.length ; i++) {         //解析请求头System.out.println(lineStrs[i]);if(i==0) {type = (lineStrs[i].split(" ")[0].equalsIgnoreCase("CONNECT") ? 1 : 0);}else {String[] hostLine = lineStrs[i].split(": ");if(hostLine[0].equalsIgnoreCase("host")) {hostTemp = hostLine[1];}}}if(hostTemp.split(":").length>1) {host = hostTemp.split(":")[0];port = Integer.valueOf(hostTemp.split(":")[1].split("\\r")[0]);}else {host = hostTemp.split(":")[0].split("\\r")[0];}final int requestType = type;ChannelFuture future = bootstrap.connect(host, port).sync();if(future.isSuccess()) {         //建立到目标服务器的连接成功,把两者的连接映射放到map,方便后续使用channelMap.put(channel, future.channel());channelMap.put(future.channel(), channel);if(requestType==1) {      //https请求的话,直接返回给客户端下面这句话就行,客户端会在建立的通道上继续请求。ByteBuf buffer = channel.alloc().buffer("HTTP/1.1 200 Connection Established\r\n\r\n".getBytes().length);buffer.writeBytes("HTTP/1.1 200 Connection Established\r\n\r\n".getBytes());channel.writeAndFlush(buffer);msgMap.get(channel).release();}else {future.channel().writeAndFlush(msgMap.get(channel));msgMap.get(channel).release();}System.out.println("=======连接建立成功");}else {System.out.println("=========connect failing");}}}}
}class ToServerHandler extends ChannelInboundHandlerAdapter{private Map<Channel,Channel> map = null;@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {System.out.println("代理到目标服务器的channel handler出错:");cause.printStackTrace();if(map.containsKey(ctx.channel())) {map.remove(ctx.channel());}}public ToServerHandler(Map<Channel, Channel> map) {this.map = map;}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {Channel channel = ctx.channel();Channel toChannel = map.get(channel);toChannel.writeAndFlush(msg);}}

使用netty 开发的过程中,我印象比较深或说做的比较大的一个改动是下面这段代码,下面我们分析一下。

ByteBuf buffer = channel.alloc().buffer("HTTP/1.1 200 Connection Established\r\n\r\n".getBytes().length);
buffer.writeBytes("HTTP/1.1 200 Connection Established\r\n\r\n".getBytes());
channel.writeAndFlush(buffer);
msgMap.get(channel).release();

也许你不会遇到,因为假如你不会用我这种方式去暂时存放channel读出的信息,但是我觉得很幸运,因为能够有这样的错误来加深一下我的认知——在netty中要想往channel中写入信息,必须通过netty中channel的buffer (这里用的ByteBuf)往channel里写入,而不能直接用byte数组作为媒介,那样是写不进数据的,亲测过,这个坑也是耗费了很长时间的。这里也可以这么写,在实现ChannelInboundAdapter时往这个对象里传入将要写入的目的channel 这样就可以直接使用direct buffer了。我没有改过来的原因是我认为有可能会由于某种原因导致在第一次建立连接时有可能会一次读不完http头部信息,虽然很不可能,但是万一由于网络延迟或服务器硬件繁忙等原因导致一次没读完,那么就无法利用这种方式了。

当然,还有一点不是完美的地方是这里的channel的对应关系都存在于一个map中,其实这个map也可以去掉的,像上面介绍buffer的那样——在实现ChannelInboundAdapter时往这个对象里传入将要写入的目的channel 即可。

          好了,这次netty实现代理服务器就算结束了,大家可以自己试试。有机会,会讲讲netty源码并且自己也试着用nio封装一个类似netty的东西出来

netty初使用——实现http及https代理服务器相关推荐

  1. python搭建https代理服务器_使用NGINX作为HTTPS正向代理服务器

    NGINX主要设计作为反向代理服务器,但随着NGINX的发展,它同样能作为正向代理的选项之一.正向代理本身并不复杂,而如何代理加密的HTTPS流量是正向代理需要解决的主要问题.本文将介绍利用NGINX ...

  2. python实现http/https代理服务器

    代码: # encoding:utf-8 import socket import _threadclass Header:"""用于读取和解析头信息"&quo ...

  3. 如何用Netty实现一个轻量级的HTTP代理服务器

    为什么会想通过Netty构建一个HTTP代理服务器?这也是笔者发表这篇文章的目的所在. 其主要还是源于解决在日常开发测试过程中,一直困扰测试同学很久的一个问题,现在我就来具体阐述一下这个问题. 在日常 ...

  4. springboot集成netty实现代理服务器

    说明 使用netty实现代理服务功能,思路是:客户端发送请求,由netty服务端通过端口监听到请求,然后在内部再开启一个netty客户端作为代理去访问真实的服务器,最后由真实的服务器将响应返回给代理, ...

  5. go语言搭建代理服务器_Go实现Https代理服务

    作者:Zarten知乎专栏:Go开发深入详解知乎ID: Zarten简介: 互联网一线工作者,尊重原创并欢迎评论留言指出不足之处,也希望多些关注和点赞是给作者最好的鼓励 ! 概述 http(s)代理在 ...

  6. 使用nginx作为HTTPS正向代理服务器(七层透传代理、中间人代理)

    [前言] 在讲解nginx正向代理https之前,我们先来解答几个小疑问. 1.nginx是什么? Java同学肯定知道apache服务器,一个很牛,但是也很庞大的web服务器.能当web服务器的不仅 ...

  7. JAVA写HTTP代理服务器(一)-socket实现

    HTTP代理服务器是一种特殊的网络服务,允许一个网络终端(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接.一些网关.路由器等网络设备具备网络代理功能.一般认为代理服务有利于 ...

  8. Fiddler中安装证书进行https协议的抓取

    Fiddler目前默认安装对http协议进行抓取但是对手机以及其他一些是https协议的通讯抓取需要配置. 首先我们要在fiddler中找到菜单栏的Tools > Options,打开" ...

  9. Fiddler抓取HTTPS包

    现在的Android应用程序几乎都会和网络打交道,所以在分析一个apk的时候,如果可以抓取出其发出的数据包,将对分析程序的流程和逻辑有极大的帮助. 对于HTTP包来说,已经有很多种分析的方法了,例如用 ...

最新文章

  1. 只要你敢进来,没有学不会xml滴
  2. cocos2dx-3.9 集成admob
  3. c程序设计语言_习题1-9_将输入流复制到输出流,并将多个空格过滤成一个空格...
  4. python基础教程:数值与字符串类型
  5. 2019.07.11
  6. python变量无需创建赋值_Python 第 2 章 变量及赋值运算符
  7. python 画布包括不了全部组件?_试验程序:画布版九键琴
  8. 铜仁计算机专业学校,听说计算机专业的在本部啊 这是真的么
  9. no route to host什么意思_Day 74:Vue里的route和router
  10. 复数基础——虚数和复数_5
  11. jQuery - 获取内容和属性
  12. Javascript String对象
  13. ActiveMQ菜鸟入门教程
  14. 现场知识竞赛如何用手机做抢答器
  15. android 指纹识别驱动 win10,win10怎么添加指纹识别?Win10 Windows Hello指纹登录设置教程...
  16. Clion解决c++源文件多个编译运行
  17. 2021年焊工(初级)模拟考试及焊工(初级)作业考试题库
  18. COLVERN LM10/3M29电位计春天,宛若初见!
  19. 洛谷-3373 【模板】线段树 2
  20. win10安装配置JDK11

热门文章

  1. CSS布局之——左边、上边固定,内容自适应(Vue)
  2. binlog回滚mysql误操作数据
  3. 4.11 51单片机-LCD1602显示屏
  4. Nginx:12---反向代理之(代理模块,代理单个上游服务器)
  5. 危化品从业人员考试题目及答案
  6. OSChina 周日乱弹 ——苟富贵,勿相忘。
  7. java sdk他edk de区别_EDK笔记——自定义IP核
  8. 超低功耗离线智能语音识别芯片AT6811
  9. 2015 android 5.0 手机排行榜,智能手机排行榜2015前十名 2015智能手机排行榜
  10. 小米红米6Pro解BL锁教程申请BootLoader解锁教程