http代理数据传播路径:

  1. 客户端将请求发送到代理,代理解析出消息目的地再去请求服务器
  2. 服务器将完整结果返回给代理,代理再将结果返回给客户端
  3. 代理就在两者之间进行中转数据

https消息传播模式:

  1. 客户端将请求的目的地端口明文发送到代理,
  2. 代理解析出服务器host 端口,并连接成功,返回客户端连接成功的标识
  3. 客户端知道代理已经连接成功了,开始将ssl握手之类的加密数据发送给代理
  4. 代理就在服务器客户端之间进行转发数据,他并不知道传输的数据到底是什么,因为是加密的

实现方式:

编程语言:java
框架选择:netty

首先创建一个标准的netty启动

EventLoopGroup bossGroup = new NioEventLoopGroup();EventLoopGroup workGroup = new NioEventLoopGroup();try {ServerBootstrap b = new ServerBootstrap();b.group(bossGroup, workGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 6000).childHandler(new ProxyServiceInit());ChannelFuture f = b.bind(PropertiesUtil.getIntProp("start.port")).sync();f.channel().closeFuture().sync();} finally {workGroup.shutdownGracefully();bossGroup.shutdownGracefully();}

向其中添加这么两个handler,其中HttpServerCodec是netty自带的,HttpService是自己实现的

@Overrideprotected void initChannel(Channel channel) throws Exception {ChannelPipeline p = channel.pipeline();p.addLast("httpcode", new HttpServerCodec());p.addLast("httpservice", new HttpService());}

HttpServerCodec会将客户端传进来的消息转成httpobject对象,并且是已经被聚合了的http消息,我们在自己写的HttpService中使用

自定义httpservice 集成simpleinbondhandlerpublic class HttpService extends SimpleChannelInboundHandler<HttpObject> {//保留全局ctxprivate ChannelHandlerContext ctx;//创建一会用于连接web服务器的 Bootstrap   private Bootstrap b = new Bootstrap();//channelActive方法中将ctx保留为全局变量@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {super.channelActive(ctx);this.ctx = ctx;}//Complete方法中刷新数据@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) {ctx.flush();}@Overrideprotected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject msg) throws Exception {if (msg instanceof HttpRequest) {//转成 HttpRequestHttpRequest req = (HttpRequest) msg;if (PasswordChecker.digestLogin(req)) { //检测密码,后面讲HttpMethod method = req.method();  //获取请求方式,http的有get post ..., https的是 CONNECTString headerHost = req.headers().get("Host");    //获取请求头中的Host字段String host = "";int port = 80;                                  //端口默认80String[] split = headerHost.split(":");         //可能有请求是 host:port的情况,host = split[0];                   if (split.length > 1) {  port = Integer.valueOf(split[1]);}Promise<Channel> promise = createPromise(host, port);   //根据host和port创建连接到服务器的连接/*根据是http还是http的不同,为promise添加不同的监听器*/if (method.equals(HttpMethod.CONNECT)) {//如果是https的连接promise.addListener(new FutureListener<Channel>() {@Overridepublic void operationComplete(Future<Channel> channelFuture) throws Exception {//首先向浏览器发送一个200的响应,证明已经连接成功了,可以发送数据了FullHttpResponse resp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, new HttpResponseStatus(200, "OK"));//向浏览器发送同意连接的响应,并在发送完成后移除httpcode和httpservice两个handlerctx.writeAndFlush(resp).addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture channelFuture) throws Exception {ChannelPipeline p = ctx.pipeline();p.remove("httpcode");p.remove("httpservice");}});ChannelPipeline p = ctx.pipeline();//将客户端channel添加到转换数据的channel,(这个NoneHandler是自己写的)p.addLast(new NoneHandler(channelFuture.getNow()));}});} else {//如果是http连接,首先将接受的请求转换成原始字节数据EmbeddedChannel em = new EmbeddedChannel(new HttpRequestEncoder());em.writeOutbound(req);final Object o = em.readOutbound();em.close();promise.addListener(new FutureListener<Channel>() {@Overridepublic void operationComplete(Future<Channel> channelFuture) throws Exception {//移除   httpcode    httpservice 并添加 NoneHandler,并向服务器发送请求的byte数据             ChannelPipeline p = ctx.pipeline();p.remove("httpcode");p.remove("httpservice");//添加handlerp.addLast(new NoneHandler(channelFuture.getNow()));channelFuture.get().writeAndFlush(o);}});}} else {ctx.writeAndFlush(PasswordChecker.getDigest());}} else {ReferenceCountUtil.release(msg);}}//根据host和端口,创建一个连接web的连接private Promise<Channel> createPromise(String host, int port) {final Promise<Channel> promise = ctx.executor().newPromise();b.group(ctx.channel().eventLoop()).channel(NioSocketChannel.class).remoteAddress(host, port).handler(new NoneHandler(ctx.channel())).option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000).connect().addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture channelFuture) throws Exception {if (channelFuture.isSuccess()) {promise.setSuccess(channelFuture.channel());} else {ctx.close();channelFuture.cancel(true);}}});return promise;}}

noneHandler里面只做数据的转发,没有任何逻辑

public class NoneHandler extends ChannelInboundHandlerAdapter {private Channel outChannel;public NoneHandler(Channel outChannel) {this.outChannel = outChannel;}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {//System.out.println("交换数据");outChannel.write(msg);}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {outChannel.flush();}
}

上面我们添加了密码的验证,去掉密码验证部分就能直接使用了,可以先去掉测试一下,添加密码验证放在服务器上更安全
密码验证的逻辑就是,

  1. http(s)请求头中包含我们约定好的密码,如果没有我们就发送一个407响应,这样浏览器(chromr,firefox,ie)就知道代理服务器要验证密码,就会弹出窗口要我们输入密码,

  2. 如果验证成功过一次,下次的请求浏览器会自动带上密码,不用再重新输入

  3. 密码的方式有两种,一种是简单的Basic,密码是明文传输的,一种是digest方式

方式一 basic 方式

如果没有输入密码的情况下,代理返回的响应是FullHttpResponse resp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.PROXY_AUTHENTICATION_REQUIRED);resp.headers().add("Proxy-Authenticate", "Basic realm=\"Text\"");resp.headers().setInt("Content-Length", resp.content().readableBytes());return resp;发送一个 PROXY_AUTHENTICATION_REQUIRED响应,响应头不包含  "Basic realm=\"Text\"",其中 `Text`可以随便起,这样浏览器就能弹窗出来报文可能长这样HTTP/1.0 407 PROXY_AUTHENTICATION_REQUIREDServer: SokEvo/1.0WWW-Authenticate: Basic realm="Text"Content-Type: text/htmlContent-Length: xxx
代理如何验证密码呢?就是将头部的帐号密码解析出来,和我们的帐号密码进行比对
 用户输入帐号密码后,服务器可能收到这样的报文Get /index.html HTTP/1.0Host:www.google.comProxy-Authorization: Basic xxxxxxxxxxxxxxxxxxxxxxxxxxxx //basic方式登录public static boolean basicLogin(HttpRequest req) {//获取请求头中的 Proxy-AuthorizationString s = req.headers().get("Proxy-Authorization");if (s == null) {return false;}//密码的形式是   `Basic 帐号:密码`用冒号拼接在一起,在取base64try {String[] split = s.split(" ");byte[] decode = Base64.decodeBase64(split[1]); //去数组中的第二个,第一个是一个Basic固定的字符String userNamePassWord = new String(decode);String[] split1 = userNamePassWord.split(":", 2);PasswordChecker.basicCheck(split1[0], split1[1]); //比较帐号密码是不是我们自己的帐号密码} catch (Exception e) {e.printStackTrace();return false;}return true;}

方式二 digest方式,

这种方式是,服务器发送一个随机数给浏览器,浏览器用密码和一堆东西混合(用户名,uri之类的)在一起,进行md5加密,将混合的东西传给服务器,
那么服务器以同样的方式进行操作一遍,如果得到的结果与浏览器传上来的相同,那么就证明密码正确,传输阶段,不会暴露帐号密码这个参考维基百科的 http digest 摘要加密方式页面

ok一个带帐号密码控制的http代理服务器开发完成

windows这样配置就可以用proxy上网了,也可以放在服务器上跑

java netty开发一个http/https代理相关推荐

  1. Java后台开发Tomcat添加https支持小程序开发过程

    文章原文:blog.ouyangsihai.cn >> Java后台开发Tomcat添加https支持小程序开发过程 1 给自己的域名申请证书 注意:申请好了如果不是在腾讯注册的域名,不会 ...

  2. 视频教程-用Java从零开始开发一个物联网项目-物联网技术

    用Java从零开始开发一个物联网项目 多年的产品设计和开发经验,带领团队完成多个知名产品.历任多家大型公司的Java架构师,对知名框架的源码均有深入研究.拥有IT一线开发.教学10多年的实战经验,能充 ...

  3. JAVA 多线程 JAVA 如何开发一个自定义线程池

    1.多线程设计介绍 每一个线程的启动和结束都是比较消耗时间和占用资源的. 如果在系统中用到了很多的线程,大量的启动和结束动作会导致系统的性能变卡,响应变慢. 为了解决这个问题,引入线程池这种设计思想. ...

  4. java websocket netty_用SpringBoot集成Netty开发一个基于WebSocket的聊天室

    前言 基于SpringBoot,借助Netty控制长链接,使用WebSocket协议做一个实时的聊天室. 项目效果 项目统一登录路径:http://localhost:8080/chat/netty ...

  5. Java程序开发一个窗体有两个按钮,一个是“开始”按钮,一个是“结束”按钮,当用户点击“开始”按钮时就在控制台打印一句话,反之则结束打印。

    package javase18;import javax.swing.*; import java.awt.*; import java.awt.event.*;public class javas ...

  6. java代下订单管理模块_用java语言开发一个订单管理系统

    管理员登陆窗体(LoginForm):窗体中包含"管理员姓名","管理员密码",按钮:"确定","取消"操作主窗体(Ma ...

  7. Java多线程开发——一个简单的数字加减小例子

    范例: 两个线程实现加法,两个线程实现减法 class Resource {private int num = 0;private boolean flag = true;//flag = true ...

  8. 使用woo 语言开发 sockets4 sockets5 http https代理完整记录

    记录下使用woo语言开发 sockets 和https代理过程 已经在gitee开源: https://gitee.com/oshine/woo_proxy 目前使用的是一个端口监听所有的代理包含ht ...

  9. java awt 简单计算器,JAVA Swing 开发简易计算器(上)

    开发一个简易的计算器我们主要分为两大部分:图形界面设计与具体功能实现.这篇文章讲图形界面设计,下篇讲具体功能实现. 代码下载:https://github.com/taifus/Java_Calcul ...

最新文章

  1. 为你的水晶报表装载本地图片
  2. Google Guava Collections 使用介绍
  3. Ignite 的helloworld第二弹!(附源码!下载即用)
  4. Python爬虫 搜索并下载图片
  5. python实现链表反转_反转链表(Python)
  6. android中资源文件的两种访问方式,在android开发中进行数据存储与访问的多种方式介绍...
  7. A+B问题(信息学奥赛一本通-T1006)
  8. python更换证件照底色
  9. welearn考试切屏会有显示吗_welearn随行课堂班级测试答案
  10. WIFI 认证 测试
  11. 数据链路层------基于TCP/IP五层模型
  12. 汇编语言里 eax ebx ecx edx esi edi ebp esp
  13. 统计|如何理解多元线性回归的F检验的作用与目的
  14. 工业控制系统协议相关的安全问题
  15. 会计科目 分类 说明
  16. 微信公众平台的基础对接
  17. 顺序栈—栈顶指针的两种初始化
  18. 【软件测试】测试与开发一对欢喜冤家......
  19. 线段树的建树 单点修改 区间查询
  20. Hbuilderx 3.3.10更改注释或主题颜色

热门文章

  1. python计算平均绩点_平均绩点的算法?
  2. 快速排序及快速排序的优化(完整版)
  3. 计算机研究生就业方向之当老师(中小学)
  4. 关于“过劳死”的法律探讨
  5. java读写orc文件_java读取hive的orc文件
  6. Google Earth Engine(GEE)——利用sentinel-2数据
  7. 使用VS Code 插件, 快速入门超账Fabric(一) : 知识回顾
  8. 从辍学到名企资深开发工程师,一名 Android 开发者逆袭血泪史
  9. 东软睿道实训日记—第一天
  10. 解决node-sass报错 :Failed at the node-sass postinstall script