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调用工程. 如果也有兴趣动手造轮子 ...
随机推荐
- 【竞价网站绝技】根据访客ip,页面显示访客所属城市的html代码(借用YY IP地址库)
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- 代码图片转文本--阿里VS度娘
最近看vue的书,居然没有提供源代码,一堆的CSS真不想手工录入,于是用手机找APP去转.发现广告普遍,于是找找网上相关的API,结果百度和阿里都有在线的API提供,于是好奇其能力如何.如于用以下两图 ...
- Python if __name__ == '__main__':
python属于脚本语言,只能逐行运行, if __name__ == '__main__':这句相当于main(),即首先执行这条语句.
- windows下Apache配置多个站点
1. httpd.conf 找到以下两行去掉注释: # Include conf/extra/httpd-vhosts.conf # LoadModule vhost_alias_module mod ...
- 2000 ASCII码排序
声明:从今天开始每周至少做七道杭电ACM题,锻炼思考能力. 2000 ASCII码排序 Problem Description 输入三个字符后,按各字符的ASCII码从小到大的顺序输出这三个字符. ...
- 画PCB之电流与线宽的关系
来源:(多图) 超强整理!PCB设计之电流与线宽的关系http://www.51hei.com/bbs/dpj-39134-1.html 关于PCB线宽和电流的经验公式,关系表和软件网上都很多,本文把 ...
- 学习笔记----html的lang属性
lang属性的取值应该遵循 BCP 47 - Tags for Identifying Languages. 单一的 zh 和 zh-CN 均属于废弃用法. 问题主要在于,zh 现在不是语言code了 ...
- JavaScript视频分享,学无止境。
http://blog.sina.com.cn/s/blog_735ed39a0102voy3.html
- 逆水行舟 —— MyBatis
第一轮总结性笔记 这是一个很漫长的过程,我买了套课程,将在日后记录学习笔记,取名为逆水行舟系列 MyBatis的基础 根据MyBatis的官方介绍: 整个测试项目结构如下:使用Maven架构项目 po ...
- FFmpeg开发实战(二):FFmpeg 文件操作
FFmpeg 提供了丰富的API供我们使用,下面我们来讲述一下文件操作相关的API: FFmpeg 删除文件:avpriv_io_delete() FFmpeg 重命名文件:avpriv_io_mov ...