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.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.server.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 headers.clear();
148 stream = null;
149 content = null;
150 return false;
151 }
152
153 @Override
154 public boolean earlyEOF()
155 {
156
157 return false;
158 }
159
160 @Override
161 public void badMessage(int status, String reason)
162 {
163
164 }
165
166 private HTTPStream syn(boolean close)
167 {
168 HTTPStream stream = new HTTPStream(1, (byte)0, session, null);
169 StreamFrameListener streamFrameListener = proxyEngineSelector.onSyn(stream, new SynInfo(headers, close));
170 stream.setStreamFrameListener(streamFrameListener);
171 return stream;
172 }
173
174 private DataInfo toDataInfo(ByteBuffer buffer, boolean close)
175 {
176 return new ByteBufferDataInfo(buffer, close);
177 }
178
179 private class HTTPSession extends StandardSession
180 {
181 private HTTPSession(short version, Connector connector)
182 {
183 super(version, connector.getByteBufferPool(), connector.getExecutor(), connector.getScheduler(), null,
184 getEndPoint(), null, 1, proxyEngineSelector, null, null);
185 }
186
187 @Override
188 public void rst(RstInfo rstInfo, Callback handler)
189 {
190
191 goAway(new GoAwayInfo(rstInfo.getTimeout(), rstInfo.getUnit()), handler);
192 }
193
194 @Override
195 public void goAway(GoAwayInfo goAwayInfo, Callback handler)
196 {
197 getEndPoint().close();
198 handler.succeeded();
199 }
200 }
201
202
203
204
205 private class HTTPStream extends StandardStream
206 {
207 private final Pattern statusRegexp = Pattern.compile("(\\d{3})\\s+(.*)");
208
209 private HTTPStream(int id, byte priority, ISession session, IStream associatedStream)
210 {
211 super(id, priority, session, associatedStream, null);
212 }
213
214 @Override
215 public void push(PushInfo pushInfo, Promise<Stream> handler)
216 {
217
218 handler.succeeded(new HTTPPushStream(2, getPriority(), getSession(), this));
219 }
220
221 @Override
222 public void headers(HeadersInfo headersInfo, Callback handler)
223 {
224
225 throw new UnsupportedOperationException("Not Yet Implemented");
226 }
227
228 @Override
229 public void reply(ReplyInfo replyInfo, Callback handler)
230 {
231 try
232 {
233 Fields headers = new Fields(replyInfo.getHeaders(), false);
234
235 headers.remove(HTTPSPDYHeader.SCHEME.name(version));
236
237 String status = headers.remove(HTTPSPDYHeader.STATUS.name(version)).value();
238 Matcher matcher = statusRegexp.matcher(status);
239 matcher.matches();
240 int code = Integer.parseInt(matcher.group(1));
241 String reason = matcher.group(2).trim();
242
243 HttpVersion httpVersion = HttpVersion.fromString(headers.remove(HTTPSPDYHeader.VERSION.name(version)).value());
244
245
246 Fields.Field host = headers.remove(HTTPSPDYHeader.HOST.name(version));
247 if (host != null)
248 headers.put("host", host.value());
249
250 HttpFields fields = new HttpFields();
251 for (Fields.Field header : headers)
252 {
253 String name = camelize(header.name());
254 fields.put(name, header.value());
255 }
256
257
258 long contentLength = fields.getLongField(HttpHeader.CONTENT_LENGTH.asString());
259 HttpGenerator.ResponseInfo info = new HttpGenerator.ResponseInfo(httpVersion, fields, contentLength, code,
260 reason, false);
261 send(info, null, replyInfo.isClose());
262
263 if (replyInfo.isClose())
264 completed();
265
266 handler.succeeded();
267 }
268 catch (IOException x)
269 {
270 handler.failed(x);
271 }
272 }
273
274 private String camelize(String name)
275 {
276 char[] chars = name.toCharArray();
277 chars[0] = Character.toUpperCase(chars[0]);
278
279 for (int i = 0; i < chars.length; ++i)
280 {
281 char c = chars[i];
282 int j = i + 1;
283 if (c == '-' && j < chars.length)
284 chars[j] = Character.toUpperCase(chars[j]);
285 }
286 return new String(chars);
287 }
288
289 @Override
290 public void data(DataInfo dataInfo, Callback handler)
291 {
292 try
293 {
294
295 ByteBuffer byteBuffer = dataInfo.asByteBuffer(false);
296
297 send(null, byteBuffer, dataInfo.isClose());
298
299 if (dataInfo.isClose())
300 completed();
301
302 handler.succeeded();
303 }
304 catch (IOException x)
305 {
306 handler.failed(x);
307 }
308 }
309 }
310
311 private class HTTPPushStream extends StandardStream
312 {
313 private HTTPPushStream(int id, byte priority, ISession session, IStream associatedStream)
314 {
315 super(id, priority, session, associatedStream, null);
316 }
317
318 @Override
319 public void headers(HeadersInfo headersInfo, Callback handler)
320 {
321
322 handler.succeeded();
323 }
324
325 @Override
326 public void data(DataInfo dataInfo, Callback handler)
327 {
328
329 handler.succeeded();
330 }
331 }
332 }