1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package org.apache.mina.proxy.handlers.http;
21
22 import java.io.UnsupportedEncodingException;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Map;
26
27 import org.apache.mina.core.buffer.IoBuffer;
28 import org.apache.mina.core.filterchain.IoFilter.NextFilter;
29 import org.apache.mina.core.future.ConnectFuture;
30 import org.apache.mina.core.future.IoFutureListener;
31 import org.apache.mina.core.session.IoSession;
32 import org.apache.mina.core.session.IoSessionInitializer;
33 import org.apache.mina.proxy.AbstractProxyLogicHandler;
34 import org.apache.mina.proxy.ProxyAuthException;
35 import org.apache.mina.proxy.session.ProxyIoSession;
36 import org.apache.mina.proxy.utils.IoBufferDecoder;
37 import org.apache.mina.proxy.utils.StringUtilities;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41
42
43
44
45
46
47
48 public abstract class AbstractHttpLogicHandler extends
49 AbstractProxyLogicHandler {
50 private final static Logger LOGGER = LoggerFactory
51 .getLogger(AbstractHttpLogicHandler.class);
52
53 private final static String DECODER = AbstractHttpLogicHandler.class
54 .getName()
55 + ".Decoder";
56
57 private final static byte[] HTTP_DELIMITER = new byte[] { '\r', '\n', '\r',
58 '\n' };
59
60 private final static byte[] CRLF_DELIMITER = new byte[] { '\r', '\n' };
61
62
63
64
65
66
67 private IoBuffer responseData = null;
68
69
70
71
72 private HttpProxyResponse parsedResponse = null;
73
74
75
76
77 private int contentLength = -1;
78
79
80
81
82
83
84 private boolean hasChunkedData;
85
86
87
88
89 private boolean waitingChunkedData;
90
91
92
93
94 private boolean waitingFooters;
95
96
97
98
99 private int entityBodyStartPosition;
100
101
102
103
104 private int entityBodyLimitPosition;
105
106
107
108
109
110
111
112 public AbstractHttpLogicHandler(final ProxyIoSession proxyIoSession) {
113 super(proxyIoSession);
114 }
115
116
117
118
119
120
121
122
123 public synchronized void messageReceived(final NextFilter nextFilter,
124 final IoBuffer buf) throws ProxyAuthException {
125 LOGGER.debug(" messageReceived()");
126
127 IoBufferDecoder decoder = (IoBufferDecoder) getSession().getAttribute(
128 DECODER);
129 if (decoder == null) {
130 decoder = new IoBufferDecoder(HTTP_DELIMITER);
131 getSession().setAttribute(DECODER, decoder);
132 }
133
134 try {
135 if (parsedResponse == null) {
136
137 responseData = decoder.decodeFully(buf);
138 if (responseData == null) {
139 return;
140 }
141
142
143 String responseHeader = responseData
144 .getString(getProxyIoSession().getCharset()
145 .newDecoder());
146 entityBodyStartPosition = responseData.position();
147
148 LOGGER.debug(" response header received:\n{}", responseHeader
149 .replace("\r", "\\r").replace("\n", "\\n\n"));
150
151
152 parsedResponse = decodeResponse(responseHeader);
153
154
155 if (parsedResponse.getStatusCode() == 200
156 || (parsedResponse.getStatusCode() >= 300 && parsedResponse
157 .getStatusCode() <= 307)) {
158 buf.position(0);
159 setHandshakeComplete();
160 return;
161 }
162
163 String contentLengthHeader = StringUtilities
164 .getSingleValuedHeader(parsedResponse.getHeaders(),
165 "Content-Length");
166
167 if (contentLengthHeader == null) {
168 contentLength = 0;
169 } else {
170 contentLength = Integer
171 .parseInt(contentLengthHeader.trim());
172 decoder.setContentLength(contentLength, true);
173 }
174 }
175
176 if (!hasChunkedData) {
177 if (contentLength > 0) {
178 IoBuffer tmp = decoder.decodeFully(buf);
179 if (tmp == null) {
180 return;
181 }
182 responseData.setAutoExpand(true);
183 responseData.put(tmp);
184 contentLength = 0;
185 }
186
187 if ("chunked".equalsIgnoreCase(StringUtilities
188 .getSingleValuedHeader(parsedResponse.getHeaders(),
189 "Transfer-Encoding"))) {
190
191 LOGGER.debug("Retrieving additional http response chunks");
192 hasChunkedData = true;
193 waitingChunkedData = true;
194 }
195 }
196
197 if (hasChunkedData) {
198
199 while (waitingChunkedData) {
200 if (contentLength == 0) {
201 decoder.setDelimiter(CRLF_DELIMITER, false);
202 IoBuffer tmp = decoder.decodeFully(buf);
203 if (tmp == null) {
204 return;
205 }
206
207 String chunkSize = tmp.getString(getProxyIoSession()
208 .getCharset().newDecoder());
209 int pos = chunkSize.indexOf(';');
210 if (pos >= 0) {
211 chunkSize = chunkSize.substring(0, pos);
212 } else {
213 chunkSize = chunkSize.substring(0, chunkSize
214 .length() - 2);
215 }
216 contentLength = Integer.decode("0x" + chunkSize);
217 if (contentLength > 0) {
218 contentLength += 2;
219 decoder.setContentLength(contentLength, true);
220 }
221 }
222
223 if (contentLength == 0) {
224 waitingChunkedData = false;
225 waitingFooters = true;
226 entityBodyLimitPosition = responseData.position();
227 break;
228 }
229
230 IoBuffer tmp = decoder.decodeFully(buf);
231 if (tmp == null) {
232 return;
233 }
234 contentLength = 0;
235 responseData.put(tmp);
236 buf.position(buf.position());
237 }
238
239
240 while (waitingFooters) {
241 decoder.setDelimiter(CRLF_DELIMITER, false);
242 IoBuffer tmp = decoder.decodeFully(buf);
243 if (tmp == null) {
244 return;
245 }
246
247 if (tmp.remaining() == 2) {
248 waitingFooters = false;
249 break;
250 }
251
252
253 String footer = tmp.getString(getProxyIoSession()
254 .getCharset().newDecoder());
255 String[] f = footer.split(":\\s?", 2);
256 StringUtilities.addValueToHeader(parsedResponse
257 .getHeaders(), f[0], f[1], false);
258 responseData.put(tmp);
259 responseData.put(CRLF_DELIMITER);
260 }
261 }
262
263 responseData.flip();
264
265 LOGGER.debug(" end of response received:\n{}",
266 responseData.getString(getProxyIoSession().getCharset()
267 .newDecoder()));
268
269
270 responseData.position(entityBodyStartPosition);
271 responseData.limit(entityBodyLimitPosition);
272 parsedResponse.setBody(responseData.getString(getProxyIoSession()
273 .getCharset().newDecoder()));
274
275
276 responseData.free();
277 responseData = null;
278
279 handleResponse(parsedResponse);
280
281 parsedResponse = null;
282 hasChunkedData = false;
283 contentLength = -1;
284 decoder.setDelimiter(HTTP_DELIMITER, true);
285
286 if (!isHandshakeComplete()) {
287 doHandshake(nextFilter);
288 }
289 } catch (Exception ex) {
290 if (ex instanceof ProxyAuthException) {
291 throw ((ProxyAuthException) ex);
292 }
293
294 throw new ProxyAuthException("Handshake failed", ex);
295 }
296 }
297
298
299
300
301
302
303 public abstract void handleResponse(final HttpProxyResponse response)
304 throws ProxyAuthException;
305
306
307
308
309
310
311
312
313 public void writeRequest(final NextFilter nextFilter,
314 final HttpProxyRequest request) {
315 ProxyIoSession proxyIoSession = getProxyIoSession();
316
317 if (proxyIoSession.isReconnectionNeeded()) {
318 reconnect(nextFilter, request);
319 } else {
320 writeRequest0(nextFilter, request);
321 }
322 }
323
324
325
326
327
328
329
330 private void writeRequest0(final NextFilter nextFilter,
331 final HttpProxyRequest request) {
332 try {
333 String data = request.toHttpString();
334 IoBuffer buf = IoBuffer.wrap(data.getBytes(getProxyIoSession()
335 .getCharsetName()));
336
337 LOGGER.debug(" write:\n{}", data.replace("\r", "\\r").replace(
338 "\n", "\\n\n"));
339
340 writeData(nextFilter, buf);
341
342 } catch (UnsupportedEncodingException ex) {
343 closeSession("Unable to send HTTP request: ", ex);
344 }
345 }
346
347
348
349
350
351
352
353
354 private void reconnect(final NextFilter nextFilter,
355 final HttpProxyRequest request) {
356 LOGGER.debug("Reconnecting to proxy ...");
357
358 final ProxyIoSession proxyIoSession = getProxyIoSession();
359
360
361 proxyIoSession.getConnector().connect(
362 new IoSessionInitializer<ConnectFuture>() {
363 public void initializeSession(final IoSession session,
364 ConnectFuture future) {
365 LOGGER.debug("Initializing new session: {}", session);
366 session.setAttribute(ProxyIoSession.PROXY_SESSION,
367 proxyIoSession);
368 proxyIoSession.setSession(session);
369 LOGGER.debug(" setting up proxyIoSession: {}", proxyIoSession);
370 future
371 .addListener(new IoFutureListener<ConnectFuture>() {
372 public void operationComplete(
373 ConnectFuture future) {
374
375
376 proxyIoSession
377 .setReconnectionNeeded(false);
378 writeRequest0(nextFilter, request);
379 }
380 });
381 }
382 });
383 }
384
385
386
387
388
389
390 protected HttpProxyResponse decodeResponse(final String response)
391 throws Exception {
392 LOGGER.debug(" parseResponse()");
393
394
395 String[] responseLines = response.split(HttpProxyConstants.CRLF);
396
397
398
399
400 String[] statusLine = responseLines[0].trim().split(" ", 2);
401
402 if (statusLine.length < 2) {
403 throw new Exception("Invalid response status line (" + statusLine
404 + "). Response: " + response);
405 }
406
407
408 if (statusLine[1].matches("^\\d\\d\\d")) {
409 throw new Exception("Invalid response code (" + statusLine[1]
410 + "). Response: " + response);
411 }
412
413 Map<String, List<String>> headers = new HashMap<String, List<String>>();
414
415 for (int i = 1; i < responseLines.length; i++) {
416 String[] args = responseLines[i].split(":\\s?", 2);
417 StringUtilities.addValueToHeader(headers, args[0], args[1], false);
418 }
419
420 return new HttpProxyResponse(statusLine[0], statusLine[1], headers);
421 }
422 }