001: /*
002: * Copyright (c) xsocket.org, 2006 - 2008. All rights reserved.
003: *
004: * This library is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License as published by the Free Software Foundation; either
007: * version 2.1 of the License, or (at your option) any later version.
008: *
009: * This library is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public
015: * License along with this library; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: *
018: * Please refer to the LGPL license at: http://www.gnu.org/copyleft/lesser.txt
019: * The latest copy of this software may be found on http://www.xsocket.org/
020: */
021: package org.xsocket.connection.http.client;
022:
023: import java.io.Closeable;
024: import java.io.IOException;
025: import java.net.ConnectException;
026: import java.net.MalformedURLException;
027: import java.net.SocketTimeoutException;
028: import java.net.URI;
029: import java.net.URISyntaxException;
030: import java.net.URL;
031: import java.nio.channels.ClosedChannelException;
032: import java.text.SimpleDateFormat;
033: import java.util.Date;
034: import java.util.LinkedList;
035: import java.util.List;
036: import java.util.concurrent.Executor;
037: import java.util.logging.Level;
038: import java.util.logging.Logger;
039:
040: import javax.net.ssl.SSLContext;
041:
042: import org.xsocket.ILifeCycle;
043: import org.xsocket.connection.IConnectionPool;
044: import org.xsocket.connection.INonBlockingConnection;
045: import org.xsocket.connection.NonBlockingConnection;
046: import org.xsocket.connection.NonBlockingConnectionPool;
047: import org.xsocket.connection.http.BodyDataSink;
048: import org.xsocket.connection.http.NonBlockingBodyDataSource;
049: import org.xsocket.connection.http.HttpRequest;
050: import org.xsocket.connection.http.HttpRequestHeader;
051: import org.xsocket.connection.http.HttpResponse;
052: import org.xsocket.connection.http.HttpResponseHeader;
053: import org.xsocket.connection.http.client.HttpClientConnection.BlockingResponseHandler;
054: import org.xsocket.connection.http.client.HttpClientConnection.ResponseHandlerAdapter;
055:
056: /**
057: * Higher level client-side abstraction of the client side endpoint. The HttpClient uses am internal pool
058: * of {@link HttpClientConnection} to perform the requests.
059: *
060: * @author grro@xsocket.org
061: */
062: public final class HttpClient implements IHttpClientEndpoint,
063: IConnectionPool, Closeable {
064:
065: private static final Logger LOG = Logger.getLogger(HttpClient.class
066: .getName());
067:
068: public static final int DEFAULT_POOLED_IDLE_TIMEOUT_MILLIS = 5 * 1000;
069: public static final int DEFAULT_POOLED_LIFE_TIMEOUT_MILLIS = 10 * 1000;
070:
071: private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(
072: "yyyy.MM.dd HH:mm:ss");
073:
074: private static final boolean DEFAULT_IS_TRANSACTION_LOG = true;
075: public static final int DEFAULT_MAX_REDIRECTS = 5;
076: public static final boolean DEFAULT_TREAT_302_REDIRECT_AS_303 = false;
077: public static final Integer DEFAULT_RECEIVE_TIMEOUT_MILLIS = Integer.MAX_VALUE;
078:
079: private int maxRedirects = DEFAULT_MAX_REDIRECTS;
080: private boolean isTreat302RedirectAs303 = DEFAULT_TREAT_302_REDIRECT_AS_303;
081: private int receiveTimeoutMillis = DEFAULT_RECEIVE_TIMEOUT_MILLIS;
082: private boolean isTransactionLog = DEFAULT_IS_TRANSACTION_LOG;
083:
084: private SSLContext sslCtx = null;
085: private NonBlockingConnectionPool pool = null;
086:
087: private boolean isPooled = true;
088: private boolean isSSLSupported = false;
089:
090: // auto handle
091: boolean isAutohandle100ContinueResponse = DEFAULT_AUTOHANDLE_100CONTINUE_RESPONSE;
092:
093: // statistics
094: private TransactionLog transactionLog = new TransactionLog(20);
095:
096: /**
097: * constructor
098: */
099: public HttpClient() {
100: this (null);
101: }
102:
103: /**
104: * constructor
105: *
106: * @param sslCtx the ssl context to use
107: */
108: public HttpClient(SSLContext sslCtx) {
109: if (sslCtx != null) {
110: this .sslCtx = sslCtx;
111: pool = new NonBlockingConnectionPool(sslCtx);
112: isSSLSupported = true;
113: } else {
114: pool = new NonBlockingConnectionPool();
115: isSSLSupported = false;
116: }
117:
118: pool.setPooledIdleTimeoutMillis(DEFAULT_IDLE_TIMEOUT_MILLIS);
119: pool.setPooledLifeTimeoutMillis(DEFAULT_LIFE_TIMEOUT_MILLIS);
120: }
121:
122: public final void setAutohandle100ContinueResponse(
123: boolean isAutohandle100ContinueResponse) {
124: this .isAutohandle100ContinueResponse = isAutohandle100ContinueResponse;
125: }
126:
127: public boolean isAutohandle100ContinueResponse() {
128: return isAutohandle100ContinueResponse;
129: }
130:
131: /**
132: * set the max redirects
133: *
134: * @param maxRedirects the max redirects
135: */
136: public void setMaxRedirects(int maxRedirects) {
137: this .maxRedirects = maxRedirects;
138: }
139:
140: /**
141: * get the max redirects
142: *
143: * @return the max redirects
144: */
145: public int getMaxRedirects() {
146: return maxRedirects;
147: }
148:
149: /**
150: * sets if a 302 response should be treat as a 303 response
151: *
152: * @param isTreat303RedirectAs302 true, if a 303 response should be treat a a 303 response
153: */
154: public void setTreat302RedirectAs303(boolean isTreat303RedirectAs302) {
155: this .isTreat302RedirectAs303 = isTreat303RedirectAs302;
156: }
157:
158: /**
159: * gets if a 302 response should be treat as a 303 response
160: *
161: * @return true, if a 302 response should be treat as a 303 response
162: */
163: public boolean isTreat302RedirectAs303() {
164: return isTreat302RedirectAs303;
165: }
166:
167: /**
168: * activates the transaction log
169: *
170: * @param isTransactionLog true, if transaction log sholud be activated
171: */
172: void setTransactionLog(boolean isTransactionLog) {
173: this .isTransactionLog = isTransactionLog;
174: }
175:
176: /**
177: * returns if the transaction log is activated
178: *
179: * @return true, if the transaction log is activated
180: */
181: boolean isTransactionLog() {
182: return isTransactionLog;
183: }
184:
185: /**
186: * get the max size of the transaction log
187: *
188: * @return the max size of the transaction log
189: */
190: int getTransactionLogMaxSize() {
191: return transactionLog.getMaxSize();
192: }
193:
194: /**
195: * sets the max size of the transaction log
196: *
197: * @param maxSize the max size of the transaction log
198: */
199: void setTransactionLogMaxSize(int maxSize) {
200: transactionLog.setMaxSize(maxSize);
201: }
202:
203: /**
204: * set the worker pool which will be assigned to the connections for call back handling
205: * @param workerpool the worker pool
206: */
207: public void setWorkerpool(Executor workerpool) {
208: pool.setWorkerpool(workerpool);
209: }
210:
211: /**
212: * returns is pooling is used
213: *
214: * @return true, if pooling is used
215: */
216: public boolean isPooled() {
217: return isPooled;
218: }
219:
220: /**
221: * sets if pooling is used
222: *
223: * @param isPooled true, if pooling is used
224: */
225: public void setPooled(boolean isPooled) {
226: this .isPooled = isPooled;
227: }
228:
229: /**
230: * {@inheritDoc}
231: */
232: public void setResponseTimeoutMillis(int receiveTimeoutMillis) {
233: this .receiveTimeoutMillis = receiveTimeoutMillis;
234: }
235:
236: /**
237: * {@inheritDoc}
238: */
239: public int getResponseTimeoutMillis() {
240: return receiveTimeoutMillis;
241: }
242:
243: /**
244: * {@inheritDoc}
245: */
246: public void close() throws IOException {
247: pool.close();
248: }
249:
250: /**
251: * {@inheritDoc}
252: */
253: public boolean isOpen() {
254: return pool.isOpen();
255: }
256:
257: /**
258: * returns a unique id
259: *
260: * @return the id
261: */
262: public String getId() {
263: return Integer.toString(this .hashCode());
264: }
265:
266: /**
267: * {@inheritDoc}
268: */
269: public void addListener(ILifeCycle listener) {
270: pool.addListener(listener);
271: }
272:
273: /**
274: * {@inheritDoc}
275: */
276: public boolean removeListener(ILifeCycle listener) {
277: return pool.removeListener(listener);
278: }
279:
280: /**
281: * {@inheritDoc}
282: */
283: public void setPooledIdleTimeoutMillis(int idleTimeoutMillis) {
284: pool.setPooledIdleTimeoutMillis(idleTimeoutMillis);
285: }
286:
287: /**
288: * {@inheritDoc}
289: */
290: public int getPooledIdleTimeoutMillis() {
291: return pool.getPooledIdleTimeoutMillis();
292: }
293:
294: /**
295: * {@inheritDoc}
296: */
297: public void setPooledLifeTimeoutMillis(int lifeTimeoutMillis) {
298: pool.setPooledLifeTimeoutMillis(lifeTimeoutMillis);
299: }
300:
301: /**
302: * {@inheritDoc}
303: */
304: public int getPooledLifeTimeoutMillis() {
305: return pool.getPooledLifeTimeoutMillis();
306: }
307:
308: /**
309: * {@inheritDoc}
310: */
311: public long getCreationMaxWaitMillis() {
312: return pool.getCreationMaxWaitMillis();
313: }
314:
315: /**
316: * {@inheritDoc}
317: */
318: public void setCreationMaxWaitMillis(long maxWaitMillis) {
319: pool.setCreationMaxWaitMillis(maxWaitMillis);
320: }
321:
322: /**
323: * {@inheritDoc}
324: */
325: public void setMaxIdlePooled(int maxIdle) {
326: pool.setMaxIdlePooled(maxIdle);
327: }
328:
329: /**
330: * {@inheritDoc}
331: */
332: public int getMaxIdlePooled() {
333: return pool.getMaxIdlePooled();
334: }
335:
336: /**
337: * {@inheritDoc}
338: */
339: public void setMaxActivePooled(int maxActive) {
340: pool.setMaxActivePooled(maxActive);
341: }
342:
343: /**
344: * {@inheritDoc}
345: */
346: public int getMaxActivePooled() {
347: return pool.getMaxActivePooled();
348: }
349:
350: /**
351: * {@inheritDoc}
352: */
353: public int getNumPooledActive() {
354: return pool.getNumPooledActive();
355: }
356:
357: /**
358: * {@inheritDoc}
359: */
360: public int getNumPooledIdle() {
361: return pool.getNumPooledIdle();
362: }
363:
364: /**
365: * {@inheritDoc}
366: */
367: int getNumPendingGet() {
368: return pool.getNumPendingGet();
369: }
370:
371: /**
372: * {@inheritDoc}
373: */
374: public int getNumCreated() {
375: return pool.getNumCreated();
376: }
377:
378: /**
379: * {@inheritDoc}
380: */
381: public int getNumDestroyed() {
382: return pool.getNumDestroyed();
383: }
384:
385: /**
386: * {@inheritDoc}
387: */
388: public int getNumTimeoutPooledIdle() {
389: return pool.getNumTimeoutPooledIdle();
390: }
391:
392: /**
393: * {@inheritDoc}
394: */
395: public int getNumTimeoutPooledLifetime() {
396: return pool.getNumTimeoutPooledLifetime();
397: }
398:
399: /**
400: * {@inheritDoc}
401: */
402: public List<String> getActiveConnectionInfos() {
403: return pool.getActiveConnectionInfos();
404: }
405:
406: /**
407: * {@inheritDoc}
408: */
409: public List<String> getIdleConnectionInfos() {
410: return pool.getIdleConnectionInfos();
411: }
412:
413: /**
414: * returns the transaction log
415: * @return the transaction log
416: */
417: List<String> getTransactionInfos() {
418: return transactionLog.getTransactions();
419: }
420:
421: /**
422: * {@inheritDoc}
423: */
424: public HttpResponse call(HttpRequest request) throws IOException,
425: SocketTimeoutException {
426:
427: // get connection
428: URI targetURI = request.getTargetURI();
429: HttpClientConnection con = getConnection(request.isSecure(),
430: targetURI.getHost(), targetURI.getPort(), targetURI
431: .getScheme());
432:
433: // create a response handler
434: BlockingResponseHandler responseHandler = new BlockingResponseHandler(
435: con, getResponseTimeoutMillis());
436:
437: // send the request and wait for the response
438: con.send(request, responseHandler);
439: HttpResponse response = responseHandler.getResponse();
440:
441: addTransactionInfo(request, response);
442: return response;
443: }
444:
445: /**
446: * performs a request, by handling redirects
447: *
448: * @param request the request
449: * @return the response
450: * @throws IOException if an exception occurs
451: * @throws ConnectException if an error occurred while attempting to connect to a remote address and port.
452: * @throws SocketTimeoutException if the received timeout is exceed
453: */
454: public HttpResponse callFollowRedirects(HttpRequest request)
455: throws IOException, ConnectException,
456: SocketTimeoutException {
457: return callFollowRedirects(request, 0);
458: }
459:
460: private HttpResponse callFollowRedirects(HttpRequest request,
461: int redirectCounter) throws IOException, ConnectException,
462: SocketTimeoutException {
463:
464: if (redirectCounter > maxRedirects) {
465: throw new IOException("max redirects " + maxRedirects
466: + " reached");
467: }
468:
469: HttpResponse response = null;
470:
471: // duplicate body is redirect is activated
472: NonBlockingBodyDataSource copiedBody = null;
473: if (request.hasBody()) {
474: copiedBody = request.getNonBlockingBody().duplicate();
475: }
476:
477: // get connection and perform call
478: URI targetURI = request.getTargetURI();
479: HttpClientConnection con = getConnection(request.isSecure(),
480: targetURI.getHost(), targetURI.getPort(), targetURI
481: .getScheme());
482: response = con.call(request);
483:
484: addTransactionInfo(request, response);
485:
486: if (isRedirectResponse(request.getRequestHeader(), response
487: .getResponseHeader())) {
488: URL newLocation = getRedirectURI(response, request
489: .isSecure(), targetURI.getHost(), targetURI
490: .getPort());
491:
492: try {
493: HttpRequestHeader newRequestHeader = new HttpRequestHeader(
494: request.getMethod(), newLocation
495: .toExternalForm());
496: newRequestHeader.copyHeaderFrom(request
497: .getRequestHeader(), "HOST", "CONTENT-LENGTH");
498:
499: HttpRequest newRequest = null;
500: if ((response.getStatus() == 303)
501: || ((response.getStatus() == 302) && isTreat302RedirectAs303)) {
502: newRequest = new HttpRequest(newRequestHeader);
503: newRequest.setMethod("GET");
504:
505: } else {
506: newRequest = new HttpRequest(newRequestHeader,
507: copiedBody);
508: }
509:
510: if (LOG.isLoggable(Level.FINE)) {
511: LOG.fine("Sending redirect ");
512: }
513: response = callFollowRedirects(newRequest,
514: ++redirectCounter);
515: } catch (URISyntaxException use) {
516: throw new IOException(use.toString());
517: }
518: }
519:
520: return response;
521: }
522:
523: /**
524: * {@inheritDoc}
525: */
526: public void send(HttpRequest request,
527: IHttpResponseHandler responseHandler) throws IOException,
528: ConnectException {
529:
530: // get connection
531: URI targetURI = request.getTargetURI();
532: HttpClientConnection con = getConnection(request.isSecure(),
533: targetURI.getHost(), targetURI.getPort(), targetURI
534: .getScheme());
535:
536: // perform call
537: ResponseHandlerAdapter responseHandlerAdapter = new ResponseHandlerAdapter(
538: responseHandler, con, getResponseTimeoutMillis());
539: con.send(request, responseHandlerAdapter);
540: }
541:
542: /**
543: * {@inheritDoc}
544: */
545: public BodyDataSink send(HttpRequestHeader requestHeader,
546: int contentLength, IHttpResponseHandler responseHandler)
547: throws IOException, ConnectException {
548:
549: // get connection
550: URI targetURI = requestHeader.getTargetURI();
551: HttpClientConnection con = getConnection(requestHeader
552: .isSecure(), targetURI.getHost(), targetURI.getPort(),
553: targetURI.getScheme());
554:
555: // perform call
556: ResponseHandlerAdapter responseHandlerAdapter = new ResponseHandlerAdapter(
557: responseHandler, con, getResponseTimeoutMillis());
558: return con.sendPlain(requestHeader, contentLength,
559: responseHandlerAdapter);
560:
561: }
562:
563: /**
564: * {@inheritDoc}
565: */
566: public BodyDataSink send(HttpRequestHeader requestHeader,
567: IHttpResponseHandler responseHandler) throws IOException,
568: ConnectException {
569:
570: // get connection
571: URI targetURI = requestHeader.getTargetURI();
572: HttpClientConnection con = getConnection(requestHeader
573: .isSecure(), targetURI.getHost(), targetURI.getPort(),
574: targetURI.getScheme());
575:
576: // perform call
577: return con.sendChunked(requestHeader,
578: new ResponseHandlerAdapter(responseHandler, con,
579: getResponseTimeoutMillis()));
580: }
581:
582: /**
583: * sends a request, by following redirects
584: *
585: * @param request the request
586: * @param responseHandler the response handler
587: * @throws IOException if an exception occurs
588: * @throws ConnectException if an error occurred while attempting to connect to a remote address and port.
589: */
590: public void sendFollowRedirects(HttpRequest request,
591: IHttpResponseHandler responseHandler) throws IOException,
592: ConnectException {
593: sendFollowRedirects(request, responseHandler, 0);
594: }
595:
596: private void sendFollowRedirects(HttpRequest request,
597: IHttpResponseHandler responseHandler, int redirectCounter)
598: throws IOException, ConnectException {
599:
600: // duplicate body and create RedirectAdapter is redirect is activated
601: NonBlockingBodyDataSource copiedBody = null;
602: if (request.hasBody()) {
603: copiedBody = request.getNonBlockingBody().duplicate();
604: }
605:
606: // get connection
607: URI targetURI = request.getTargetURI();
608: HttpClientConnection con = getConnection(request.isSecure(),
609: targetURI.getHost(), targetURI.getPort(), targetURI
610: .getScheme());
611:
612: // perform call
613: RedirectResponseHandlerAdapter responseHandlerAdapter = new RedirectResponseHandlerAdapter(
614: responseHandler, con, request.getRequestHeader(),
615: copiedBody, redirectCounter, getResponseTimeoutMillis());
616: con.send(request, responseHandlerAdapter);
617: }
618:
619: private void addTransactionInfo(HttpRequest request,
620: HttpResponse response) {
621:
622: if (isTransactionLog) {
623: String info = null;
624: if (request.getQueryString() != null) {
625: info = "[" + DATE_FORMAT.format(new Date()) + "] "
626: + request.getRemoteHost() + ":"
627: + request.getRemotePort() + " "
628: + request.getMethod() + " "
629: + request.getRequestURI()
630: + request.getQueryString() + " -> "
631: + response.getStatus() + " "
632: + response.getReason();
633: } else {
634: info = "[" + DATE_FORMAT.format(new Date()) + "] "
635: + request.getRemoteHost() + ":"
636: + request.getRemotePort() + " "
637: + request.getMethod() + " "
638: + request.getRequestURI() + " -> "
639: + response.getStatus() + " "
640: + response.getReason();
641: }
642:
643: if (response.containsHeader("connection")) {
644: info = info + " (connection: "
645: + response.getHeader("connection") + ")";
646: }
647:
648: transactionLog.add(info);
649: }
650: }
651:
652: private HttpClientConnection getConnection(boolean isSSL,
653: String host, int port, String scheme) throws IOException,
654: ConnectException {
655:
656: if (port == -1) {
657: if (scheme.equalsIgnoreCase("HTTP")) {
658: port = 80;
659: } else if (scheme.equalsIgnoreCase("HTTPS")) {
660: port = 443;
661: } else {
662: throw new IOException("wrong address host=" + host
663: + " port=" + port + " scheme=" + scheme);
664: }
665: }
666:
667: if ((isSSL == true) && !isSSLSupported) {
668: throw new IOException(
669: "ssl connection are not supported (use pool sslContext parameter constructor)");
670: }
671:
672: INonBlockingConnection tcpConnection = null;
673:
674: if (isPooled) {
675: try {
676: tcpConnection = pool.getNonBlockingConnection(host,
677: port, isSSL);
678:
679: if (!tcpConnection.isOpen()) {
680: System.out.println("Stop");
681: }
682: } catch (IOException ioe) {
683: throw new ConnectException("could not connect to "
684: + host + ":" + port + " (" + ioe.toString()
685: + ")");
686: }
687:
688: } else {
689: try {
690: if (sslCtx != null) {
691: tcpConnection = new NonBlockingConnection(host,
692: port, sslCtx, true);
693: ((NonBlockingConnection) tcpConnection)
694: .setWorkerpool(pool.getWorkerpool());
695:
696: } else {
697: tcpConnection = new NonBlockingConnection(host,
698: port);
699: ((NonBlockingConnection) tcpConnection)
700: .setWorkerpool(pool.getWorkerpool());
701: }
702: } catch (IOException ioe) {
703: throw new ConnectException(
704: "could not establish new tcp connection to "
705: + host + ":" + port + " reason: "
706: + ioe.toString());
707: }
708: }
709:
710: HttpClientConnection httpConnection = new HttpClientConnection(
711: tcpConnection);
712: httpConnection.setResponseTimeoutMillis(receiveTimeoutMillis);
713: httpConnection
714: .setAutohandle100ContinueResponse(isAutohandle100ContinueResponse);
715: httpConnection.setCloseAfterResponse(true);
716:
717: return httpConnection;
718: }
719:
720: private boolean isRedirectResponse(HttpRequestHeader requestHeader,
721: HttpResponseHeader responseHeader) {
722:
723: switch (responseHeader.getStatus()) {
724:
725: // 300 Multiple choices
726: case 300:
727: return false;
728:
729: // 301 Moved permanently
730: case 301:
731: if (requestHeader.getMethod().equalsIgnoreCase("GET")
732: || requestHeader.getMethod().equalsIgnoreCase(
733: "HEAD")) {
734: return true;
735: }
736:
737: return false;
738:
739: // 302 found
740: case 302:
741: if (isTreat302RedirectAs303) {
742: return true;
743: }
744:
745: if (requestHeader.getMethod().equalsIgnoreCase("GET")
746: || requestHeader.getMethod().equalsIgnoreCase(
747: "HEAD")) {
748: return true;
749: }
750:
751: return false;
752:
753: // 303 See other
754: case 303:
755: return true;
756:
757: // 304 Not modified
758: case 304:
759: return false;
760:
761: // 305 Use proxy
762: case 305:
763: return false;
764:
765: // 306 (unused)
766: case 306:
767: return false;
768:
769: // 307 temporary redirect
770: case 307:
771: return false;
772:
773: default:
774: return false;
775: }
776: }
777:
778: private static final URL getRedirectURI(HttpResponse response,
779: boolean isSSL, String originalHost, int originalPort) {
780: if (response.getStatus() == 302) {
781: String location = response.getHeader("Location");
782:
783: // absolute URL?
784: try {
785: return new URL(location);
786:
787: } catch (MalformedURLException mue) {
788: // no
789: try {
790: if (isSSL) {
791: if (originalPort == -1) {
792: return new URL("https://" + originalHost
793: + location);
794: } else {
795: return new URL("https://" + originalHost
796: + ":" + originalPort + location);
797: }
798: } else {
799: if (originalPort == -1) {
800: return new URL("http://" + originalHost
801: + location);
802: } else {
803: return new URL("http://" + originalHost
804: + ":" + originalPort + location);
805: }
806: }
807: } catch (MalformedURLException e) {
808: if (LOG.isLoggable(Level.FINE)) {
809: LOG
810: .fine("could not create relocation url . reason "
811: + e.toString());
812: }
813: }
814: }
815: }
816:
817: return null;
818: }
819:
820: /**
821: * {@inheritDoc}
822: */
823: @Override
824: public String toString() {
825: StringBuilder sb = new StringBuilder(super .toString());
826:
827: sb.append("\r\nactive connections:");
828: for (String connectionInfo : getActiveConnectionInfos()) {
829: sb.append("\r\n " + connectionInfo);
830: }
831:
832: sb.append("\r\nidle connections:");
833: for (String connectionInfo : getIdleConnectionInfos()) {
834: sb.append("\r\n " + connectionInfo);
835: }
836:
837: sb.append("\r\ntransaction log:");
838: for (String transactionInfo : getTransactionInfos()) {
839: sb.append("\r\n " + transactionInfo);
840: }
841:
842: return sb.toString();
843: }
844:
845: class RedirectResponseHandlerAdapter extends ResponseHandlerAdapter {
846:
847: private IHttpResponseHandler responseHandler = null;
848:
849: // redirect support
850: private int currentRedirects = 0;
851: private HttpRequestHeader originalRequestHeader = null;
852: private NonBlockingBodyDataSource originalRequestBody = null;
853:
854: public RedirectResponseHandlerAdapter(
855: IHttpResponseHandler responseHandler,
856: HttpClientConnection httpConnection,
857: HttpRequestHeader originalRequestHeader,
858: NonBlockingBodyDataSource originalRequestBody,
859: int currentRedirects, int receiveTimeoutMillis)
860: throws IOException {
861: super (responseHandler, httpConnection, receiveTimeoutMillis);
862: this .responseHandler = responseHandler;
863: this .originalRequestHeader = originalRequestHeader;
864: this .originalRequestBody = originalRequestBody;
865: this .currentRedirects = currentRedirects;
866: }
867:
868: @Override
869: public void performOnResponse(HttpResponse response) {
870:
871: try {
872: // redirect handling
873: if (isRedirectResponse(originalRequestHeader, response
874: .getResponseHeader())) {
875:
876: try {
877: URL newLocation = getRedirectURI(response,
878: originalRequestHeader.isSecure(),
879: originalRequestHeader.getRemoteHost(),
880: originalRequestHeader.getRemotePort());
881:
882: HttpRequestHeader newRequestHeader = new HttpRequestHeader(
883: originalRequestHeader.getMethod(),
884: newLocation.toExternalForm());
885: newRequestHeader.copyHeaderFrom(
886: originalRequestHeader, "HOST",
887: "CONTENT-LENGTH");
888:
889: HttpRequest newRequest = null;
890: if ((response.getStatus() == 303)
891: || ((response.getStatus() == 302) && isTreat302RedirectAs303)) {
892: newRequest = new HttpRequest(
893: newRequestHeader);
894: newRequest.setMethod("GET");
895:
896: } else {
897: newRequest = new HttpRequest(
898: newRequestHeader,
899: originalRequestBody);
900: }
901:
902: sendFollowRedirects(newRequest,
903: responseHandler, ++currentRedirects);
904: } catch (URISyntaxException use) {
905: throw new IOException(use.toString());
906: }
907:
908: } else {
909: super .performOnResponse(response);
910: }
911:
912: } catch (IOException ioe) {
913: if (LOG.isLoggable(Level.FINE)) {
914: LOG.fine("error occured by calling on response "
915: + ioe.toString());
916: }
917: }
918: }
919: }
920:
921: @SuppressWarnings("unchecked")
922: private static final class TransactionLog {
923:
924: private LinkedList<String> transactions = new LinkedList<String>();
925: private int maxSize = 0;
926:
927: TransactionLog(int maxSize) {
928: this .maxSize = maxSize;
929: }
930:
931: void setMaxSize(int maxSize) {
932: this .maxSize = maxSize;
933: }
934:
935: int getMaxSize() {
936: return maxSize;
937: }
938:
939: void add(String transactionInfo) {
940: transactions.add(transactionInfo);
941: if (transactions.size() > maxSize) {
942: try {
943: transactions.removeFirst();
944: } catch (Exception e) {
945: if (LOG.isLoggable(Level.FINE)) {
946: LOG
947: .fine("error occured by removing list entry "
948: + e.toString());
949: }
950: }
951: }
952: }
953:
954: public List<String> getTransactions() {
955: return (List<String>) transactions.clone();
956: }
957:
958: }
959: }
|