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
107 public AbstractHttpLogicHandler(final ProxyIoSession proxyIoSession) {
108 super(proxyIoSession);
109 }
110
111
112
113
114
115
116
117
118 public synchronized void messageReceived(final NextFilter nextFilter, final IoBuffer buf) throws ProxyAuthException {
119 LOGGER.debug(" messageReceived()");
120
121 IoBufferDecoder decoder = (IoBufferDecoder) getSession().getAttribute(DECODER);
122 if (decoder == null) {
123 decoder = new IoBufferDecoder(HTTP_DELIMITER);
124 getSession().setAttribute(DECODER, decoder);
125 }
126
127 try {
128 if (parsedResponse == null) {
129
130 responseData = decoder.decodeFully(buf);
131 if (responseData == null) {
132 return;
133 }
134
135
136 String responseHeader = responseData.getString(getProxyIoSession().getCharset().newDecoder());
137 entityBodyStartPosition = responseData.position();
138
139 LOGGER.debug(" response header received:\n{}",
140 responseHeader.replace("\r", "\\r").replace("\n", "\\n\n"));
141
142
143 parsedResponse = decodeResponse(responseHeader);
144
145
146 if (parsedResponse.getStatusCode() == 200
147 || (parsedResponse.getStatusCode() >= 300 && parsedResponse.getStatusCode() <= 307)) {
148 buf.position(0);
149 setHandshakeComplete();
150 return;
151 }
152
153 String contentLengthHeader = StringUtilities.getSingleValuedHeader(parsedResponse.getHeaders(),
154 "Content-Length");
155
156 if (contentLengthHeader == null) {
157 contentLength = 0;
158 } else {
159 contentLength = Integer.parseInt(contentLengthHeader.trim());
160 decoder.setContentLength(contentLength, true);
161 }
162 }
163
164 if (!hasChunkedData) {
165 if (contentLength > 0) {
166 IoBuffer tmp = decoder.decodeFully(buf);
167 if (tmp == null) {
168 return;
169 }
170 responseData.setAutoExpand(true);
171 responseData.put(tmp);
172 contentLength = 0;
173 }
174
175 if ("chunked".equalsIgnoreCase(StringUtilities.getSingleValuedHeader(parsedResponse.getHeaders(),
176 "Transfer-Encoding"))) {
177
178 LOGGER.debug("Retrieving additional http response chunks");
179 hasChunkedData = true;
180 waitingChunkedData = true;
181 }
182 }
183
184 if (hasChunkedData) {
185
186 while (waitingChunkedData) {
187 if (contentLength == 0) {
188 decoder.setDelimiter(CRLF_DELIMITER, false);
189 IoBuffer tmp = decoder.decodeFully(buf);
190 if (tmp == null) {
191 return;
192 }
193
194 String chunkSize = tmp.getString(getProxyIoSession().getCharset().newDecoder());
195 int pos = chunkSize.indexOf(';');
196 if (pos >= 0) {
197 chunkSize = chunkSize.substring(0, pos);
198 } else {
199 chunkSize = chunkSize.substring(0, chunkSize.length() - 2);
200 }
201 contentLength = Integer.decode("0x" + chunkSize);
202 if (contentLength > 0) {
203 contentLength += 2;
204 decoder.setContentLength(contentLength, true);
205 }
206 }
207
208 if (contentLength == 0) {
209 waitingChunkedData = false;
210 waitingFooters = true;
211 entityBodyLimitPosition = responseData.position();
212 break;
213 }
214
215 IoBuffer tmp = decoder.decodeFully(buf);
216 if (tmp == null) {
217 return;
218 }
219 contentLength = 0;
220 responseData.put(tmp);
221 buf.position(buf.position());
222 }
223
224
225 while (waitingFooters) {
226 decoder.setDelimiter(CRLF_DELIMITER, false);
227 IoBuffer tmp = decoder.decodeFully(buf);
228 if (tmp == null) {
229 return;
230 }
231
232 if (tmp.remaining() == 2) {
233 waitingFooters = false;
234 break;
235 }
236
237
238 String footer = tmp.getString(getProxyIoSession().getCharset().newDecoder());
239 String[] f = footer.split(":\\s?", 2);
240 StringUtilities.addValueToHeader(parsedResponse.getHeaders(), f[0], f[1], false);
241 responseData.put(tmp);
242 responseData.put(CRLF_DELIMITER);
243 }
244 }
245
246 responseData.flip();
247
248 LOGGER.debug(" end of response received:\n{}",
249 responseData.getString(getProxyIoSession().getCharset().newDecoder()));
250
251
252 responseData.position(entityBodyStartPosition);
253 responseData.limit(entityBodyLimitPosition);
254 parsedResponse.setBody(responseData.getString(getProxyIoSession().getCharset().newDecoder()));
255
256
257 responseData.free();
258 responseData = null;
259
260 handleResponse(parsedResponse);
261
262 parsedResponse = null;
263 hasChunkedData = false;
264 contentLength = -1;
265 decoder.setDelimiter(HTTP_DELIMITER, true);
266
267 if (!isHandshakeComplete()) {
268 doHandshake(nextFilter);
269 }
270 } catch (Exception ex) {
271 if (ex instanceof ProxyAuthException) {
272 throw ((ProxyAuthException) ex);
273 }
274
275 throw new ProxyAuthException("Handshake failed", ex);
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 protected HttpProxyResponse decodeResponse(final String response) throws Exception {
360 LOGGER.debug(" parseResponse()");
361
362
363 String[] responseLines = response.split(HttpProxyConstants.CRLF);
364
365
366
367
368 String[] statusLine = responseLines[0].trim().split(" ", 2);
369
370 if (statusLine.length < 2) {
371 throw new Exception("Invalid response status line (" + statusLine + "). Response: " + response);
372 }
373
374
375 if (statusLine[1].matches("^\\d\\d\\d")) {
376 throw new Exception("Invalid response code (" + statusLine[1] + "). Response: " + response);
377 }
378
379 Map<String, List<String>> headers = new HashMap<String, List<String>>();
380
381 for (int i = 1; i < responseLines.length; i++) {
382 String[] args = responseLines[i].split(":\\s?", 2);
383 StringUtilities.addValueToHeader(headers, args[0], args[1], false);
384 }
385
386 return new HttpProxyResponse(statusLine[0], statusLine[1], headers);
387 }
388 }