Netty实现简单HTTP代理服务器
自上次使用Openresty+Lua+Nginx的来加速自己的网站,用上了比较时髦的技术,感觉算是让自己的网站响应速度达到极限了,直到看到了Netty,公司就是打算用Netty来替代Openresty这一套,所以,自己也学了好久,琢磨了好一趟才知道怎么用,现在用来写一套HTTP代理服务器吧,之后再测试一下性能。
之前相关的文章如下:
【网页加速】lua redis的二次升级
使用Openresty加快网页速度
一、Netty中的HTTP
参考自《Netty实战》
一个完整的HttpRequest请求
FullHttpRequest:
- HTTP Request 第一部分是包含的头信息
- HttpContent 里面包含的是数据,可以后续有多个 HttpContent 部分
- LastHttpContent 标记是 HTTP request 的结束,同时可能包含头的尾部信息
- 完整的 HTTP request
一个完整的HttpResponse请求
FullHttpResponse:
- HTTP response 第一部分是包含的头信息
- HttpContent 里面包含的是数据,可以后续有多个 HttpContent 部分
- LastHttpContent 标记是 HTTP response 的结束,同时可能包含头的尾部信息
- 完整的 HTTP response
二、Netty实现HTTP代理服务器的流程
在实现Http代理服务器之前,我们先来查看一下Netty实现代理服务器的完整流程:
Netty的Http服务的流程是:
1、Client向Server发送http请求,在通常的情况中,client一般指的是浏览器,也可以由自己用netty实现一个客户端。此时,客户端需要用到HttpRequestEncoder将http请求进行编码。
2、Server端对http请求进行解析,服务端中,需要用到HttpRequestDecoder来对请求进行解码,然后实现自己的业务需求。
3、Server端向client发送http响应,处理完业务需求后,将相应的内容,用HttpResponseEncoder进行编码,返回数据。
4、Client对http响应进行解析,用HttpResponseDecoder进行解码。
而Netty实现Http代理服务器的过程跟上面的所说无意,只不过是在自己的业务层增加了回源到tomcat服务器这一过程。结合上自己之前实现过的用OpenResty+Nginx来做代理服务器这一套,此处的Netty实现的过程也与此类似。此处粘贴一下OpenResty+Nginx实现的流程图:
而使用了Netty之后,便是将中间的OpenResty+Nginx换成了Netty,下面我们来看一下具体的实现过程。
三、主要代码如下:
HttpServer
public class HttpServer {
public void start(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.handler(new LoggingHandler(LogLevel.DEBUG))
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
// server端发送的是httpResponse,所以要使用HttpResponseEncoder进行编码
ch.pipeline().addLast(
new HttpResponseEncoder());
// server端接收到的是httpRequest,所以要使用HttpRequestDecoder进行解码
ch.pipeline().addLast(
new HttpRequestDecoder());
ch.pipeline().addLast(
new HttpServerHandler());
//增加自定义实现的Handler
ch.pipeline().addLast(new HttpServerCodec());
}
}).option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = b.bind(port).sync();
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
HttpServer server = new HttpServer();
server.start(8080);
}
}
HttpServerHandler
@Slf4j
public class HttpServerHandler extends ChannelInboundHandlerAdapter {
private RedisUtil redisUtil = new RedisUtil();
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
if (msg instanceof HttpRequest) {
DefaultHttpRequest request = (DefaultHttpRequest) msg;
String uri = request.uri();
if ("/favicon.ico".equals(uri)) {
return;
}
log.info(new Date().toString());
Jedis jedis = redisUtil.getJedis();
String s = jedis.get(uri);
if (s == null || s.length() == 0) {
//这里我们的处理是回源到tomcat服务器进行抓取,然后
//将抓取的内容放回到redis里面
try {
URL url = new URL("http://119.29.188.224:8080" + uri);
log.info(url.toString());
URLConnection urlConnection = url.openConnection();
HttpURLConnection connection = (HttpURLConnection) urlConnection;
connection.setRequestMethod("GET");
//连接
connection.connect();
//得到响应码
int responseCode = connection.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader
(connection.getInputStream(), StandardCharsets.UTF_8));
StringBuilder bs = new StringBuilder();
String l;
while ((l = bufferedReader.readLine()) != null) {
bs.append(l).append("\n");
}
s = bs.toString();
}
jedis.set(uri, s);
connection.disconnect();
} catch (Exception e) {
log.error("", e);
return;
}
}
jedis.close();
FullHttpResponse response = new DefaultFullHttpResponse(
HTTP_1_1, OK, Unpooled.wrappedBuffer(s != null ? s
.getBytes() : new byte[0]));
response.headers().set(CONTENT_TYPE, "text/html");
response.headers().set(CONTENT_LENGTH,
response.content().readableBytes());
response.headers().set(CONNECTION, HttpHeaderValues.KEEP_ALIVE);
ctx.write(response);
ctx.flush();
} else {
//这里必须加抛出异常,要不然ab测试的时候一直卡住不动,暂未解决
throw new Exception();
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
ctx.close();
}
}
四、性能测试
下面的是ab测试,在1GHz、2G内存的centos7机器(阿里云服务器)下进行测试,测试命令ab -c 100 -n 10000 localhost:8000/,并发数为100,总数为10000。
性能:
整体响应时间的分布比(单位:ms):
看完之后,我自己也震惊了,Netty实现的不仅稳定、吞吐率还比OpenResty的高出一倍,OpenResty的居然还有那么多的失败次数,不知是不是我的代码的问题还是测试例子不规范,至今,我还是OpenResty的脑残粉。总体的来说,Netty实现的服务器性能还是比较强的,不仅能够快速地开发高性能的面向协议的服务器和客户端,还可以在Netty上轻松实现各种自定义的协议。
五、源码地址
https://github.com/Zephery/myway
参考:
- 《Netty实战》
- 基于Netty4构建HTTP服务----浏览器访问和Netty客户端访问
Netty实现简单HTTP代理服务器的更多相关文章
- netty系列之:小白福利!手把手教你做一个简单的代理服务器
目录 简介 代理和反向代理 netty实现代理的原理 实战 总结 简介 爱因斯坦说过:所有的伟大,都产生于简单的细节中.netty为我们提供了如此强大的eventloop.channel通过对这些简单 ...
- Netty学习——基于netty实现简单的客户端聊天小程序
Netty学习——基于netty实现简单的客户端聊天小程序 效果图,聊天程序展示 (TCP编程实现) 后端代码: package com.dawa.netty.chatexample; import ...
- .Net Core 3.0后台使用httpclient请求网络网页和图片_使用Core3.0做一个简单的代理服务器
目标:使用.net core最新的3.0版本,借助httpclient和本机的host域名代理,实现网络请求转发和内容获取,最终显示到目标客户端! 背景:本人在core领域是个新手,对core的使用不 ...
- netty 实现简单的rpc调用
yls 2020/5/23 netty 实现简单rpc准备 使用netty传输java bean对象,可以使用protobuf,也可以通过json转化 客户端要将调用的接口名称,方法名称,参数列表的类 ...
- netty的简单的应用例子
一.简单的聊天室程序 public class ChatClient { public static void main(String[] args) throws InterruptedExcept ...
- Netty心跳简单Demo
前面简单地了解了一下IdleStateHandler,我们现在写一个简单的心跳demo: 1)服务器端每隔5秒检测服务器端的读超时,如果5秒没有接受到客户端的写请求,也就说服务器端5秒没有收到读事件, ...
- Netty构建游戏服务器(三)--netty spring简单整合
一,基本方法 上节实现了netty的基本连接,这节加入spring来管理netty,由spring来开启netty服务. 在netty服务器中,我们建立了三个类:HelloServer(程序主入口) ...
- Netty+WebSocket简单实现网页聊天
基于Netty+WebSocket的网页聊天简单实现 一.pom依赖 <dependency> <groupId>io.netty</groupId> ...
- Java使用Netty实现简单的RPC
造一个轮子,实现RPC调用 在写了一个Netty实现通信的简单例子后,萌发了自己实现RPC调用的想法,于是就开始进行了Netty-Rpc的工作,实现了一个简单的RPC调用工程. 如果也有兴趣动手造轮子 ...
随机推荐
- linux_基本命令使用(后续更新)
安装文件上传下载快捷键 --> rz/sz yum -y install lrzsz 获取进程名.进程号以及用户ID netstat –nlpt 修改主机名(重启后永久生效)-->cent ...
- Java程序设计(第二版)复习 第三章
数组的使用 首先定义,然后用new生成数组,最后通过下标访问 定义 此时只是引用还未分配内存空间,需要使用new去分配内存空间,否则是无法被访问的 定义的两种方法:数据类型 数组名[];数据类型 [] ...
- 爸爸在家庭中最应该扮演的角色,是爸爸本爸!zz
不然呢?还是爸爸应该cosplay什么物种?细想下,爸爸这个角色很多人是不称职的,经常加班或完全不管孩子的隐形人.肆意把脾气撒在孩子身上的炸弹君.动不动就不耐烦的刺猬......孩子经常挂在嘴边的不是 ...
- VS2015配置OpenCV,使用mfc摄像头程序测试
转自:https://blog.csdn.net/Lee_Dk/article/details/80466523 这只是介绍了如何加入OpenCV,怎么查找OpenCV请看出处. 新建一个项目.找到属 ...
- Win10专业版激活
转载来自:http://www.zhuangjiba.com/bios/3432.html 如何激活win10正式版图文解说 打开开始菜单,找到设置,点开“更新和安全”,切换到“激活”选项卡,查看到当 ...
- 基于面向方面和UML的实时系统建模研究
一.基本信息 标题:基于面向方面和UML的实时系统建模研究 时间:2010 出版源:计算机技术与发展 领域分类:面向方向:实时系统:横切关注点:统一建模语言: 二.研究背景 问题定义:实时系统建模研究 ...
- 07arguments对象.html
07arguments对象.html <!DOCTYPE html> <html lang="en"> <head> <meta char ...
- Teradata Delete Database and Drop Database
DELETE DATABASE and DELETE USER statements delete all data tables, views, and macros from a database ...
- 使用Python多渠道打包apk
使用Python生成多渠道包 往apk包中追加到一个空文件到META-INF目录以标识渠道,Android中获取此文件即可获得App的下载渠道 首先在info文件夹新建一个qdb.txt的空文本文件 ...
- JAVA泛型实现原理
1. Java范型时编译时技术,在运行时不包含范型信息,仅仅Class的实例中包含了类型参数的定义信息.泛型是通过java编译器的称为擦除(erasure)的前端处理来实现的.你可以(基本上就是)把它 ...