使用Netty实现HTTP服务器
关键字:使用Netty实现HTTP服务器,使用Netty实现httpserver,Netty Http server
Netty是一个异步事件驱动的网络应用程序框架用于快速开发可维护的高性能协议服务器和客户端。Netty经过精心设计,具有丰富的协议,如FTP,SMTP,HTTP以及各种二进制和基于文本的传统协议。
Java程序员在开发web应用的时候,截止2018年大多数公司采用的还是servlet规范的那一套来开发的,比如springmvc。虽然在2018年Java程序员们可以选择使用spring5中的webflux,但是这个转变没那么快。然而,基于servlet那一套的springmvc性能很差,如果你厌烦了,你大可以使用netty来实现一个web框架。假设你想使用netty来实现,那么第一步你得会使用Netty启动一个HTTP服务器,下面开始吧。
Netty系列的文章在这里https://www.cnblogs.com/demingblog/p/9912099.html
本文HttpServer的实现目标
本文只是为了演示如何使用Netty来实现一个HTTP服务器,如果要实现一个完整的,那将是十分复杂的。所以,我们只实现最基本的,请求-响应。具体来说是这样的:
1. 启动服务
2. 客户端访问服务器,如:http://localhost:8081/index
3. 服务器返回 : 你请求的uri为:/index
创建server
netty 的api设计非常好,具有通用性,几乎就是一个固定模式的感觉。server端的启动和客户端的启动代码十分相似。启动server的时候指定初始化器,在初始化器中,我们可以放一个一个的handler,而具体业务逻辑处理就是放在这一个个的handler中的。写好的server端代码如下:
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import java.net.InetSocketAddress;
/**
* netty server
* 2018/11/1.
*/
public class HttpServer {
int port ;
public HttpServer(int port){
this.port = port;
}
public void start() throws Exception{
ServerBootstrap bootstrap = new ServerBootstrap();
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup work = new NioEventLoopGroup();
bootstrap.group(boss,work)
.handler(new LoggingHandler(LogLevel.DEBUG))
.channel(NioServerSocketChannel.class)
.childHandler(new HttpServerInitializer());
ChannelFuture f = bootstrap.bind(new InetSocketAddress(port)).sync();
System.out.println(" server start up on port : " + port);
f.channel().closeFuture().sync();
}
}
server端代码就这么多,看起来很长,但是这就是一个样板代码,你需要着重留意的就是childHandler(new HttpServerInitializer());
这一行。如果你对netty还不是十分熟悉,那么你不需要着急把每一行的代码都看懂。这段代码翻译成可以理解的文字是这样的:
1.bootstrap为启动引导器。
2.指定了使用两个时间循环器。EventLoopGroup
3.指定使用Nio模式。(NioServerSocketChannel.class)
4.初始化器为HttpServerInitializer
server启动代码就是这么多,我们注意看 HttpServerInitializer
做了什么。
在HttpServerInitializer 中添加server配置
HttpServerInitializer
其实就是一个ChannelInitializer
,在这里我们可以指定我们的handler。前面我们说过handler是用来承载我们具体逻辑实现代码的地方,我们需要在ChannelInitializer
中加入我们的特殊实现。代码如下:
public class HttpServerInitializer extends ChannelInitializer<SocketChannel>{
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new HttpServerCodec());// http 编解码
pipeline.addLast("httpAggregator",new HttpObjectAggregator(512*1024)); // http 消息聚合器 512*1024为接收的最大contentlength
pipeline.addLast(new HttpRequestHandler());// 请求处理器
}
}
上面代码很简单,需要解释的点如下:
1. channel 代表了一个socket.
2. ChannelPipeline 就是一个“羊肉串”,这个“羊肉串”里边的每一块羊肉就是一个 handler.
handler分为两种,inbound handler,outbound handler 。顾名思义,分别处理 流入,流出。
3. HttpServerCodec 是 http消息的编解码器。
4. HttpObjectAggregator是Http消息聚合器,Aggregator这个单次就是“聚合,聚集”的意思。http消息在传输的过程中可能是一片片的消息片端,所以当服务器接收到的是一片片的时候,就需要HttpObjectAggregator来把它们聚合起来。
5. 接收到请求之后,你要做什么,准备怎么做,就在HttpRequestHandler中实现。
httpserver处理请求
上面的展示了 server端启动的代码,然后又展示了 server端初始化器的代码。下面我们来看看,请求处理的handler的代码:
public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception {
//100 Continue
if (is100ContinueExpected(req)) {
ctx.write(new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1,
HttpResponseStatus.CONTINUE));
}
// 获取请求的uri
String uri = req.uri();
Map<String,String> resMap = new HashMap<>();
resMap.put("method",req.method().name());
resMap.put("uri",uri);
String msg = "<html><head><title>test</title></head><body>你请求uri为:" + uri+"</body></html>";
// 创建http响应
FullHttpResponse response = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,
Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8));
// 设置头信息
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");
//response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
// 将html write到客户端
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
}
}
上面代码的意思,我用注释标明了,逻辑很简单。无论是FullHttpResponse
,还是 ctx.writeAndFlush
都是netty的api,看名知意即可。半蒙半猜之下,也可以看明白这个handler其实是:1 .获取请求uri,2. 组装返回的响应内容,3. 响应对融到客户端。需要解释的是100 Continue的问题:
100 Continue含义
HTTP客户端程序有一个实体的主体部分要发送给服务器,但希望在发送之前查看下服务器是否会接受这个实体,所以在发送实体之前先发送了一个携带100 Continue的Expect请求首部的请求。
服务器在收到这样的请求后,应该用 100 Continue或一条错误码来进行响应。
启动Http服务器,演示效果
上面的代码写完了,看的再多,不如运行起来跑一把。上面的3个类已经包含了我们目标中的服务端的完整代码。我们只需要在main函数中将其启动即可,代码如下:
public class Application {
public static void main(String[] args) throws Exception{
HttpServer server = new HttpServer(8081);// 8081为启动端口
server.start();
}
}
运行这个main函数,在浏览器中访问:http://localhost:8081/index ,http://localhost:8081/text/test 试试看吧。
至此,我们基于Netty的简易的Http服务器实现了(如果可以称作“HTTP服务器”的话)。 假如我们想要实现,访问 /index.html
就返回index.html
页面,访问/productList
就返回“商品列表JSON”,那么我们还需要做请求路由,还要加入JSON序列化支持,还要根据不同的请求类型调整HTTP响应头。本篇就不做展开了,本篇的目标是为了试下一个最简单的Http服务器。源码下载
从被裁到仲裁——劳动仲裁有多难?!
使用Netty实现HTTP服务器
Netty实现心跳机制
Netty开发redis客户端,Netty发送redis命令,netty解析redis消息
Netty系列
spring如何启动的?这里结合spring源码描述了启动过程
SpringMVC是怎么工作的,SpringMVC的工作原理
spring 异常处理。结合spring源码分析400异常处理流程及解决方法
Mybatis Mapper接口是如何找到实现类的-源码分析