在上一章中我們認識了netty潜支,他有三大優(yōu)點:并發(fā)高遍略,傳輸快,封裝好辅肾。在這一章我們來用Netty搭建一個HttpServer,從實際開發(fā)中了解netty框架的一些特性和概念轮锥。
認識Http請求
在動手寫Netty框架之前矫钓,我們先要了解http請求的組成,如下圖:
- HTTP Request 第一部分是包含的頭信息
- HttpContent 里面包含的是數(shù)據(jù)舍杜,可以后續(xù)有多個 HttpContent 部分
- LastHttpContent 標記是 HTTP request 的結束新娜,同時可能包含頭的尾部信息
- 完整的 HTTP request,由1既绩,2概龄,3組成
- HTTP response 第一部分是包含的頭信息
- HttpContent 里面包含的是數(shù)據(jù),可以后續(xù)有多個 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,負責具體的業(yè)務邏輯刀崖,我們先從啟動類說起惊科。
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();
}
}
這個類同上一章中出現(xiàn)的Netty簡易封裝服務器代碼類似,不一樣的是這里使用了多個ChannelHandler亮钦,在這里一一介紹:
- HttpRequestDecoder馆截,用于解碼request
- HttpResponseEncoder,用于編碼response
-
aggregator蜂莉,消息聚合器(重要)蜡娶。為什么能有FullHttpRequest這個東西,就是因為有他映穗,HttpObjectAggregator窖张,如果沒有他,就不會有那個消息是FullHttpRequest的那段Channel蚁滋,同樣也不會有FullHttpResponse宿接。
如果我們將z'h
HttpObjectAggregator(512 * 1024)的參數(shù)含義是消息合并的數(shù)據(jù)大小,如此代表聚合的消息內容長度不超過512kb辕录。 - 添加我們自己的處理接口
完成啟動類之后澄阳,接下來就是我們的業(yè)務處理類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被處理涧黄,這對我們的業(yè)務開發(fā)是不方便的篮昧,而aggregator的作用就在于此。
- 生成response笋妥,這里使用的FullHttpResponse懊昨,同F(xiàn)ullHttpRequest類似,通過這個我們就不用將response拆分成多個channel返回給請求端了春宣。
- 添加header描述length酵颁。這一步是很重要的一步,如果沒有這一步月帝,你會發(fā)現(xiàn)用postman發(fā)出請求之后就一直在刷新躏惋,因為http請求方不知道返回的數(shù)據(jù)到底有多長。
- channel讀取完成之后需要輸出緩沖流嚷辅。如果沒有這一步簿姨,你會發(fā)現(xiàn)postman同樣會一直在刷新。
構建HTTPS服務
? 首先潦蝇,構建HTTPS服務需要證書款熬,那么什么是SSL證書呢?
? SSL 證書就是遵守 SSL協(xié)議攘乒,由受信任的數(shù)字證書頒發(fā)機構CA贤牛,在驗證服務器身份后頒發(fā),具有服務器身份驗證和數(shù)據(jù)傳輸加密功能则酝。
? 也就是說殉簸,HTTPS相比于HTTP服務,能夠防止網(wǎng)絡劫持沽讹,同時具備一定的安全加密作用般卑。
? 一般來說,證書可以在阿里云爽雄、騰訊云這種云服務上申請蝠检。申請下來之后,證書只能用于指定的域名和服務器上挚瘟。
? 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的全部代碼和剖析析苫,希望這篇文章能幫到你兜叨,有問題評論區(qū)溝通。