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