Netty入门2之----手动搭建HttpServer
在上一章中我们认识了netty,他有三大优点:并发高,传输快,封装好。在这一章我们来用Netty搭建一个HttpServer,从实际开发中了解netty框架的一些特性和概念。
认识Http请求
在动手写Netty框架之前,我们先要了解http请求的组成,如下图:
HTTP response component parts
- HTTP response 第一部分是包含的头信息
- HttpContent 里面包含的是数据,可以后续有多个 HttpContent 部分
- LastHttpContent 标记是 HTTP response 的结束,同时可能包含头的尾部信息
- 完整的 HTTP response,由1,2,3组成
从request的介绍我们可以看出来,一次http请求并不是通过一次对话完成的,他中间可能有很次的连接。通过上一章我们队netty的了解,每一次对话都会建立一个channel,并且一个ChannelInboundHandler一般是不会同时去处理多个Channel的。
如何在一个Channel里面处理一次完整的Http请求?这就要用到我们上图提到的FullHttpRequest,我们只需要在使用netty处理channel的时候,只处理消息是FullHttpRequest的Channel,这样我们就能在一个ChannelHandler中处理一个完整的Http请求了。
开始动手
搭建一个Netty服务器,我们只需要两个类——一个是启动类,负责启动(BootStrap)和main方法,一个是ChannelHandler,负责具体的业务逻辑,我们先从启动类说起。
package com.dz.netty.http;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
/**
* Created by RoyDeng on 17/7/20.
*/
public class HttpServer {
private final int port;
public HttpServer(int port) {
this.port = port;
}
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println(
"Usage: " + HttpServer.class.getSimpleName() +
" <port>");
return;
}
int port = Integer.parseInt(args[0]);
new HttpServer(port).start();
}
public void start() throws Exception {
ServerBootstrap b = new ServerBootstrap();
NioEventLoopGroup group = new NioEventLoopGroup();
b.group(group)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
System.out.println("initChannel ch:" + ch);
ch.pipeline()
.addLast("decoder", new HttpRequestDecoder()) // 1
.addLast("encoder", new HttpResponseEncoder()) // 2
.addLast("aggregator", new HttpObjectAggregator(512 * 1024)) // 3
.addLast("handler", new HttpHandler()); // 4
}
})
.option(ChannelOption.SO_BACKLOG, 128) // determining the number of connections queued
.childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE);
b.bind(port).sync();
}
}
这个类同上一章中出现的Netty简易封装服务器代码类似,不一样的是这里使用了多个ChannelHandler,在这里一一介绍:
- HttpRequestDecoder,用于解码request
- HttpResponseEncoder,用于编码response
- aggregator,消息聚合器(重要)。为什么能有FullHttpRequest这个东西,就是因为有他,HttpObjectAggregator,如果没有他,就不会有那个消息是FullHttpRequest的那段Channel,同样也不会有FullHttpResponse。
如果我们将z'h
HttpObjectAggregator(512 * 1024)的参数含义是消息合并的数据大小,如此代表聚合的消息内容长度不超过512kb。 - 添加我们自己的处理接口
完成启动类之后,接下来就是我们的业务处理类HttpHandler了,先上代码:
package com.dz.netty.http;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.AsciiString;
/**
* Created by RoyDeng on 17/7/20.
*/
public class HttpHandler extends SimpleChannelInboundHandler<FullHttpRequest> { // 1
private AsciiString contentType = HttpHeaderValues.TEXT_PLAIN;
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception {
System.out.println("class:" + msg.getClass().getName());
DefaultFullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,
Unpooled.wrappedBuffer("test".getBytes())); // 2
HttpHeaders heads = response.headers();
heads.add(HttpHeaderNames.CONTENT_TYPE, contentType + "; charset=UTF-8");
heads.add(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes()); // 3
heads.add(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
ctx.write(response);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("channelReadComplete");
super.channelReadComplete(ctx);
ctx.flush(); // 4
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("exceptionCaught");
if(null != cause) cause.printStackTrace();
if(null != ctx) ctx.close();
}
}
该段代码需要注意的地方如注释所示,有以下四点:
- Handler需要声明泛型为<FullHttpRequest>,声明之后,只有msg为FullHttpRequest的消息才能进来。
由于泛型的过滤比较简单,我们就不改代码来验证了,但是在这里我们可以利用泛型的特性另外做个小测试,将泛型去掉,并且将HttpServer中.addLast("aggregator", new HttpObjectAggregator(512 * 1024)) // 3
这一行代码注释掉,然后观察注释前后的log。
注释前:
initChannel ch:[id: 0xcb9d8e9e, L:/0:0:0:0:0:0:0:1:8888 - R:/0:0:0:0:0:0:0:1:58855]
class:io.netty.handler.codec.http.HttpObjectAggregator$AggregatedFullHttpRequest
channelReadComplete
注释后:
initChannel ch:[id: 0xc5415409, L:/0:0:0:0:0:0:0:1:8888 - R:/0:0:0:0:0:0:0:1:58567]
class:io.netty.handler.codec.http.DefaultHttpRequest
class:io.netty.handler.codec.http.LastHttpContent$1
channelReadComplete
channelReadComplete
从中可以看出,如果没有aggregator,那么一个http请求就会通过多个Channel被处理,这对我们的业务开发是不方便的,而aggregator的作用就在于此。
- 生成response,这里使用的FullHttpResponse,同FullHttpRequest类似,通过这个我们就不用将response拆分成多个channel返回给请求端了。
- 添加header描述length。这一步是很重要的一步,如果没有这一步,你会发现用postman发出请求之后就一直在刷新,因为http请求方不知道返回的数据到底有多长。
- channel读取完成之后需要输出缓冲流。如果没有这一步,你会发现postman同样会一直在刷新。
构建HTTPS服务
首先,构建HTTPS服务需要证书,那么什么是SSL证书呢?
SSL 证书就是遵守 SSL协议,由受信任的数字证书颁发机构CA,在验证服务器身份后颁发,具有服务器身份验证和数据传输加密功能。
也就是说,HTTPS相比于HTTP服务,能够防止网络劫持,同时具备一定的安全加密作用。
一般来说,证书可以在阿里云、腾讯云这种云服务上申请。申请下来之后,证书只能用于指定的域名和服务器上。
netty有提供SSL加密的工具包,只需要通过添加SslHandler,就能快速搭建。基于上面的代码,我们重新定义一个ChannelInitializer。
public class SSLChannelInitializer extends ChannelInitializer<SocketChannel> {
private final SslContext sslContext;
public SSLChannelInitializer() {
String keyStoreFilePath = "/root/.ssl/test.pkcs12";
String keyStorePassword = "Password@123";
try {
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream(keyStoreFilePath), keyStorePassword.toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
sslContext = SslContextBuilder.forServer(keyManagerFactory).build();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
protected void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
SSLEngine sslEngine = sslContext.newEngine(ch.alloc());
pipeline
.addLast(new SslHandler(sslEngine))
.addLast("decoder", new HttpRequestDecoder())
.addLast("encoder", new HttpResponseEncoder())
.addLast("aggregator", new HttpObjectAggregator(512 * 1024))
.addLast("handler", new HttpHandler());
;
}
}
以上就是我通过netty做http服务器demo的全部代码和剖析,希望这篇文章能帮到你,有问题评论区沟通。
Netty入门2之----手动搭建HttpServer的更多相关文章
- Netty入门教程——认识Netty
什么是Netty? Netty 是一个利用 Java 的高级网络的能力,隐藏其背后的复杂性而提供一个易于使用的 API 的客户端/服务器框架. Netty 是一个广泛使用的 Java 网络编程框架(N ...
- 手动搭建I/O网络通信框架2:Socket和ServerSocket入门实战,实现单聊
第一章:手动搭建I/O网络通信框架1:Socket和ServerSocket入门实战,实现单聊 在第一章中运用Socket和ServerSocket简单的实现了网络通信.这一章,利用BIO编程模型进行 ...
- vue新手入门之使用vue框架搭建用户登录注册案例,手动搭建webpack+Vue项目(附源码,图文详解,亲测有效)
前言 本篇随笔主要写了手动搭建一个webpack+Vue项目,掌握相关loader的安装与使用,包括css-loader.style-loader.vue-loader.url-loader.sass ...
- OceanBase数据库实践入门——手动搭建OceanBase集群
前言 目前有关OceanBase功能.案例.故事的文章已经很多,对OceanBase感兴趣的朋友都想安装一个数据库试试.本文就是分享初学者如何手动搭建一个OceanBase集群.这也是学习理解Ocea ...
- 手动搭建I/O网络通信框架1:Socket和ServerSocket入门实战,实现单聊
资料:慕课网 第二章:手动搭建I/O网络通信框架2:Socket和ServerSocket入门实战,实现单聊 这个基础项目会作为BIO.NIO.AIO的一个前提,后面会有数篇博客会基于这个小项目利用B ...
- Netty 系列(三)Netty 入门
Netty 系列(三)Netty 入门 Netty 是一个提供异步事件驱动的网络应用框架,用以快速开发高性能.高可靠性的网络服务器和客户端程序.更多请参考:Netty Github 和 Netty中文 ...
- 手动搭建I/O网络通信框架4:AIO编程模型,聊天室终极改造
第一章:手动搭建I/O网络通信框架1:Socket和ServerSocket入门实战,实现单聊 第二章:手动搭建I/O网络通信框架2:BIO编程模型实现群聊 第三章:手动搭建I/O网络通信框架3:NI ...
- Netty入门与实战教程总结分享
前言:都说Netty是Java程序员必须要掌握的一项技能,带着不止要知其然还要知其所以然的目的,在慕课上找了一个学习Netty源码的教程,看了几章后着实有点懵逼.虽然用过Netty,并且在自己的个人网 ...
- netty入门(一)
1. netty入门(一) 1.1. 传统socket编程 在任何时候都可能有大量的线程处于休眠状态,只是等待输入或者输出数据就绪,这可能算是一种资源浪费. 需要为每个线程的调用栈都分配内存,其默认值 ...
随机推荐
- UML-5-进化式需求
1.需求管理定义 瀑布式式中,研发之前,完全定义和固化需求. 但,需求是不断变化的,你之前可能会有45%的需求,不会被使用到,经常使用到的只占20%左右. 因此,如何寻找这20%的需求,是重点.其方法 ...
- 关于cg语言中求法向量 N=mul(worldMatrix_IT,normal); 的随笔
解释一下标题,N是变换到世界坐标后的法向量,worldMatrix_IT是变换矩阵worldMatrix的逆的转置矩阵,normal就是模型坐标的法向量. 对于点p,我们根据变换矩阵M(即worldM ...
- Fiddler模拟发送post请求
fiddler在进行接口测试时,会模拟post请求,发送不同的请求参数,返回不同的结果,今天我们就来分享一下,怎么用Fiddler工具模拟post请求: 打开Fiddler工具,在右侧点击“compo ...
- PHP如何判断对象为空的方法分享
1.网上很多方法分享说用empty()方法判断对象是否为空.empty()的方法介绍:格式:bool empty ( mixed var )功能:检查一个变量是否为空返回值:若变量不存在则返回 TRU ...
- 【CSS】 布局之多列等高
这两天看了不少文章,对于css布局多了一些理解,现在来总结下. 我们来写一个最普遍的Top.Left.Content.Right.Foot布局. 第一步:自然是写一个坯子 <!DOCTYPE H ...
- JavaScript的六种数据类型
JavaScript数据类型有六种:number.string.boolean.null.undefined.object
- ASP.NET MVC4 新手入门教程特别篇之一----Code First Migrations更新数据库结构(数据迁移)修改Entity FrameWork 数据结构(不删除数据)
背景 code first起初当修改model后,要持久化至数据库中时,总要把原数据库给删除掉再创建(DropCreateDatabaseIfModelChanges),此时就会产生一个问题,当我们的 ...
- Java - 谨慎覆盖clone
覆盖clone时需要实现Cloneable接口,Cloneable并没有定义任何方法. 那Cloneable的意义是什么? 如果一个类实现了Clonable,Object的clone方法就可以返回该对 ...
- 二:Vim常用命令
一般模式下的命令: -- 插入命令 i 光标前插入 I 当前行开始 o 下一行 O 上一行插入新行 a 光标后插入 A 当前行末尾 -- 定位命令 :set nu 显示行号 :set nonu 取消行 ...
- Servlet开发(三)之ServletConfig,ServletContext
1. ServletConfig Servlet是开发动态web的技术,而web.xml是Tomcat工程中最基础也最重要的配置文件,Tomcat启动项目的时候会加载并读取这个文件,其中web.xml ...