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