1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.jboss.netty.handler.codec.spdy;
17
18 import org.jboss.netty.channel.ChannelDownstreamHandler;
19 import org.jboss.netty.channel.ChannelEvent;
20 import org.jboss.netty.channel.ChannelFuture;
21 import org.jboss.netty.channel.ChannelFutureListener;
22 import org.jboss.netty.channel.ChannelHandlerContext;
23 import org.jboss.netty.channel.Channels;
24 import org.jboss.netty.channel.MessageEvent;
25 import org.jboss.netty.handler.codec.http.HttpChunk;
26 import org.jboss.netty.handler.codec.http.HttpChunkTrailer;
27 import org.jboss.netty.handler.codec.http.HttpHeaders;
28 import org.jboss.netty.handler.codec.http.HttpMessage;
29 import org.jboss.netty.handler.codec.http.HttpRequest;
30 import org.jboss.netty.handler.codec.http.HttpResponse;
31
32 import java.util.List;
33 import java.util.Map;
34
35 import static org.jboss.netty.handler.codec.spdy.SpdyCodecUtil.*;
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126 public class SpdyHttpEncoder implements ChannelDownstreamHandler {
127
128 private final int spdyVersion;
129 private volatile int currentStreamID;
130
131
132
133
134 @Deprecated
135 public SpdyHttpEncoder() {
136 this(2);
137 }
138
139
140
141
142
143
144 public SpdyHttpEncoder(int version) {
145 if (version < SPDY_MIN_VERSION || version > SPDY_MAX_VERSION) {
146 throw new IllegalArgumentException(
147 "unsupported version: " + version);
148 }
149 spdyVersion = version;
150 }
151
152 public void handleDownstream(ChannelHandlerContext ctx, ChannelEvent evt)
153 throws Exception {
154 if (!(evt instanceof MessageEvent)) {
155 ctx.sendDownstream(evt);
156 return;
157 }
158
159 MessageEvent e = (MessageEvent) evt;
160 Object msg = e.getMessage();
161
162 if (msg instanceof HttpRequest) {
163
164 HttpRequest httpRequest = (HttpRequest) msg;
165 SpdySynStreamFrame spdySynStreamFrame = createSynStreamFrame(httpRequest);
166 int streamID = spdySynStreamFrame.getStreamId();
167 ChannelFuture future = getContentFuture(ctx, e, streamID, httpRequest);
168 Channels.write(ctx, future, spdySynStreamFrame, e.getRemoteAddress());
169
170 } else if (msg instanceof HttpResponse) {
171
172 HttpResponse httpResponse = (HttpResponse) msg;
173 if (httpResponse.containsHeader(SpdyHttpHeaders.Names.ASSOCIATED_TO_STREAM_ID)) {
174 SpdySynStreamFrame spdySynStreamFrame = createSynStreamFrame(httpResponse);
175 int streamID = spdySynStreamFrame.getStreamId();
176 ChannelFuture future = getContentFuture(ctx, e, streamID, httpResponse);
177 Channels.write(ctx, future, spdySynStreamFrame, e.getRemoteAddress());
178 } else {
179 SpdySynReplyFrame spdySynReplyFrame = createSynReplyFrame(httpResponse);
180 int streamID = spdySynReplyFrame.getStreamId();
181 ChannelFuture future = getContentFuture(ctx, e, streamID, httpResponse);
182 Channels.write(ctx, future, spdySynReplyFrame, e.getRemoteAddress());
183 }
184
185 } else if (msg instanceof HttpChunk) {
186
187 HttpChunk chunk = (HttpChunk) msg;
188 SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(currentStreamID);
189 spdyDataFrame.setData(chunk.getContent());
190 spdyDataFrame.setLast(chunk.isLast());
191
192 if (chunk instanceof HttpChunkTrailer) {
193 HttpChunkTrailer trailer = (HttpChunkTrailer) chunk;
194 List<Map.Entry<String, String>> trailers = trailer.getHeaders();
195 if (trailers.isEmpty()) {
196 Channels.write(ctx, e.getFuture(), spdyDataFrame, e.getRemoteAddress());
197 } else {
198
199 SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(currentStreamID);
200 for (Map.Entry<String, String> entry: trailers) {
201 spdyHeadersFrame.addHeader(entry.getKey(), entry.getValue());
202 }
203
204
205 ChannelFuture future = Channels.future(e.getChannel());
206 future.addListener(new SpdyFrameWriter(ctx, e, spdyDataFrame));
207 Channels.write(ctx, future, spdyHeadersFrame, e.getRemoteAddress());
208 }
209 } else {
210 Channels.write(ctx, e.getFuture(), spdyDataFrame, e.getRemoteAddress());
211 }
212 } else {
213
214 ctx.sendDownstream(evt);
215 }
216 }
217
218 private static ChannelFuture getContentFuture(
219 ChannelHandlerContext ctx, MessageEvent e, int streamID, HttpMessage httpMessage) {
220 if (httpMessage.getContent().readableBytes() == 0) {
221 return e.getFuture();
222 }
223
224
225 SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(streamID);
226 spdyDataFrame.setData(httpMessage.getContent());
227 spdyDataFrame.setLast(true);
228
229
230 ChannelFuture future = Channels.future(e.getChannel());
231 future.addListener(new SpdyFrameWriter(ctx, e, spdyDataFrame));
232
233 return future;
234 }
235
236 private static class SpdyFrameWriter implements ChannelFutureListener {
237
238 private final ChannelHandlerContext ctx;
239 private final MessageEvent e;
240 private final Object spdyFrame;
241
242 SpdyFrameWriter(ChannelHandlerContext ctx, MessageEvent e, Object spdyFrame) {
243 this.ctx = ctx;
244 this.e = e;
245 this.spdyFrame = spdyFrame;
246 }
247
248 public void operationComplete(ChannelFuture future) throws Exception {
249 if (future.isSuccess()) {
250 Channels.write(ctx, e.getFuture(), spdyFrame, e.getRemoteAddress());
251 } else if (future.isCancelled()) {
252 e.getFuture().cancel();
253 } else {
254 e.getFuture().setFailure(future.getCause());
255 }
256 }
257 }
258
259 private SpdySynStreamFrame createSynStreamFrame(HttpMessage httpMessage)
260 throws Exception {
261 boolean chunked = httpMessage.isChunked();
262
263
264 int streamID = SpdyHttpHeaders.getStreamId(httpMessage);
265 int associatedToStreamID = SpdyHttpHeaders.getAssociatedToStreamId(httpMessage);
266 byte priority = SpdyHttpHeaders.getPriority(httpMessage);
267 String URL = SpdyHttpHeaders.getUrl(httpMessage);
268 String scheme = SpdyHttpHeaders.getScheme(httpMessage);
269 SpdyHttpHeaders.removeStreamId(httpMessage);
270 SpdyHttpHeaders.removeAssociatedToStreamId(httpMessage);
271 SpdyHttpHeaders.removePriority(httpMessage);
272 SpdyHttpHeaders.removeUrl(httpMessage);
273 SpdyHttpHeaders.removeScheme(httpMessage);
274
275
276
277 httpMessage.removeHeader(HttpHeaders.Names.CONNECTION);
278 httpMessage.removeHeader("Keep-Alive");
279 httpMessage.removeHeader("Proxy-Connection");
280 httpMessage.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
281
282 SpdySynStreamFrame spdySynStreamFrame =
283 new DefaultSpdySynStreamFrame(streamID, associatedToStreamID, priority);
284
285
286 if (httpMessage instanceof HttpRequest) {
287 HttpRequest httpRequest = (HttpRequest) httpMessage;
288 SpdyHeaders.setMethod(spdyVersion, spdySynStreamFrame, httpRequest.getMethod());
289 SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, httpRequest.getUri());
290 SpdyHeaders.setVersion(spdyVersion, spdySynStreamFrame, httpMessage.getProtocolVersion());
291 }
292 if (httpMessage instanceof HttpResponse) {
293 HttpResponse httpResponse = (HttpResponse) httpMessage;
294 SpdyHeaders.setStatus(spdyVersion, spdySynStreamFrame, httpResponse.getStatus());
295 SpdyHeaders.setUrl(spdyVersion, spdySynStreamFrame, URL);
296 SpdyHeaders.setVersion(spdyVersion, spdySynStreamFrame, httpMessage.getProtocolVersion());
297 spdySynStreamFrame.setUnidirectional(true);
298 }
299
300
301 if (spdyVersion >= 3) {
302 String host = HttpHeaders.getHost(httpMessage);
303 httpMessage.removeHeader(HttpHeaders.Names.HOST);
304 SpdyHeaders.setHost(spdySynStreamFrame, host);
305 }
306
307
308 if (scheme == null) {
309 scheme = "https";
310 }
311 SpdyHeaders.setScheme(spdyVersion, spdySynStreamFrame, scheme);
312
313
314 for (Map.Entry<String, String> entry: httpMessage.getHeaders()) {
315 spdySynStreamFrame.addHeader(entry.getKey(), entry.getValue());
316 }
317
318 if (chunked) {
319 currentStreamID = streamID;
320 spdySynStreamFrame.setLast(false);
321 } else {
322 spdySynStreamFrame.setLast(httpMessage.getContent().readableBytes() == 0);
323 }
324
325 return spdySynStreamFrame;
326 }
327
328 private SpdySynReplyFrame createSynReplyFrame(HttpResponse httpResponse)
329 throws Exception {
330 boolean chunked = httpResponse.isChunked();
331
332
333 int streamID = SpdyHttpHeaders.getStreamId(httpResponse);
334 SpdyHttpHeaders.removeStreamId(httpResponse);
335
336
337
338 httpResponse.removeHeader(HttpHeaders.Names.CONNECTION);
339 httpResponse.removeHeader("Keep-Alive");
340 httpResponse.removeHeader("Proxy-Connection");
341 httpResponse.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
342
343 SpdySynReplyFrame spdySynReplyFrame = new DefaultSpdySynReplyFrame(streamID);
344
345
346 SpdyHeaders.setStatus(spdyVersion, spdySynReplyFrame, httpResponse.getStatus());
347 SpdyHeaders.setVersion(spdyVersion, spdySynReplyFrame, httpResponse.getProtocolVersion());
348
349
350 for (Map.Entry<String, String> entry: httpResponse.getHeaders()) {
351 spdySynReplyFrame.addHeader(entry.getKey(), entry.getValue());
352 }
353
354 if (chunked) {
355 currentStreamID = streamID;
356 spdySynReplyFrame.setLast(false);
357 } else {
358 spdySynReplyFrame.setLast(httpResponse.getContent().readableBytes() == 0);
359 }
360
361 return spdySynReplyFrame;
362 }
363 }