1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.spdy.server.proxy;
20
21 import java.io.IOException;
22 import java.nio.ByteBuffer;
23 import java.util.regex.Matcher;
24 import java.util.regex.Pattern;
25
26 import org.eclipse.jetty.http.HttpField;
27 import org.eclipse.jetty.http.HttpFields;
28 import org.eclipse.jetty.http.HttpGenerator;
29 import org.eclipse.jetty.http.HttpHeader;
30 import org.eclipse.jetty.http.HttpHeaderValue;
31 import org.eclipse.jetty.http.HttpMethod;
32 import org.eclipse.jetty.http.HttpParser;
33 import org.eclipse.jetty.http.HttpVersion;
34 import org.eclipse.jetty.io.EndPoint;
35 import org.eclipse.jetty.server.Connector;
36 import org.eclipse.jetty.server.HttpConfiguration;
37 import org.eclipse.jetty.server.HttpConnection;
38 import org.eclipse.jetty.server.SslConnectionFactory;
39 import org.eclipse.jetty.spdy.ISession;
40 import org.eclipse.jetty.spdy.IStream;
41 import org.eclipse.jetty.spdy.StandardSession;
42 import org.eclipse.jetty.spdy.StandardStream;
43 import org.eclipse.jetty.spdy.api.ByteBufferDataInfo;
44 import org.eclipse.jetty.spdy.api.DataInfo;
45 import org.eclipse.jetty.spdy.api.GoAwayInfo;
46 import org.eclipse.jetty.spdy.api.GoAwayResultInfo;
47 import org.eclipse.jetty.spdy.api.HeadersInfo;
48 import org.eclipse.jetty.spdy.api.PushInfo;
49 import org.eclipse.jetty.spdy.api.ReplyInfo;
50 import org.eclipse.jetty.spdy.api.RstInfo;
51 import org.eclipse.jetty.spdy.api.SessionStatus;
52 import org.eclipse.jetty.spdy.api.Stream;
53 import org.eclipse.jetty.spdy.api.StreamFrameListener;
54 import org.eclipse.jetty.spdy.api.SynInfo;
55 import org.eclipse.jetty.spdy.server.http.HTTPSPDYHeader;
56 import org.eclipse.jetty.util.BufferUtil;
57 import org.eclipse.jetty.util.Callback;
58 import org.eclipse.jetty.util.Fields;
59 import org.eclipse.jetty.util.Promise;
60
61 public class ProxyHTTPSPDYConnection extends HttpConnection implements HttpParser.RequestHandler<ByteBuffer>
62 {
63 private final short version;
64 private final Fields headers = new Fields();
65 private final ProxyEngineSelector proxyEngineSelector;
66 private final ISession session;
67 private HTTPStream stream;
68 private ByteBuffer content;
69
70 public ProxyHTTPSPDYConnection(Connector connector, HttpConfiguration config, EndPoint endPoint, short version, ProxyEngineSelector proxyEngineSelector)
71 {
72 super(config, connector, endPoint);
73 this.version = version;
74 this.proxyEngineSelector = proxyEngineSelector;
75 this.session = new HTTPSession(version, connector);
76 }
77
78 @Override
79 protected HttpParser.RequestHandler<ByteBuffer> newRequestHandler()
80 {
81 return this;
82 }
83
84 @Override
85 public boolean startRequest(HttpMethod method, String methodString, ByteBuffer uri, HttpVersion httpVersion)
86 {
87 Connector connector = getConnector();
88 String scheme = connector.getConnectionFactory(SslConnectionFactory.class) != null ? "https" : "http";
89 headers.put(HTTPSPDYHeader.SCHEME.name(version), scheme);
90 headers.put(HTTPSPDYHeader.METHOD.name(version), methodString);
91 headers.put(HTTPSPDYHeader.URI.name(version), BufferUtil.toUTF8String(uri));
92 headers.put(HTTPSPDYHeader.VERSION.name(version), httpVersion.asString());
93 return false;
94 }
95
96 @Override
97 public boolean parsedHeader(HttpField field)
98 {
99 if (field.getHeader() == HttpHeader.HOST)
100 headers.put(HTTPSPDYHeader.HOST.name(version), field.getValue());
101 else
102 headers.put(field.getName(), field.getValue());
103 return false;
104 }
105
106 @Override
107 public boolean parsedHostHeader(String host, int port)
108 {
109 return false;
110 }
111
112 @Override
113 public boolean headerComplete()
114 {
115 return false;
116 }
117
118 @Override
119 public boolean content(ByteBuffer item)
120 {
121 if (content == null)
122 {
123 stream = syn(false);
124 content = item;
125 }
126 else
127 {
128 stream.getStreamFrameListener().onData(stream, toDataInfo(item, false));
129 }
130 return false;
131 }
132
133 @Override
134 public boolean messageComplete()
135 {
136 if (stream == null)
137 {
138 assert content == null;
139 if (headers.isEmpty())
140 proxyEngineSelector.onGoAway(session, new GoAwayResultInfo(0, SessionStatus.OK));
141 else
142 syn(true);
143 }
144 else
145 {
146 stream.getStreamFrameListener().onData(stream, toDataInfo(content, true));
147 }
148 return false;
149 }
150
151 @Override
152 public void completed()
153 {
154 headers.clear();
155 stream = null;
156 content = null;
157 super.completed();
158 }
159
160 @Override
161 public int getHeaderCacheSize()
162 {
163
164 return 256;
165 }
166
167 @Override
168 public void earlyEOF()
169 {
170
171 }
172
173 @Override
174 public void badMessage(int status, String reason)
175 {
176
177 }
178
179 private HTTPStream syn(boolean close)
180 {
181 HTTPStream stream = new HTTPStream(1, (byte)0, session, null);
182 StreamFrameListener streamFrameListener = proxyEngineSelector.onSyn(stream, new SynInfo(headers, close));
183 stream.setStreamFrameListener(streamFrameListener);
184 return stream;
185 }
186
187 private DataInfo toDataInfo(ByteBuffer buffer, boolean close)
188 {
189 return new ByteBufferDataInfo(buffer, close);
190 }
191
192 private class HTTPSession extends StandardSession
193 {
194 private HTTPSession(short version, Connector connector)
195 {
196 super(version, connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), null,
197 getEndPoint(), null, 1, proxyEngineSelector, null, null);
198 }
199
200 @Override
201 public void rst(RstInfo rstInfo, Callback handler)
202 {
203 HttpGenerator.ResponseInfo info = new HttpGenerator.ResponseInfo(HttpVersion.fromString(headers.get
204 ("version").value()), null, 0, 502, "SPDY reset received from upstream server", false);
205 send(info, null, true, new Callback.Adapter());
206 }
207
208 @Override
209 public void goAway(GoAwayInfo goAwayInfo, Callback handler)
210 {
211 ProxyHTTPSPDYConnection.this.close();
212 handler.succeeded();
213 }
214 }
215
216
217
218
219 private class HTTPStream extends StandardStream
220 {
221 private final Pattern statusRegexp = Pattern.compile("(\\d{3})\\s+(.*)");
222
223 private HTTPStream(int id, byte priority, ISession session, IStream associatedStream)
224 {
225 super(id, priority, session, associatedStream, null);
226 }
227
228 @Override
229 public void push(PushInfo pushInfo, Promise<Stream> handler)
230 {
231
232 handler.succeeded(new HTTPPushStream(2, getPriority(), getSession(), this));
233 }
234
235 @Override
236 public void headers(HeadersInfo headersInfo, Callback handler)
237 {
238
239 throw new UnsupportedOperationException("Not Yet Implemented");
240 }
241
242 @Override
243 public void reply(ReplyInfo replyInfo, Callback handler)
244 {
245 try
246 {
247 Fields headers = new Fields(replyInfo.getHeaders(), false);
248
249 addPersistenceHeader(headers);
250
251 headers.remove(HTTPSPDYHeader.SCHEME.name(version));
252
253 String status = headers.remove(HTTPSPDYHeader.STATUS.name(version)).value();
254 Matcher matcher = statusRegexp.matcher(status);
255 matcher.matches();
256 int code = Integer.parseInt(matcher.group(1));
257 String reason = matcher.group(2).trim();
258
259 HttpVersion httpVersion = HttpVersion.fromString(headers.remove(HTTPSPDYHeader.VERSION.name(version)).value());
260
261
262 Fields.Field host = headers.remove(HTTPSPDYHeader.HOST.name(version));
263 if (host != null)
264 headers.put("host", host.value());
265
266 HttpFields fields = new HttpFields();
267 for (Fields.Field header : headers)
268 {
269 String name = camelize(header.name());
270 fields.put(name, header.value());
271 }
272
273
274 long contentLength = fields.getLongField(HttpHeader.CONTENT_LENGTH.asString());
275 HttpGenerator.ResponseInfo info = new HttpGenerator.ResponseInfo(httpVersion, fields, contentLength, code,
276 reason, false);
277
278
279 send(info, null, replyInfo.isClose());
280
281 if (replyInfo.isClose())
282 completed();
283
284 handler.succeeded();
285 }
286 catch (IOException x)
287 {
288 handler.failed(x);
289 }
290 }
291
292 private String camelize(String name)
293 {
294 char[] chars = name.toCharArray();
295 chars[0] = Character.toUpperCase(chars[0]);
296
297 for (int i = 0; i < chars.length; ++i)
298 {
299 char c = chars[i];
300 int j = i + 1;
301 if (c == '-' && j < chars.length)
302 chars[j] = Character.toUpperCase(chars[j]);
303 }
304 return new String(chars);
305 }
306
307 @Override
308 public void data(DataInfo dataInfo, Callback handler)
309 {
310 try
311 {
312
313 ByteBuffer byteBuffer = dataInfo.asByteBuffer(false);
314
315
316 send(null, byteBuffer, dataInfo.isClose());
317
318 if (dataInfo.isClose())
319 completed();
320
321 handler.succeeded();
322 }
323 catch (IOException x)
324 {
325 handler.failed(x);
326 }
327 }
328 }
329
330 private void addPersistenceHeader(Fields headersToAddTo)
331 {
332 HttpVersion httpVersion = HttpVersion.fromString(headers.get("version").value());
333 boolean persistent = false;
334 switch (httpVersion)
335 {
336 case HTTP_1_0:
337 {
338 Fields.Field keepAliveHeader = headers.get(HttpHeader.KEEP_ALIVE.asString());
339 if (keepAliveHeader != null)
340 persistent = HttpHeaderValue.KEEP_ALIVE.asString().equals(keepAliveHeader.value());
341 if (!persistent)
342 persistent = HttpMethod.CONNECT.is(headers.get("method").value());
343 if (persistent)
344 headersToAddTo.add(HttpHeader.CONNECTION.asString(), HttpHeaderValue.KEEP_ALIVE.asString());
345 break;
346 }
347 case HTTP_1_1:
348 {
349 Fields.Field connectionHeader = headers.get(HttpHeader.CONNECTION.asString());
350 if (connectionHeader != null)
351 persistent = !HttpHeaderValue.CLOSE.asString().equals(connectionHeader.value());
352 else
353 persistent = true;
354 if (!persistent)
355 persistent = HttpMethod.CONNECT.is(headers.get("method").value());
356 if (!persistent)
357 headersToAddTo.add(HttpHeader.CONNECTION.asString(), HttpHeaderValue.CLOSE.asString());
358 break;
359 }
360 default:
361 {
362 throw new IllegalStateException();
363 }
364 }
365 }
366
367 private class HTTPPushStream extends StandardStream
368 {
369 private HTTPPushStream(int id, byte priority, ISession session, IStream associatedStream)
370 {
371 super(id, priority, session, associatedStream, null);
372 }
373
374 @Override
375 public void headers(HeadersInfo headersInfo, Callback handler)
376 {
377
378 handler.succeeded();
379 }
380
381 @Override
382 public void data(DataInfo dataInfo, Callback handler)
383 {
384
385 handler.succeeded();
386 }
387 }
388 }