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