1:
37:
38:
39: package ;
40:
41: import ;
42: import ;
43:
44: import ;
45: import ;
46: import ;
47: import ;
48: import ;
49: import ;
50: import ;
51: import ;
52: import ;
53: import ;
54: import ;
55: import ;
56: import ;
57: import ;
58: import ;
59: import ;
60:
61: import ;
62: import ;
63: import ;
64: import ;
65: import ;
66:
67:
72: public class HTTPConnection
73: {
74:
75:
78: public static final int HTTP_PORT = 80;
79:
80:
83: public static final int HTTPS_PORT = 443;
84:
85: private static final String userAgent = SystemProperties.getProperty("http.agent");
86:
87:
90: protected final String hostname;
91:
92:
95: protected final int port;
96:
97:
100: protected final boolean secure;
101:
102:
105: protected final int connectionTimeout;
106:
107:
110: protected final int timeout;
111:
112:
115: protected String proxyHostname;
116:
117:
120: protected int proxyPort;
121:
122:
125: protected int majorVersion;
126:
127:
130: protected int minorVersion;
131:
132: private final List<HandshakeCompletedListener> handshakeCompletedListeners;
133:
134:
137: protected Socket socket;
138:
139:
142: private SSLSocketFactory sslSocketFactory;
143:
144:
147: protected InputStream in;
148:
149:
152: protected OutputStream out;
153:
154:
157: private Map<String, Integer> nonceCounts;
158:
159:
162: protected CookieManager cookieManager;
163:
164:
165:
168: private Pool pool;
169:
170:
174: public HTTPConnection(String hostname)
175: {
176: this(hostname, HTTP_PORT, false, 0, 0);
177: }
178:
179:
184: public HTTPConnection(String hostname, boolean secure)
185: {
186: this(hostname, secure ? HTTPS_PORT : HTTP_PORT, secure, 0, 0);
187: }
188:
189:
196: public HTTPConnection(String hostname, boolean secure,
197: int connectionTimeout, int timeout)
198: {
199: this(hostname, secure ? HTTPS_PORT : HTTP_PORT, secure,
200: connectionTimeout, timeout);
201: }
202:
203:
208: public HTTPConnection(String hostname, int port)
209: {
210: this(hostname, port, false, 0, 0);
211: }
212:
213:
219: public HTTPConnection(String hostname, int port, boolean secure)
220: {
221: this(hostname, port, secure, 0, 0);
222: }
223:
224:
235: public HTTPConnection(String hostname, int port, boolean secure,
236: int connectionTimeout, int timeout)
237: {
238: if (connectionTimeout < 0 || timeout < 0)
239: throw new IllegalArgumentException();
240:
241: this.hostname = hostname;
242: this.port = port;
243: this.secure = secure;
244: this.connectionTimeout = connectionTimeout;
245: this.timeout = timeout;
246: majorVersion = minorVersion = 1;
247: handshakeCompletedListeners
248: = new ArrayList<HandshakeCompletedListener>(2);
249: }
250:
251:
254: public String getHostName()
255: {
256: return hostname;
257: }
258:
259:
262: public int getPort()
263: {
264: return port;
265: }
266:
267:
270: public boolean isSecure()
271: {
272: return secure;
273: }
274:
275:
280: public String getVersion()
281: {
282: return "HTTP/" + majorVersion + '.' + minorVersion;
283: }
284:
285:
290: public void setVersion(int majorVersion, int minorVersion)
291: {
292: if (majorVersion != 1)
293: {
294: throw new IllegalArgumentException("major version not supported: " +
295: majorVersion);
296: }
297: if (minorVersion < 0 || minorVersion > 1)
298: {
299: throw new IllegalArgumentException("minor version not supported: " +
300: minorVersion);
301: }
302: this.majorVersion = majorVersion;
303: this.minorVersion = minorVersion;
304: }
305:
306:
311: public void setProxy(String hostname, int port)
312: {
313: proxyHostname = hostname;
314: proxyPort = port;
315: }
316:
317:
320: public boolean isUsingProxy()
321: {
322: return (proxyHostname != null && proxyPort > 0);
323: }
324:
325:
329: public void setCookieManager(CookieManager cookieManager)
330: {
331: this.cookieManager = cookieManager;
332: }
333:
334:
337: public CookieManager getCookieManager()
338: {
339: return cookieManager;
340: }
341:
342:
351: static class Pool
352: {
353:
356: static Pool instance = new Pool();
357:
358:
361: final LinkedList<HTTPConnection> connectionPool
362: = new LinkedList<HTTPConnection>();
363:
364:
367: int maxConnections;
368:
369:
373: int connectionTTL;
374:
375:
378: class Reaper
379: implements Runnable
380: {
381: public void run()
382: {
383: synchronized (Pool.this)
384: {
385: try
386: {
387: do
388: {
389: while (connectionPool.size() > 0)
390: {
391: long currentTime = System.currentTimeMillis();
392:
393: HTTPConnection c =
394: (HTTPConnection)connectionPool.getFirst();
395:
396: long waitTime = c.timeLastUsed
397: + connectionTTL - currentTime;
398:
399: if (waitTime <= 0)
400: removeOldest();
401: else
402: try
403: {
404: Pool.this.wait(waitTime);
405: }
406: catch (InterruptedException _)
407: {
408:
409: }
410: }
411:
412:
413:
414:
415:
416:
417:
418:
419:
420:
421: try
422: {
423: Pool.this.wait(connectionTTL);
424: }
425: catch (InterruptedException _)
426: {
427:
428: }
429: }
430: while (connectionPool.size() > 0);
431: }
432: finally
433: {
434: reaper = null;
435: }
436: }
437: }
438: }
439:
440: Reaper reaper;
441:
442:
445: private Pool()
446: {
447: }
448:
449:
459: private static boolean matches(HTTPConnection c,
460: String h, int p, boolean sec)
461: {
462: return h.equals(c.hostname) && (p == c.port) && (sec == c.secure);
463: }
464:
465:
476: synchronized HTTPConnection get(String host,
477: int port,
478: boolean secure,
479: int connectionTimeout, int timeout)
480: {
481: String ttl =
482: SystemProperties.getProperty("classpath.net.http.keepAliveTTL");
483: connectionTTL = 10000;
484: if (ttl != null && ttl.length() > 0)
485: try
486: {
487: int v = 1000 * Integer.parseInt(ttl);
488: if (v >= 0)
489: connectionTTL = v;
490: }
491: catch (NumberFormatException _)
492: {
493:
494: }
495:
496: String mc = SystemProperties.getProperty("http.maxConnections");
497: maxConnections = 5;
498: if (mc != null && mc.length() > 0)
499: try
500: {
501: int v = Integer.parseInt(mc);
502: if (v > 0)
503: maxConnections = v;
504: }
505: catch (NumberFormatException _)
506: {
507:
508: }
509:
510: HTTPConnection c = null;
511:
512: ListIterator it = connectionPool.listIterator(0);
513: while (it.hasNext())
514: {
515: HTTPConnection cc = (HTTPConnection)it.next();
516: if (matches(cc, host, port, secure))
517: {
518: c = cc;
519: it.remove();
520:
521: if (c.socket != null)
522: try
523: {
524: c.socket.setSoTimeout(timeout);
525: }
526: catch (SocketException _)
527: {
528:
529: }
530: break;
531: }
532: }
533: if (c == null)
534: {
535: c = new HTTPConnection(host, port, secure,
536: connectionTimeout, timeout);
537: c.setPool(this);
538: }
539: return c;
540: }
541:
542:
548: synchronized void put(HTTPConnection c)
549: {
550: c.timeLastUsed = System.currentTimeMillis();
551: connectionPool.addLast(c);
552:
553:
554: while (connectionPool.size() >= maxConnections)
555: removeOldest();
556:
557: if (connectionTTL > 0 && null == reaper) {
558:
559:
560:
561:
562: reaper = new Reaper();
563: Thread t = new Thread(reaper, "HTTPConnection.Reaper");
564: t.setDaemon(true);
565: t.start();
566: }
567: }
568:
569:
572: void removeOldest()
573: {
574: HTTPConnection cx = (HTTPConnection)connectionPool.removeFirst();
575: try
576: {
577: cx.closeConnection();
578: }
579: catch (IOException ioe)
580: {
581:
582: }
583: }
584: }
585:
586:
589: int useCount;
590:
591:
594: long timeLastUsed;
595:
596:
603: void setPool(Pool p)
604: {
605: pool = p;
606: }
607:
608:
613: void release()
614: {
615: if (pool != null)
616: {
617: useCount++;
618: pool.put(this);
619:
620: }
621: else
622: {
623:
624: try
625: {
626: closeConnection();
627: }
628: catch (IOException ioe)
629: {
630:
631: }
632: }
633: }
634:
635:
641: public Request newRequest(String method, String path)
642: {
643: if (method == null || method.length() == 0)
644: {
645: throw new IllegalArgumentException("method must have non-zero length");
646: }
647: if (path == null || path.length() == 0)
648: {
649: path = "/";
650: }
651: Request ret = new Request(this, method, path);
652: if ((secure && port != HTTPS_PORT) ||
653: (!secure && port != HTTP_PORT))
654: {
655: ret.setHeader("Host", hostname + ":" + port);
656: }
657: else
658: {
659: ret.setHeader("Host", hostname);
660: }
661: ret.setHeader("User-Agent", userAgent);
662: ret.setHeader("Connection", "keep-alive");
663: ret.setHeader("Accept-Encoding",
664: "chunked;q=1.0, gzip;q=0.9, deflate;q=0.8, " +
665: "identity;q=0.6, *;q=0");
666: if (cookieManager != null)
667: {
668: Cookie[] cookies = cookieManager.getCookies(hostname, secure, path);
669: if (cookies != null && cookies.length > 0)
670: {
671: StringBuilder buf = new StringBuilder();
672: buf.append("$Version=1");
673: for (int i = 0; i < cookies.length; i++)
674: {
675: buf.append(',');
676: buf.append(' ');
677: buf.append(cookies[i].toString());
678: }
679: ret.setHeader("Cookie", buf.toString());
680: }
681: }
682: return ret;
683: }
684:
685:
688: public void close()
689: throws IOException
690: {
691: closeConnection();
692: }
693:
694:
698: protected synchronized Socket getSocket()
699: throws IOException
700: {
701: if (socket == null)
702: {
703: String connectHostname = hostname;
704: int connectPort = port;
705: if (isUsingProxy())
706: {
707: connectHostname = proxyHostname;
708: connectPort = proxyPort;
709: }
710: socket = new Socket();
711: InetSocketAddress address =
712: new InetSocketAddress(connectHostname, connectPort);
713: if (connectionTimeout > 0)
714: {
715: socket.connect(address, connectionTimeout);
716: }
717: else
718: {
719: socket.connect(address);
720: }
721: if (timeout > 0)
722: {
723: socket.setSoTimeout(timeout);
724: }
725: if (secure)
726: {
727: try
728: {
729: SSLSocketFactory factory = getSSLSocketFactory();
730: SSLSocket ss =
731: (SSLSocket) factory.createSocket(socket, connectHostname,
732: connectPort, true);
733: String[] protocols = { "TLSv1", "SSLv3" };
734: ss.setEnabledProtocols(protocols);
735: ss.setUseClientMode(true);
736: synchronized (handshakeCompletedListeners)
737: {
738: if (!handshakeCompletedListeners.isEmpty())
739: {
740: for (Iterator i =
741: handshakeCompletedListeners.iterator();
742: i.hasNext(); )
743: {
744: HandshakeCompletedListener l =
745: (HandshakeCompletedListener) i.next();
746: ss.addHandshakeCompletedListener(l);
747: }
748: }
749: }
750: ss.startHandshake();
751: socket = ss;
752: }
753: catch (GeneralSecurityException e)
754: {
755: throw new IOException(e.getMessage());
756: }
757: }
758: in = socket.getInputStream();
759: in = new BufferedInputStream(in);
760: out = socket.getOutputStream();
761: out = new BufferedOutputStream(out);
762: }
763: return socket;
764: }
765:
766: SSLSocketFactory getSSLSocketFactory()
767: throws GeneralSecurityException
768: {
769: if (sslSocketFactory == null)
770: {
771: TrustManager tm = new EmptyX509TrustManager();
772: SSLContext context = SSLContext.getInstance("SSL");
773: TrustManager[] trust = new TrustManager[] { tm };
774: context.init(null, trust, null);
775: sslSocketFactory = context.getSocketFactory();
776: }
777: return sslSocketFactory;
778: }
779:
780: void setSSLSocketFactory(SSLSocketFactory factory)
781: {
782: sslSocketFactory = factory;
783: }
784:
785: protected synchronized InputStream getInputStream()
786: throws IOException
787: {
788: if (socket == null)
789: {
790: getSocket();
791: }
792: return in;
793: }
794:
795: protected synchronized OutputStream getOutputStream()
796: throws IOException
797: {
798: if (socket == null)
799: {
800: getSocket();
801: }
802: return out;
803: }
804:
805:
808: protected synchronized void closeConnection()
809: throws IOException
810: {
811: if (socket != null)
812: {
813: try
814: {
815: socket.close();
816: }
817: finally
818: {
819: socket = null;
820: }
821: }
822: }
823:
824:
828: protected String getURI()
829: {
830: StringBuilder buf = new StringBuilder();
831: buf.append(secure ? "https://" : "http://");
832: buf.append(hostname);
833: if (secure)
834: {
835: if (port != HTTPConnection.HTTPS_PORT)
836: {
837: buf.append(':');
838: buf.append(port);
839: }
840: }
841: else
842: {
843: if (port != HTTPConnection.HTTP_PORT)
844: {
845: buf.append(':');
846: buf.append(port);
847: }
848: }
849: return buf.toString();
850: }
851:
852:
856: int getNonceCount(String nonce)
857: {
858: if (nonceCounts == null)
859: {
860: return 0;
861: }
862: return nonceCounts.get(nonce).intValue();
863: }
864:
865:
868: void incrementNonce(String nonce)
869: {
870: int current = getNonceCount(nonce);
871: if (nonceCounts == null)
872: {
873: nonceCounts = new HashMap<String, Integer>();
874: }
875: nonceCounts.put(nonce, new Integer(current + 1));
876: }
877:
878:
879:
880: void addHandshakeCompletedListener(HandshakeCompletedListener l)
881: {
882: synchronized (handshakeCompletedListeners)
883: {
884: handshakeCompletedListeners.add(l);
885: }
886: }
887: void removeHandshakeCompletedListener(HandshakeCompletedListener l)
888: {
889: synchronized (handshakeCompletedListeners)
890: {
891: handshakeCompletedListeners.remove(l);
892: }
893: }
894:
895: }