1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.codec.spdy;
17
18 import io.netty.channel.ChannelHandlerContext;
19 import io.netty.handler.codec.MessageToMessageEncoder;
20 import io.netty.handler.codec.UnsupportedMessageTypeException;
21 import io.netty.handler.codec.http.FullHttpMessage;
22 import io.netty.handler.codec.http.HttpContent;
23 import io.netty.handler.codec.http.HttpHeaderNames;
24 import io.netty.handler.codec.http.HttpHeaders;
25 import io.netty.handler.codec.http.HttpMessage;
26 import io.netty.handler.codec.http.HttpObject;
27 import io.netty.handler.codec.http.HttpRequest;
28 import io.netty.handler.codec.http.HttpResponse;
29 import io.netty.handler.codec.http.LastHttpContent;
30 import io.netty.util.AsciiString;
31 import io.netty.util.internal.ObjectUtil;
32
33 import java.util.Iterator;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Map.Entry;
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 public class SpdyHttpEncoder extends MessageToMessageEncoder<HttpObject> {
124
125 private int currentStreamId;
126
127 private final boolean validateHeaders;
128 private final boolean headersToLowerCase;
129
130
131
132
133
134
135 public SpdyHttpEncoder(SpdyVersion version) {
136 this(version, true, true);
137 }
138
139
140
141
142
143
144
145
146
147 public SpdyHttpEncoder(SpdyVersion version, boolean headersToLowerCase, boolean validateHeaders) {
148 ObjectUtil.checkNotNull(version, "version");
149 this.headersToLowerCase = headersToLowerCase;
150 this.validateHeaders = validateHeaders;
151 }
152
153 @Override
154 protected void encode(ChannelHandlerContext ctx, HttpObject msg, List<Object> out) throws Exception {
155
156 boolean valid = false;
157 boolean last = false;
158
159 if (msg instanceof HttpRequest) {
160
161 HttpRequest httpRequest = (HttpRequest) msg;
162 SpdySynStreamFrame spdySynStreamFrame = createSynStreamFrame(httpRequest);
163 out.add(spdySynStreamFrame);
164
165 last = spdySynStreamFrame.isLast() || spdySynStreamFrame.isUnidirectional();
166 valid = true;
167 }
168 if (msg instanceof HttpResponse) {
169
170 HttpResponse httpResponse = (HttpResponse) msg;
171 SpdyHeadersFrame spdyHeadersFrame = createHeadersFrame(httpResponse);
172 out.add(spdyHeadersFrame);
173
174 last = spdyHeadersFrame.isLast();
175 valid = true;
176 }
177 if (msg instanceof HttpContent && !last) {
178
179 HttpContent chunk = (HttpContent) msg;
180
181 chunk.content().retain();
182 SpdyDataFrame spdyDataFrame = new DefaultSpdyDataFrame(currentStreamId, chunk.content());
183 if (chunk instanceof LastHttpContent) {
184 LastHttpContent trailer = (LastHttpContent) chunk;
185 HttpHeaders trailers = trailer.trailingHeaders();
186 if (trailers.isEmpty()) {
187 spdyDataFrame.setLast(true);
188 out.add(spdyDataFrame);
189 } else {
190
191 SpdyHeadersFrame spdyHeadersFrame = new DefaultSpdyHeadersFrame(currentStreamId, validateHeaders);
192 spdyHeadersFrame.setLast(true);
193 Iterator<Entry<CharSequence, CharSequence>> itr = trailers.iteratorCharSequence();
194 while (itr.hasNext()) {
195 Map.Entry<CharSequence, CharSequence> entry = itr.next();
196 final CharSequence headerName =
197 headersToLowerCase ? AsciiString.of(entry.getKey()).toLowerCase() : entry.getKey();
198 spdyHeadersFrame.headers().add(headerName, entry.getValue());
199 }
200
201
202 out.add(spdyDataFrame);
203 out.add(spdyHeadersFrame);
204 }
205 } else {
206 out.add(spdyDataFrame);
207 }
208
209 valid = true;
210 }
211
212 if (!valid) {
213 throw new UnsupportedMessageTypeException(msg);
214 }
215 }
216
217 @SuppressWarnings("deprecation")
218 private SpdySynStreamFrame createSynStreamFrame(HttpRequest httpRequest) throws Exception {
219
220 final HttpHeaders httpHeaders = httpRequest.headers();
221 int streamId = httpHeaders.getInt(SpdyHttpHeaders.Names.STREAM_ID);
222 int associatedToStreamId = httpHeaders.getInt(SpdyHttpHeaders.Names.ASSOCIATED_TO_STREAM_ID, 0);
223 byte priority = (byte) httpHeaders.getInt(SpdyHttpHeaders.Names.PRIORITY, 0);
224 CharSequence scheme = httpHeaders.get(SpdyHttpHeaders.Names.SCHEME);
225 httpHeaders.remove(SpdyHttpHeaders.Names.STREAM_ID);
226 httpHeaders.remove(SpdyHttpHeaders.Names.ASSOCIATED_TO_STREAM_ID);
227 httpHeaders.remove(SpdyHttpHeaders.Names.PRIORITY);
228 httpHeaders.remove(SpdyHttpHeaders.Names.SCHEME);
229
230
231
232 httpHeaders.remove(HttpHeaderNames.CONNECTION);
233 httpHeaders.remove("Keep-Alive");
234 httpHeaders.remove("Proxy-Connection");
235 httpHeaders.remove(HttpHeaderNames.TRANSFER_ENCODING);
236
237 SpdySynStreamFrame spdySynStreamFrame =
238 new DefaultSpdySynStreamFrame(streamId, associatedToStreamId, priority, validateHeaders);
239
240
241 SpdyHeaders frameHeaders = spdySynStreamFrame.headers();
242 frameHeaders.set(SpdyHeaders.HttpNames.METHOD, httpRequest.method().name());
243 frameHeaders.set(SpdyHeaders.HttpNames.PATH, httpRequest.uri());
244 frameHeaders.set(SpdyHeaders.HttpNames.VERSION, httpRequest.protocolVersion().text());
245
246
247 CharSequence host = httpHeaders.get(HttpHeaderNames.HOST);
248 httpHeaders.remove(HttpHeaderNames.HOST);
249 frameHeaders.set(SpdyHeaders.HttpNames.HOST, host);
250
251
252 if (scheme == null) {
253 scheme = "https";
254 }
255 frameHeaders.set(SpdyHeaders.HttpNames.SCHEME, scheme);
256
257
258 Iterator<Entry<CharSequence, CharSequence>> itr = httpHeaders.iteratorCharSequence();
259 while (itr.hasNext()) {
260 Map.Entry<CharSequence, CharSequence> entry = itr.next();
261 final CharSequence headerName =
262 headersToLowerCase ? AsciiString.of(entry.getKey()).toLowerCase() : entry.getKey();
263 frameHeaders.add(headerName, entry.getValue());
264 }
265 currentStreamId = spdySynStreamFrame.streamId();
266 if (associatedToStreamId == 0) {
267 spdySynStreamFrame.setLast(isLast(httpRequest));
268 } else {
269 spdySynStreamFrame.setUnidirectional(true);
270 }
271
272 return spdySynStreamFrame;
273 }
274
275 @SuppressWarnings("deprecation")
276 private SpdyHeadersFrame createHeadersFrame(HttpResponse httpResponse) throws Exception {
277
278 final HttpHeaders httpHeaders = httpResponse.headers();
279 int streamId = httpHeaders.getInt(SpdyHttpHeaders.Names.STREAM_ID);
280 httpHeaders.remove(SpdyHttpHeaders.Names.STREAM_ID);
281
282
283
284 httpHeaders.remove(HttpHeaderNames.CONNECTION);
285 httpHeaders.remove("Keep-Alive");
286 httpHeaders.remove("Proxy-Connection");
287 httpHeaders.remove(HttpHeaderNames.TRANSFER_ENCODING);
288
289 SpdyHeadersFrame spdyHeadersFrame;
290 if (SpdyCodecUtil.isServerId(streamId)) {
291 spdyHeadersFrame = new DefaultSpdyHeadersFrame(streamId, validateHeaders);
292 } else {
293 spdyHeadersFrame = new DefaultSpdySynReplyFrame(streamId, validateHeaders);
294 }
295 SpdyHeaders frameHeaders = spdyHeadersFrame.headers();
296
297 frameHeaders.set(SpdyHeaders.HttpNames.STATUS, httpResponse.status().codeAsText());
298 frameHeaders.set(SpdyHeaders.HttpNames.VERSION, httpResponse.protocolVersion().text());
299
300
301 Iterator<Entry<CharSequence, CharSequence>> itr = httpHeaders.iteratorCharSequence();
302 while (itr.hasNext()) {
303 Map.Entry<CharSequence, CharSequence> entry = itr.next();
304 final CharSequence headerName =
305 headersToLowerCase ? AsciiString.of(entry.getKey()).toLowerCase() : entry.getKey();
306 spdyHeadersFrame.headers().add(headerName, entry.getValue());
307 }
308
309 currentStreamId = streamId;
310 spdyHeadersFrame.setLast(isLast(httpResponse));
311
312 return spdyHeadersFrame;
313 }
314
315
316
317
318
319
320
321 private static boolean isLast(HttpMessage httpMessage) {
322 if (httpMessage instanceof FullHttpMessage) {
323 FullHttpMessage fullMessage = (FullHttpMessage) httpMessage;
324 if (fullMessage.trailingHeaders().isEmpty() && !fullMessage.content().isReadable()) {
325 return true;
326 }
327 }
328
329 return false;
330 }
331 }