SPDY 使用 TLS 的擴(kuò)展稱為 Next Protocol Negotiation (NPN)。在Java 中,我們有兩種不同的方式選擇的基于 NPN 的協(xié)議:
在這個(gè)例子中使用 Jetty 庫。如果你想使用 ssl_npn,請參閱https://github.com/benmmurphy/ssl_npn項(xiàng)目文檔
Jetty NPN 庫
Jetty NPN 庫是一個(gè)外部的庫,而不是 Netty 的本身的一部分。它用于處理 Next Protocol Negotiation, 這是用于檢測客戶端是否支持 SPDY。
Jetty 庫提供了一個(gè)接口稱為 ServerProvider,確定所使用的協(xié)議和選擇哪個(gè)鉤子。這個(gè)的實(shí)現(xiàn)可能取決于不同版本的 HTTP 和 SPDY 版本的支持。下面的清單顯示了將用于我們的示例應(yīng)用程序的實(shí)現(xiàn)。
Listing 12.1 Implementation of ServerProvider
public class DefaultServerProvider implements NextProtoNego.ServerProvider {
private static final List<String> PROTOCOLS =
Collections.unmodifiableList(Arrays.asList("spdy/2", "spdy/3", "http/1.1")); //1
private String protocol;
@Override
public void unsupported() {
protocol = "http/1.1"; //2
}
@Override
public List<String> protocols() {
return PROTOCOLS; //3
}
@Override
public void protocolSelected(String protocol) {
this.protocol = protocol; //4
}
public String getSelectedProtocol() {
return protocol; //5
}
}
在 ServerProvider 的實(shí)現(xiàn),我們支持下面的3種協(xié)議:
如果客戶端不支持 SPDY ,則默認(rèn)使用 HTTP 1.1
第一個(gè) ChannelInboundHandler 是用于不支持 SPDY 的情況下處理客戶端 HTTP 請求,如果不支持 SPDY 就回滾使用默認(rèn)的 HTTP 協(xié)議。
清單12.2顯示了HTTP流量的處理程序。
Listing 12.2 Implementation that handles HTTP
@ChannelHandler.Sharable
public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {
@Override
public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception { //1
if (HttpHeaders.is100ContinueExpected(request)) {
send100Continue(ctx); //2
}
FullHttpResponse response = new DefaultFullHttpResponse(request.getProtocolVersion(), HttpResponseStatus.OK); //3
response.content().writeBytes(getContent().getBytes(CharsetUtil.UTF_8)); //4
response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/plain; charset=UTF-8"); //5
boolean keepAlive = HttpHeaders.isKeepAlive(request);
if (keepAlive) { //6
response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, response.content().readableBytes());
response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
}
ChannelFuture future = ctx.writeAndFlush(response); //7
if (!keepAlive) {
future.addListener (ChannelFutureListener.CLOSE); //8
}
}
protected String getContent() { //9
return "This content is transmitted via HTTP\r\n";
}
private static void send100Continue(ChannelHandlerContext ctx) { //10
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
ctx.writeAndFlush(response);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception { //11
cause.printStackTrace();
ctx.close();
}
}
這就是 Netty 處理標(biāo)準(zhǔn)的 HTTP 。你可能需要分別處理特定 URI ,應(yīng)對不同的狀態(tài)代碼,這取決于資源存在與否,但基本的概念將是相同的。
我們的下一個(gè)任務(wù)將會提供一個(gè)組件來支持 SPDY 作為首選協(xié)議。 Netty 提供了簡單的處理 SPDY 方法。這些將使您能夠重用FullHttpRequest 和 FullHttpResponse 消息,通過 SPDY 透明地接收和發(fā)送他們。
HttpRequestHandler 雖然是我們可以重用代碼,我們將改變我們的內(nèi)容寫回客戶端只是強(qiáng)調(diào)協(xié)議變化;通常您會返回相同的內(nèi)容。下面的清單展示了實(shí)現(xiàn),它擴(kuò)展了先前的 HttpRequestHandler。
Listing 12.3 Implementation that handles SPDY
@ChannelHandler.Sharable
public class SpdyRequestHandler extends HttpRequestHandler { //1
@Override
protected String getContent() {
return "This content is transmitted via SPDY\r\n"; //2
}
}
SpdyRequestHandler 繼承自 HttpRequestHandler,但區(qū)別是:寫入的內(nèi)容的 payload 狀態(tài)的響應(yīng)是在 SPDY 寫的。
我們可以實(shí)現(xiàn)兩個(gè)處理程序邏輯,將選擇一個(gè)相匹配的協(xié)議。然而添加以前寫過的處理程序到 ChannelPipeline 是不夠的;正確的編解碼器還需要補(bǔ)充。它的責(zé)任是檢測傳輸字節(jié)數(shù),然后使用 FullHttpResponse 和 FullHttpRequest 的抽象進(jìn)行工作。
Netty 的附帶一個(gè)基類,完全能做這個(gè)。所有您需要做的是實(shí)現(xiàn)邏輯選擇協(xié)議和選擇適當(dāng)?shù)奶幚沓绦颉?/p>
清單12.4顯示了實(shí)現(xiàn),它使用 Netty 的提供的抽象基類。
public class DefaultSpdyOrHttpChooser extends SpdyOrHttpChooser {
public DefaultSpdyOrHttpChooser(int maxSpdyContentLength, int maxHttpContentLength) {
super(maxSpdyContentLength, maxHttpContentLength);
}
@Override
protected SelectedProtocol getProtocol(SSLEngine engine) {
DefaultServerProvider provider = (DefaultServerProvider) NextProtoNego.get(engine); //1
String protocol = provider.getSelectedProtocol();
if (protocol == null) {
return SelectedProtocol.UNKNOWN; //2
}
switch (protocol) {
case "spdy/2":
return SelectedProtocol.SPDY_2; //3
case "spdy/3.1":
return SelectedProtocol.SPDY_3_1; //4
case "http/1.1":
return SelectedProtocol.HTTP_1_1; //5
default:
return SelectedProtocol.UNKNOWN; //6
}
}
@Override
protected ChannelInboundHandler createHttpRequestHandlerForHttp() {
return new HttpRequestHandler(); //7
}
@Override
protected ChannelInboundHandler createHttpRequestHandlerForSpdy() {
return new SpdyRequestHandler(); //8
}
}
該實(shí)現(xiàn)要注意檢測正確的協(xié)議并設(shè)置 ChannelPipeline 。它可以處理SPDY 版本 2、3 和 HTTP 1.1,但可以很容易地修改 SPDY 支持額外的版本。
通過實(shí)現(xiàn) ChannelInitializer 將所有的處理器連接到一起。正如你所了解的那樣,這將設(shè)置 ChannelPipeline 并添加所有需要的ChannelHandler 的。
SPDY 需要兩個(gè) ChannelHandler:
除了添加 ChannelHandler 到 ChannelPipeline, ChannelInitializer 還有另一個(gè)責(zé)任;即,分配之前創(chuàng)建的 DefaultServerProvider 通過 SslHandler 到 SslEngine 。這將通過Jetty NPN 類庫的 NextProtoNego helper 類實(shí)現(xiàn)
Listing 12.5 Implementation that handles SPDY
public class SpdyChannelInitializer extends ChannelInitializer<SocketChannel> { //1
private final SslContext context;
public SpdyChannelInitializer(SslContext context) //2 {
this.context = context;
}
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
SSLEngine engine = context.newEngine(ch.alloc()); //3
engine.setUseClientMode(false); //4
NextProtoNego.put(engine, new DefaultServerProvider()); //5
NextProtoNego.debug = true;
pipeline.addLast("sslHandler", new SslHandler(engine)); //6
pipeline.addLast("chooser", new DefaultSpdyOrHttpChooser(1024 * 1024, 1024 * 1024));
}
}
實(shí)際的 ChannelPipeline 設(shè)置將會在 DefaultSpdyOrHttpChooser 實(shí)現(xiàn)之后完成,因?yàn)樵谶@一點(diǎn)上它可能只需要知道客戶端是否支持 SPDY
為了說明這一點(diǎn),讓我們總結(jié)一下,看看不同 ChannelPipeline 狀態(tài)期間與客戶連接的生命周期。圖12.2顯示了在 Channel 初始化后的 ChannelPipeline。
Figure 12.2 ChannelPipeline after connection
現(xiàn)在,這取決于客戶端是否支持 SPDY,管道將修改DefaultSpdyOrHttpChooser 來處理協(xié)議。之后并不需要添加所需的 ChannelHandler 到 ChannelPipeline,所以刪除本身。這個(gè)邏輯是由抽象 SpdyOrHttpChooser 類封裝,DefaultSpdyOrHttpChooser 父類。
圖12.3顯示了支持 SPDY 的 ChannelPipeline 用于連接客戶端的配置。
Figure 12.3 ChannelPipeline if SPDY is supported
每個(gè) ChannelHandler 負(fù)責(zé)的一小部分工作,這個(gè)就是對基于 Netty 構(gòu)造的應(yīng)用程序最完美的詮釋。每個(gè) ChannelHandler 的職責(zé)如表12.3所示。
Table 12.3 Responsibilities of the ChannelHandlers when SPDY is used
名稱 | 職責(zé) |
---|---|
SslHandler | 加解密兩端交換的數(shù)據(jù) |
SpdyFrameDecoder | 從接收到的 SPDY 幀中解碼字節(jié) |
SpdyFrameEncoder | 編碼 SPDY 幀到字節(jié) |
SpdySessionHandler | 處理 SPDY session |
SpdyHttpEncoder | 編碼 HTTP 消息到 SPDY 幀 |
SpdyHttpDecoder | 解碼 SDPY 幀到 HTTP 消息 |
SpdyHttpResponseStreamIdHandler | 處理基于 SPDY ID 請求和響應(yīng)之間的映射關(guān)系 |
SpdyRequestHandler | 處理 FullHttpRequest, 用于從 SPDY 幀中解碼,因此允許 SPDY 透明傳輸使用 |
當(dāng)協(xié)議是 HTTP(s) 時(shí),ChannelPipeline 看起來相當(dāng)不同,如圖13.4所示。
Figure 13.4 ChannelPipeline if SPDY is not supported
和之前一樣,每個(gè) ChannelHandler 都有職責(zé),定義在表12.4
Table 12.4 Responsibilities of the ChannelHandlers when HTTP is used
名稱 | 職責(zé) |
---|---|
SslHandler | 加解密兩端交換的數(shù)據(jù) |
HttpRequestDecoder | 從接收到的 HTTP 請求中解碼字節(jié) |
HttpResponseEncoder | 編碼 HTTP 響應(yīng)到字節(jié) |
HttpObjectAggregator 處理 SPDY session HttpRequestHandler | 解碼時(shí)處理 FullHttpRequest
所有的 ChannelHandler 實(shí)現(xiàn)已經(jīng)準(zhǔn)備好,現(xiàn)在組合成一個(gè) SpdyServer
Listing 12.6 SpdyServer implementation
public class SpdyServer {
private final NioEventLoopGroup group = new NioEventLoopGroup(); //1
private final SslContext context;
private Channel channel;
public SpdyServer(SslContext context) { //2
this.context = context;
}
public ChannelFuture start(InetSocketAddress address) {
ServerBootstrap bootstrap = new ServerBootstrap(); //3
bootstrap.group(group)
.channel(NioServerSocketChannel.class)
.childHandler(new SpdyChannelInitializer(context)); //4
ChannelFuture future = bootstrap.bind(address); //5
future.syncUninterruptibly();
channel = future.channel();
return future;
}
public void destroy() { //6
if (channel != null) {
channel.close();
}
group.shutdownGracefully();
}
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println("Please give port as argument");
System.exit(1);
}
int port = Integer.parseInt(args[0]);
SelfSignedCertificate cert = new SelfSignedCertificate();
SslContext context = SslContext.newServerContext(cert.certificate(), cert.privateKey()); //7
final SpdyServer endpoint = new SpdyServer(context);
ChannelFuture future = endpoint.start(new InetSocketAddress(port));
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
endpoint.destroy();
}
});
future.channel().closeFuture().syncUninterruptibly();
}
}
更多建議: