之前已经提到了Netty
是一个高性能网络通信框架。而Http(超文本传输协议)
是目前互联网应用最广泛的一种网络协议了。了Netty
对Http
提供了非常丰富的支持,让我们可以针对自己的需求实现需要的Http
服务器。
Http 协议
Http
是建立在TCP之上的应用层协议。它分为请求(Request)和响应(Response)两个部分,其中Request由以下部分组成:
Method Request-URI HTTP-Version CRLF
Header CRLF
Header CRLF
Header CRLF
……(n个Header)
CRLF
[ message-body ]
Response由以下部分组成:
HTTP-Version Status-Code Reason-Phrase CRLF
Header CRLF
Header CRLF
Header CRLF
……(n个Header)
CRLF
[ message-body ]
其中CRLF(Carriage-Return Line-Feed)表示回车换行
编写Netty版本的Http服务器
Http
自定义的逻辑控制器HttpHandler.java
:
public class HttpHandler extends SimpleChannelInboundHandler<Object> {
private static final byte[] CONTENT = {'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd'};
private boolean keepAlive;
public HttpHandler() {
super();
System.out.printf("控制器 %s 被创建.\n", this.toString());
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
super.handlerRemoved(ctx);
System.out.printf("控制器 %s 销毁.\n", this.toString());
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
super.channelReadComplete(ctx);
System.out.printf("控制器 %s 读取一个包.\n", this.toString());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.printf("控制器 %s 出现异常.\n", this.toString());
ctx.close();
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof HttpRequest) {
HttpRequest request = (HttpRequest) msg;
if (request.getMethod() != HttpMethod.GET) {
throw new IllegalStateException("请求不是GET请求.");
}
if (HttpHeaders.is100ContinueExpected(request)) {
ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE));
}
keepAlive = HttpHeaders.isKeepAlive(request);
}
if(msg instanceof LastHttpContent){
// 模拟事务处理
TimeUnit.SECONDS.sleep(1);
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(CONTENT));
response.headers().set(CONTENT_TYPE, "text/plain");
response.headers().set(CONTENT_LENGTH, response.content().readableBytes());
if (!keepAlive) {
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
} else {
response.headers().set(CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
ctx.writeAndFlush(response);
}
}
}
}
这上面的逻辑非常简单,服务器接收到一个请求之后,发送一个『Hello World』的消息给客户端。
每一个客户端连接到server上会被分配到对应的Handler上处理数据。Netty
的设计中把Request
分为了HttpRequest
和HttpContent
两个部分。而由于担心HttpContent内容过长(例如上传文件这种场景),HttpContent
又被分成了普通的HttpContent
和LastHttpContent
两个部分,这些消息的处理放到Handler中。
Http初始化组件HttpInitializer.java
:
public class HttpInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
// 获取通道
ChannelPipeline p = channel.pipeline();
// 添加http加解码器
p.addLast(new HttpServerCodec());
p.addLast(new HttpHandler());
}
}
这里使用Netty自带的Http编解码组件HttpServerCodec
对通信数据进行编解码,然后加入自定义的Handler
组件,处理自定义的业务逻辑。
Server
服务器启动程序HttpServer.java
:
public class HttpServer {
private static final int PORT = 8888;
private ServerBootstrap serverBootstrap;
private EventLoopGroup bossGroup = new NioEventLoopGroup(1);
private EventLoopGroup workerGroup = new NioEventLoopGroup();
public void open() throws InterruptedException {
serverBootstrap = new ServerBootstrap();
serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new HttpInitializer());
Channel ch = serverBootstrap.bind(PORT).sync().channel();
System.err.printf("访问地址 http://127.0.0.1:%d/'", PORT);
ch.closeFuture().sync();
}
public void close() {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
public static void main(String args[]) {
HttpServer server = new HttpServer();
try {
server.open();
} catch (InterruptedException e) {
System.out.println("出错了!");
}
server.close();
}
}
在main()
方法里面会启动服务器,然后Netty就会开始监听8888
端口的消息。
安装一个http客户端
写好了Http服务器的server,还需要一个客户端程序来测试Netty。这里我会使用httpie
作为HTTP
测试客户端。
在OSX上安装:
brew install httpie
在Linux系统上安装:
# Debian-based系列的系统比如:Ubuntu安装
$ apt-get install httpie
# RPM-based系列的系统安装
$ yum install httpie
在命令行使用http
命令就可以发出请求。使用--verbose
参数可以打印Request
的HTTP
信息
➜ ~ http --verbose get httpie.org
GET / HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Host: httpie.org
User-Agent: HTTPie/0.9.2
HTTP/1.1 302 Found
Connection: close
Content-Length: 292
Content-Type: text/html; charset=iso-8859-1
Date: Wed, 20 Apr 2016 07:10:53 GMT
Location: https://github.com/jkbrzt/httpie
Server: Apache/2.2.15 (CentOS)
X-Awesome: Thanks for trying HTTPie :)
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html><head>
<title>302 Found</title>
</head><body>
<h1>Found</h1>
<p>The document has moved <a href="https://github.com/jkbrzt/httpie">here</a>.</p>
<hr>
<address>Apache/2.2.15 (CentOS) Server at httpie.org Port 80</address>
</body></html>
Http的server就构建完成了。