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