@@ -119,3 +119,233 @@ Listing 12.2 Implementation that handles HTTP
119
119
10 . Helper 方法生成了100 持续的响应,并写回给客户端
120
120
11 . 若执行阶段抛出异常,则关闭管道
121
121
122
+ 这就是 Netty 处理标准的 HTTP 。你可能需要分别处理特定 URI ,应对不同的状态代码,这取决于资源存在与否,但基本的概念将是相同的。
123
+
124
+ 我们的下一个任务将会提供一个组件来支持 SPDY 作为首选协议。
125
+ Netty 提供了简单的处理 SPDY 方法。这些将使您能够重用FullHttpRequest 和 FullHttpResponse 消息,通过 SPDY 透明地接收和发送他们。
126
+
127
+ HttpRequestHandler 虽然是我们可以重用代码,我们将改变我们的内容写回客户端只是强调协议变化;通常您会返回相同的内容。下面的清单展示了实现,它扩展了先前的 HttpRequestHandler。
128
+
129
+ Listing 12.3 Implementation that handles SPDY
130
+
131
+ @ChannelHandler.Sharable
132
+ public class SpdyRequestHandler extends HttpRequestHandler { //1
133
+ @Override
134
+ protected String getContent() {
135
+ return "This content is transmitted via SPDY\r\n"; //2
136
+ }
137
+ }
138
+
139
+ 1 . 继承 HttpRequestHandler 这样就能共享相同的逻辑
140
+ 2 . 生产内容写到 payload。这个重写了 HttpRequestHandler 的
141
+ getContent() 的实现
142
+
143
+ SpdyRequestHandler 继承自 HttpRequestHandler,但区别是:写入的内容的 payload 状态的响应是在 SPDY 写的。
144
+
145
+ 我们可以实现两个处理程序逻辑,将选择一个相匹配的协议。然而添加以前写过的处理程序到 ChannelPipeline 是不够的;正确的编解码器还需要补充。它的责任是检测传输字节数,然后使用 FullHttpResponse 和 FullHttpRequest 的抽象进行工作。
146
+
147
+ Netty 的附带一个基类,完全能做这个。所有您需要做的是实现逻辑选择协议和选择适当的处理程序。
148
+
149
+ 清单12.4显示了实现,它使用 Netty 的提供的抽象基类。
150
+
151
+ public class DefaultSpdyOrHttpChooser extends SpdyOrHttpChooser {
152
+
153
+ public DefaultSpdyOrHttpChooser(int maxSpdyContentLength, int maxHttpContentLength) {
154
+ super(maxSpdyContentLength, maxHttpContentLength);
155
+ }
156
+
157
+ @Override
158
+ protected SelectedProtocol getProtocol(SSLEngine engine) {
159
+ DefaultServerProvider provider = (DefaultServerProvider) NextProtoNego.get(engine); //1
160
+ String protocol = provider.getSelectedProtocol();
161
+ if (protocol == null) {
162
+ return SelectedProtocol.UNKNOWN; //2
163
+ }
164
+ switch (protocol) {
165
+ case "spdy/2":
166
+ return SelectedProtocol.SPDY_2; //3
167
+ case "spdy/3.1":
168
+ return SelectedProtocol.SPDY_3_1; //4
169
+ case "http/1.1":
170
+ return SelectedProtocol.HTTP_1_1; //5
171
+ default:
172
+ return SelectedProtocol.UNKNOWN; //6
173
+ }
174
+ }
175
+
176
+ @Override
177
+ protected ChannelInboundHandler createHttpRequestHandlerForHttp() {
178
+ return new HttpRequestHandler(); //7
179
+ }
180
+
181
+ @Override
182
+ protected ChannelInboundHandler createHttpRequestHandlerForSpdy() {
183
+ return new SpdyRequestHandler(); //8
184
+ }
185
+ }
186
+
187
+ 1 . 使用 NextProtoNego 用于获取 DefaultServerProvider 的引用, 用于 SSLEngine
188
+ 2 . 协议不能被检测到。一旦字节已经准备好读,检测过程将重新开始。
189
+ 3 . SPDY 2 被检测到
190
+ 4 . SPDY 3 被检测到
191
+ 5 . HTTP 1.1 被检测到
192
+ 6 . 未知协议被检测到
193
+ 7 . 将会被调用给 FullHttpRequest 消息添加处理器。该方法只会在不支持 SPDY 时调用,那么将会使用 HTTPS
194
+ 8 . 将会被调用给 FullHttpRequest 消息添加处理器。该方法在支持 SPDY 时调用
195
+
196
+ 该实现要注意检测正确的协议并设置 ChannelPipeline 。它可以处理SPDY 版本 2、3 和 HTTP 1.1,但可以很容易地修改 SPDY 支持额外的版本。
197
+
198
+ ### 设置 ChannelPipeline
199
+
200
+ 通过实现 ChannelInitializer 将所有的处理器连接到一起。正如你所了解的那样,这将设置 ChannelPipeline 并添加所有需要的ChannelHandler 的。
201
+
202
+ SPDY 需要两个 ChannelHandler:
203
+
204
+ * SslHandler,用于检测 SPDY 是否通过 TLS 扩展
205
+ * DefaultSpdyOrHttpChooser,用于当协议被检测到时,添加正确的 ChannelHandler 到 ChannelPipeline
206
+
207
+ 除了添加 ChannelHandler 到 ChannelPipeline, ChannelInitializer 还有另一个责任;即,分配之前创建的 DefaultServerProvider 通过 SslHandler 到 SslEngine 。这将通过Jetty NPN 类库的 NextProtoNego helper 类实现
208
+
209
+ Listing 12.5 Implementation that handles SPDY
210
+
211
+ public class SpdyChannelInitializer extends ChannelInitializer<SocketChannel> { //1
212
+ private final SslContext context;
213
+
214
+ public SpdyChannelInitializer(SslContext context) //2 {
215
+ this.context = context;
216
+ }
217
+
218
+ @Override
219
+ protected void initChannel(SocketChannel ch) throws Exception {
220
+ ChannelPipeline pipeline = ch.pipeline();
221
+ SSLEngine engine = context.newEngine(ch.alloc()); //3
222
+ engine.setUseClientMode(false); //4
223
+
224
+ NextProtoNego.put(engine, new DefaultServerProvider()); //5
225
+ NextProtoNego.debug = true;
226
+
227
+ pipeline.addLast("sslHandler", new SslHandler(engine)); //6
228
+ pipeline.addLast("chooser", new DefaultSpdyOrHttpChooser(1024 * 1024, 1024 * 1024));
229
+ }
230
+ }
231
+
232
+ 1 . 继承 ChannelInitializer 是一个简单的开始
233
+ 2 . 传递 SSLContext 用于创建 SSLEngine
234
+ 3 . 新建 SSLEngine,用于新的管道和连接
235
+ 4 . 配置 SSLEngine 用于非客户端使用
236
+ 5 . 通过 NextProtoNego helper 类绑定 DefaultServerProvider 到 SSLEngine
237
+ 6 . 添加 SslHandler 到 ChannelPipeline 这将会在协议检测到时保存在 ChannelPipeline
238
+ 7 . 添加 DefaultSpyOrHttpChooser 到 ChannelPipeline 。这个实现将会监测协议。添加正确的 ChannelHandler 到 ChannelPipeline,并且移除自身
239
+
240
+ 实际的 ChannelPipeline 设置将会在 DefaultSpdyOrHttpChooser 实现之后完成,因为在这一点上它可能只需要知道客户端是否支持 SPDY
241
+
242
+ 为了说明这一点,让我们总结一下,看看不同 ChannelPipeline 状态期间与客户连接的生命周期。图12.2显示了在 Channel 初始化后的 ChannelPipeline。
243
+
244
+ ![ ] (../images/Figure 12.2 ChannelPipeline after connection.jpg)
245
+
246
+ Figure 12.2 ChannelPipeline after connection
247
+
248
+ 现在,这取决于客户端是否支持 SPDY,管道将修改DefaultSpdyOrHttpChooser 来处理协议。之后并不需要添加所需的 ChannelHandler 到 ChannelPipeline,所以删除本身。这个逻辑是由抽象 SpdyOrHttpChooser 类封装,DefaultSpdyOrHttpChooser 父类。
249
+
250
+ 图12.3显示了支持 SPDY 的 ChannelPipeline 用于连接客户端的配置。
251
+
252
+ ![ ] (../images/Figure 12.3 ChannelPipeline if SPDY is supported.jpg)
253
+
254
+ Figure 12.3 ChannelPipeline if SPDY is supported
255
+
256
+ 每个 ChannelHandler 负责的一小部分工作,这个就是对基于 Netty 构造的应用程序最完美的诠释。每个 ChannelHandler 的职责如表12.3所示。
257
+
258
+ Table 12.3 Responsibilities of the ChannelHandlers when SPDY is used
259
+
260
+ 名称 | 职责
261
+ ----|----
262
+ SslHandler | 加解密两端交换的数据
263
+ SpdyFrameDecoder | 从接收到的 SPDY 帧中解码字节
264
+ SpdyFrameEncoder | 编码 SPDY 帧到字节
265
+ SpdySessionHandler | 处理 SPDY session
266
+ SpdyHttpEncoder | 编码 HTTP 消息到 SPDY 帧
267
+ SpdyHttpDecoder | 解码 SDPY 帧到 HTTP 消息
268
+ SpdyHttpResponseStreamIdHandler | 处理基于 SPDY ID 请求和响应之间的映射关系
269
+ SpdyRequestHandler | 处理 FullHttpRequest, 用于从 SPDY 帧中解码,因此允许 SPDY 透明传输使用
270
+
271
+ 当协议是 HTTP(s) 时,ChannelPipeline 看起来相当不同,如图13.4所示。
272
+
273
+ ![ ] (../images/Figure 12.3 ChannelPipeline if SPDY is not supported.jpg)
274
+
275
+ Figure 12.3 ChannelPipeline if SPDY is not supported
276
+
277
+ 和之前一样,每个 ChannelHandler 都有职责,定义在表12.4
278
+
279
+ Table 12.4 Responsibilities of the ChannelHandlers when HTTP is used
280
+
281
+ 名称 | 职责
282
+ ----|----
283
+ SslHandler | 加解密两端交换的数据
284
+ HttpRequestDecoder | 从接收到的 HTTP 请求中解码字节
285
+ HttpResponseEncoder | 编码 HTTP 响应到字节
286
+ HttpObjectAggregator 处理 SPDY session
287
+ HttpRequestHandler | 解码时处理 FullHttpRequest
288
+
289
+ ### 所有东西组合在一起
290
+
291
+ 所有的 ChannelHandler 实现已经准备好,现在组合成一个 SpdyServer
292
+
293
+ Listing 12.6 SpdyServer implementation
294
+
295
+ public class SpdyServer {
296
+
297
+ private final NioEventLoopGroup group = new NioEventLoopGroup(); //1
298
+ private final SslContext context;
299
+ private Channel channel;
300
+
301
+ public SpdyServer(SslContext context) { //2
302
+ this.context = context;
303
+ }
304
+
305
+ public ChannelFuture start(InetSocketAddress address) {
306
+ ServerBootstrap bootstrap = new ServerBootstrap(); //3
307
+ bootstrap.group(group)
308
+ .channel(NioServerSocketChannel.class)
309
+ .childHandler(new SpdyChannelInitializer(context)); //4
310
+ ChannelFuture future = bootstrap.bind(address); //5
311
+ future.syncUninterruptibly();
312
+ channel = future.channel();
313
+ return future;
314
+ }
315
+
316
+ public void destroy() { //6
317
+ if (channel != null) {
318
+ channel.close();
319
+ }
320
+ group.shutdownGracefully();
321
+ }
322
+
323
+ public static void main(String[] args) throws Exception {
324
+ if (args.length != 1) {
325
+ System.err.println("Please give port as argument");
326
+ System.exit(1);
327
+ }
328
+ int port = Integer.parseInt(args[0]);
329
+
330
+ SelfSignedCertificate cert = new SelfSignedCertificate();
331
+ SslContext context = SslContext.newServerContext(cert.certificate(), cert.privateKey()); //7
332
+ final SpdyServer endpoint = new SpdyServer(context);
333
+ ChannelFuture future = endpoint.start(new InetSocketAddress(port));
334
+
335
+ Runtime.getRuntime().addShutdownHook(new Thread() {
336
+ @Override
337
+ public void run() {
338
+ endpoint.destroy();
339
+ }
340
+ });
341
+ future.channel().closeFuture().syncUninterruptibly();
342
+ }
343
+ }
344
+
345
+ 1 . 构建新的 NioEventLoopGroup 用于处理 I/O
346
+ 2 . 传递 SSLContext 用于加密
347
+ 3 . 新建 ServerBootstrap 用于配置服务器
348
+ 4 . 配置 ServerBootstrap
349
+ 5 . 绑定服务器用于接收指定地址的连接
350
+ 6 . 销毁服务器,用于关闭管道和 NioEventLoopGroup
351
+ 7 . 从 BogusSslContextFactory 获取 SSLContext 。这是一个虚拟实现进行测试。真正的实现将为 SslContext 配置适当的密钥存储库。
0 commit comments