用netty实现一个简单的http server

netty是java网络框架中非常有名,因为它把各种网络类型,阻塞非阻塞的都做了上层的抽象,非常值得学习,这边就先以一个简单的http server来做下实践,
主体的server代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public static void main(String[] args) throws InterruptedException {

// boss 线程组
EventLoopGroup boss = new NioEventLoopGroup();
// worker 线程组
EventLoopGroup work = new NioEventLoopGroup();
try {
// 服务端启动类
ServerBootstrap bootstrap = new ServerBootstrap();
// 绑定线程组
bootstrap.group(boss, work)
// 设置服务端的tcp连接的最大排队连接数
.option(ChannelOption.SO_BACKLOG, 1024)
// 设置日志处理器
.handler(new LoggingHandler(LogLevel.DEBUG))
// 绑定Channel
.channel(NioServerSocketChannel.class)
// 实际的逻辑handler
.childHandler(new HttpServerInitializer());

// 绑定端口
ChannelFuture f = bootstrap.bind(new InetSocketAddress(8082)).sync();
System.out.println("server start up on port 8080");
f.channel().closeFuture().sync();
} finally {
boss.shutdownGracefully();
work.shutdownGracefully();
}
}

然后来看下 HttpServerInitializer

1
2
3
4
5
6
7
8
9
10
11
12
13
public class HttpServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
// 在这个Channel的pipeline上添加处理器
// 前三个都是netty提供的
pipeline.addLast(new HttpServerCodec());
pipeline.addLast(new HttpContentCompressor((CompressionOptions[]) null ));
pipeline.addLast(new HttpServerExpectContinueHandler());
// 这个是我们自己实现的逻辑
pipeline.addLast(new SimpleHttpServerHandler());
}
}

实际的代码也是很简略,我们用一个hello world统一响应所有的请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public class SimpleHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> {
private static final byte[] CONTENT = { 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd' };

@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
// 刷写响应
ctx.flush();
}

@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpObject msg) throws Exception {
// 先是从这个Channel读取到内容
System.out.println("channel read");
if (msg instanceof HttpRequest) {
// 判断是否为HttpRequest,这个其实依赖于前面netty自带的编码器
HttpRequest httpRequest = (HttpRequest) msg;
boolean keepAlive = HttpUtil.isKeepAlive(httpRequest);

// 包装http响应
FullHttpResponse response = new DefaultFullHttpResponse(httpRequest.protocolVersion(), OK, Unpooled.wrappedBuffer(CONTENT));

// 设置请求头
response.headers()
.set(CONTENT_TYPE, TEXT_PLAIN)
.setInt(CONTENT_LENGTH, response.content().readableBytes());

// 设置是否要连接保活
if (keepAlive) {
if (!httpRequest.protocolVersion().isKeepAliveDefault()) {
response.headers().set(CONNECTION, KEEP_ALIVE);
}
} else {
response.headers().set(CONNECTION, CLOSE);
}

// 写入channelFuture
ChannelFuture future = channelHandlerContext.write(response);

// 如果不保活,添加监听器
if (!keepAlive) {
future.addListener(ChannelFutureListener.CLOSE);
}
}
}

@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}

上面就是这个简单的示例了,只是我在 channelRead0 入口建了个打印,但是这个再接收请求时会打印两次,可以作为个探讨题,我们下次来分析下