SSL 和 TLS 安全協議層疊在其餘協議之上,用以實現數據安全。爲了支持 SSL/TLS,Java 提供了 javax.net.ssl 包,它的 SSLContext 和 SSLEngine 類使得實現解密和加密變得至關簡單。Netty 經過一個名爲 SsLHandler 的 ChannelHandler 實現了這個 API,其中 SSLHandler 在內部使用 SSLEngine 來完成實際工做java
Netty 還提供了基於 OpenSSL 工具包的 SSLEngine 實現,比 JDK 提供的 SSLEngine 具備更好的性能。若是 OpenSSL 可用,能夠將 Netty 應用程序配置爲默認使用 OpenSSLEngine。若是不可用,Netty 將會退回到 JDK 實現web
下述代碼展現瞭如何使用 ChannelInitializer 來將 SslHandler 添加到 ChannelPipeline 中安全
public class SslChannelInitializer extends ChannelInitializer<Channel> { private final SslContext context; private final boolean startTls; public SslChannelInitializer(SslContext context, boolean startTls) { this.context = context; this.startTls = startTls; } @Override protected void initChannel(Channel ch) throws Exception { SSLEngine engine = context.newEngine(ch.alloc()); ch.pipeline().addFirst("ssl", new SslHandler(engine, startTls)); } }
大多數狀況下,Sslhandler 將是 ChannelPipeline 中的第一個 ChannelHandler,這確保了只有在全部其餘的 ChannelHandler 將它們的邏輯應用到數據以後,纔會進行加密服務器
SSLHandler 具備一些有用的方法,如表所示,例如,在握手階段,兩個節點將相互驗證而且商定一種加密方式,你能夠經過配置 SslHandler 來修改它的行爲,或者在 SSL/TLS 握手一旦完成以後提供通知,握手階段以後,全部的數據都將會被加密websocket
方法名稱 | 描述 |
---|---|
setHandshakeTimeout(long, TimeUnit) setHandshakeTimeoutMillis(long) getHandshakeTimeoutMillis() |
設置和獲取超時時間,超時以後,握手 ChannelFuture 將會被通知失敗 |
setCloseNotifyTimeout(long, TimeUnit) setCloseNotifyTimeoutMillis(long) getCloseNotifyTimeoutMillis() |
設置和獲取超時時間,超時以後,將會觸發一個關閉通知並關閉鏈接,這也會致使通知該 ChannelFuture 失敗 |
handshakeFuture() | 返回一個在握手完成後將會獲得通知的 ChannelFuture,若是握手先前已經執行過,則返回一個包含了先前握手結果的 ChannelFuture |
close() close(ChannelPipeline) close(ChannelHandlerContext, ChannelPromise) |
發送 close_notify 以請求關閉並銷燬底層的 SslEngine |
HTTP 是基於請求/響應模式的,客戶端向服務器發送一個 HTTP 請求,而後服務器將會返回一個 HTTP 響應,Netty 提供了多種多種編碼器和解碼器以簡化對這個協議的使用socket
下圖分別展現了生產和消費 HTTP 請求和 HTTP 響應的方法ide
如圖所示,一個 HTTP 請求/響應可能由多個數據部分組成,而且總以一個 LastHttpContent 部分做爲結束工具
下表概要地介紹了處理和生成這些消息的 HTTP 解碼器和編碼器性能
名稱 | 描述 |
---|---|
HttpRequestEncoder | 將 HTTPRequest、HttpContent 和 LastHttpContent 消息編碼爲字節 |
HttpResponseEncoder | 將 HTTPResponse、HttpContent 和 LastHttpContent 消息編碼爲字節 |
HttpRequestDecoder | 將字節編碼爲 HTTPRequest、HttpContent 和 LastHttpContent 消息 |
HttpResponseDecoder | 將字節編碼爲 HTTPResponse、HttpContent 和 LastHttpContent 消息 |
下述代碼中的 HttpPipelineInitializer 類展現了將 HTTP 支持添加到你的應用程序是多麼簡單 —— 只須要將正確的 ChannelHandler 添加到 ChannelPipeline 中this
public class HttpPipelineInitializer extends ChannelInitializer<Channel> { private final boolean client; public HttpPipelineInitializer(boolean client) { this.client = client; } @Override protected void initChannel(Channel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); if (client) { // 若是是客戶端,則添加 HttpResponseDecoder 處理來自服務器的響應 pipeline.addLast("decoder", new HttpResponseDecoder()); // 若是是客戶端,則添加 HttpRequestEncoder 向服務器發送請求 pipeline.addLast("encoder", new HttpRequestEncoder()); } else { // 若是是服務端,則添加 HttpRequestDecoder 處理來自客戶端的請求 pipeline.addLast("decoder", new HttpRequestDecoder()); // 若是是客戶端,則添加 HttpResponseEncoder 向客戶端發送響應 pipeline.addLast("encoder", new HttpResponseEncoder()); } } }
在 ChannelInitializer 將 ChannelHandler 安裝到 ChannelPipeline 中以後,你就能夠處理不一樣類型的 HTTPObject 消息了。但因爲 HTTP 請求和響應可能由許多部分組成,所以你須要聚合它們以造成完整的消息。Netty 提供了一個聚合器,它能夠將多個消息部分合併爲 FullHttpRequest 或者 FullHttpResponse 消息
因爲消息分段須要被緩衝,直到能夠轉發下一個完整的消息給下一個 ChannelInboundHandler,因此這個操做有輕微的開銷,其所帶來的好處就是你能夠沒必要關心消息碎片了
引入這種自動聚合機制只不過是向 ChannelPipeline 中添加另一個 ChannelHandler 罷了,下述代碼展現瞭如何作到這一點:
public class HttpAggregatorInitializer extends ChannelInitializer<Channel> { private final boolean isClient; public HttpAggregatorInitializer(boolean isClient) { this.isClient = isClient; } @Override protected void initChannel(Channel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); if (isClient) { // 若是是客戶端,則添加 HttpClientCodec pipeline.addLast("codec", new HttpClientCodec()); } else { // 若是是服務器,則添加 HttpServerCodec pipeline.addLast("codec", new HttpServerCodec()); } // 將最大的消息大小爲 512KB 的 HTTPObjectAggregator 添加到 ChannelPipeline pipeline.addLast("aggregator", new HttpObjectAggregator(512 * 1024)); } }
當使用 HTTP 時,建議開啓壓縮功能以儘量多地減少傳輸數據的大小。雖然壓縮會帶來一些消耗,但一般來講它都是一個好主意,尤爲是對於文本數據而言
Netty 爲壓縮和解壓都提供了 ChannelHandler 實現,它們同時支持 gzip 和 deflate 編碼
客戶端能夠經過提供如下頭部信息來指示服務器它所支持的壓縮格式
GET /encrypted-area HTTP/1.1
Host: www.example.com
Accept-Encoding: gzip, deflate
然而,須要注意的是,服務器沒有義務壓縮它所發送的數據
public class HttpCompressionInitializer extends ChannelInitializer<Channel> { private final boolean isClient; public HttpCompressionInitializer(boolean isClient) { this.isClient = isClient; } @Override protected void initChannel(Channel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); if (isClient) { // 若是是客戶端,則添加 HTTPClientCodec pipeline.addLast("codec", new HttpClientCodec()); // 若是是客戶端,則添加 HttpContentDecompressor 以處理來自服務器的壓縮內容 pipeline.addLast("decompressor", new HttpContentDecompressor()); } else { // 若是是服務端,則添加 HttpServerCodec pipeline.addLast("codec", new HttpServerCodec()); // 若是是服務器,則添加 HttpContentDecompressor 來壓縮數據 pipeline.addLast("decompressor", new HttpContentDecompressor()); } } }
啓用 HTTPS 只須要將 SslHandler 添加到 ChannelPipeline 的 ChannelHandler 組合中
public class HttpsCodecInitializer extends ChannelInitializer<Channel> { private final SslContext context; private final boolean isClient; public HttpsCodecInitializer(SslContext context, boolean isClient) { this.context = context; this.isClient = isClient; } @Override protected void initChannel(Channel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); SSLEngine engine = context.newEngine(ch.alloc()); pipeline.addLast("ssl", new SslHandler(engine)); if (isClient) { pipeline.addLast("codec", new HttpClientCodec()); } else { pipeline.addLast("codec", new HttpServerCodec()); } } }
WebSocket 解決了一個長期存在的問題:既然底層協議(HTTP)是一個請求/響應模式的交互序列,那麼如何實時地發佈信息呢?AJAX必定程度上解決了這個問題,但數據流仍然是由客戶端所發送的請求驅動的
WebSocket 提供了在單個 TCP 鏈接上提供雙向的通訊,它爲網頁和遠程服務器之間的雙向通訊提供了一種替代 HTTP 輪詢的方案
要想向你的應用程序添加對於 WebSocket 的支持,你須要將適當的客戶端或者服務器 WebSocketChannelHandler 添加到 ChannelPipeline 中。這個類將處理由 WebSocket 定義的稱爲幀的特殊消息類型,如表所示,WebSocketFrame 能夠被歸類爲數據幀或者控制幀
名稱 | 描述 |
---|---|
BinaryWebSocketFrame | 數據幀:二進制數據 |
TextWebSocketFrame | 數據幀:文本數據 |
ContinuationWebSocketFrame | 數據幀:屬於上一個 BinaryWebSocketFrame 或者 TextWebSocketFrame 的文本或者二進制的數據 |
CloseWebSocketFrame | 控制幀:一個 CLOSE 請求,關閉的狀態碼以及關閉的緣由 |
PingWebSocketFrame | 控制幀:請求一個 PongWebSocketFrame |
PongWebSocketFrame | 控制幀:對 PingWebSocketFrame 請求的響應 |
由於 Netty 主要是一種服務器端技術,因此咱們重點建立 WebSocket 服務器。下述代碼展現了使用 WebSocketChannelHandler 的簡單示例,這個類會處理協議升級握手,以及三種控制幀 —— Close、Ping 和 Pong,Text 和 Binary 數據幀將會被傳遞給下一個 ChannelHandler 進行處理
public class WebSocketServerInitializer extends ChannelInitializer<Channel> { @Override protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast( new HttpServerCodec(), new HttpObjectAggregator(65536), // 若是被請求的端點是 /websocket,則處理該升級握手 new WebSocketServerProtocolHandler("/websocket"), // TextFrameHandler 處理 TextWebSocketFrame new TextFrameHandler(), // BinaryFrameHandler 處理 BinaryWebSocketFrame new BinaryFrameHandler(), // ContinuationFrameHandler 處理 Continuation WebSocketFrame new ContinuationFrameHandler()); } public static final class TextFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { @Override protected void messageReceived(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { // do something } } public static final class BinaryFrameHandler extends SimpleChannelInboundHandler<BinaryWebSocketFrame> { @Override protected void messageReceived(ChannelHandlerContext ctx, BinaryWebSocketFrame msg) throws Exception { // do something } } public static final class ContinuationFrameHandler extends SimpleChannelInboundHandler<ContinuationWebSocketFrame> { @Override protected void messageReceived(ChannelHandlerContext ctx, ContinuationWebSocketFrame msg) throws Exception { // do something } } }