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:
60: import ;
61: import ;
62: import ;
63: import ;
64: import ;
65:
66:
71: public class HTTPConnection
72: {
73:
74:
77: public static final int HTTP_PORT = 80;
78:
79:
82: public static final int HTTPS_PORT = 443;
83:
84: private static final String userAgent = SystemProperties.getProperty("http.agent");
85:
86:
89: protected final String hostname;
90:
91:
94: protected final int port;
95:
96:
99: protected final boolean secure;
100:
101:
104: protected final int connectionTimeout;
105:
106:
109: protected final int timeout;
110:
111:
114: protected String proxyHostname;
115:
116:
119: protected int proxyPort;
120:
121:
124: protected int majorVersion;
125:
126:
129: protected int minorVersion;
130:
131: private final List handshakeCompletedListeners;
132:
133:
136: protected Socket socket;
137:
138:
141: private SSLSocketFactory sslSocketFactory;
142:
143:
146: protected InputStream in;
147:
148:
151: protected OutputStream out;
152:
153:
156: private Map nonceCounts;
157:
158:
161: protected CookieManager cookieManager;
162:
163:
164:
167: private Pool pool;
168:
169:
173: public HTTPConnection(String hostname)
174: {
175: this(hostname, HTTP_PORT, false, 0, 0);
176: }
177:
178:
183: public HTTPConnection(String hostname, boolean secure)
184: {
185: this(hostname, secure ? HTTPS_PORT : HTTP_PORT, secure, 0, 0);
186: }
187:
188:
195: public HTTPConnection(String hostname, boolean secure,
196: int connectionTimeout, int timeout)
197: {
198: this(hostname, secure ? HTTPS_PORT : HTTP_PORT, secure,
199: connectionTimeout, timeout);
200: }
201:
202:
207: public HTTPConnection(String hostname, int port)
208: {
209: this(hostname, port, false, 0, 0);
210: }
211:
212:
218: public HTTPConnection(String hostname, int port, boolean secure)
219: {
220: this(hostname, port, secure, 0, 0);
221: }
222:
223:
231: public HTTPConnection(String hostname, int port, boolean secure,
232: int connectionTimeout, int timeout)
233: {
234: this.hostname = hostname;
235: this.port = port;
236: this.secure = secure;
237: this.connectionTimeout = connectionTimeout;
238: this.timeout = timeout;
239: majorVersion = minorVersion = 1;
240: handshakeCompletedListeners = new ArrayList(2);
241: }
242:
243:
246: public String getHostName()
247: {
248: return hostname;
249: }
250:
251:
254: public int getPort()
255: {
256: return port;
257: }
258:
259:
262: public boolean isSecure()
263: {
264: return secure;
265: }
266:
267:
272: public String getVersion()
273: {
274: return "HTTP/" + majorVersion + '.' + minorVersion;
275: }
276:
277:
282: public void setVersion(int majorVersion, int minorVersion)
283: {
284: if (majorVersion != 1)
285: {
286: throw new IllegalArgumentException("major version not supported: " +
287: majorVersion);
288: }
289: if (minorVersion < 0 || minorVersion > 1)
290: {
291: throw new IllegalArgumentException("minor version not supported: " +
292: minorVersion);
293: }
294: this.majorVersion = majorVersion;
295: this.minorVersion = minorVersion;
296: }
297:
298:
303: public void setProxy(String hostname, int port)
304: {
305: proxyHostname = hostname;
306: proxyPort = port;
307: }
308:
309:
312: public boolean isUsingProxy()
313: {
314: return (proxyHostname != null && proxyPort > 0);
315: }
316:
317:
321: public void setCookieManager(CookieManager cookieManager)
322: {
323: this.cookieManager = cookieManager;
324: }
325:
326:
329: public CookieManager getCookieManager()
330: {
331: return cookieManager;
332: }
333:
334:
343: static class Pool
344: {
345:
348: static Pool instance = new Pool();
349:
350:
353: final LinkedList connectionPool = new LinkedList();
354:
355:
358: int maxConnections;
359:
360:
364: int connectionTTL;
365:
366:
369: class Reaper
370: implements Runnable
371: {
372: public void run()
373: {
374: synchronized (Pool.this)
375: {
376: try
377: {
378: do
379: {
380: while (connectionPool.size() > 0)
381: {
382: long currentTime = System.currentTimeMillis();
383:
384: HTTPConnection c =
385: (HTTPConnection)connectionPool.getFirst();
386:
387: long waitTime = c.timeLastUsed
388: + connectionTTL - currentTime;
389:
390: if (waitTime <= 0)
391: removeOldest();
392: else
393: try
394: {
395: Pool.this.wait(waitTime);
396: }
397: catch (InterruptedException _)
398: {
399:
400: }
401: }
402:
403:
404:
405:
406:
407:
408:
409:
410:
411:
412: try
413: {
414: Pool.this.wait(connectionTTL);
415: }
416: catch (InterruptedException _)
417: {
418:
419: }
420: }
421: while (connectionPool.size() > 0);
422: }
423: finally
424: {
425: reaper = null;
426: }
427: }
428: }
429: }
430:
431: Reaper reaper;
432:
433:
436: private Pool()
437: {
438: }
439:
440:
450: private static boolean matches(HTTPConnection c,
451: String h, int p, boolean sec)
452: {
453: return h.equals(c.hostname) && (p == c.port) && (sec == c.secure);
454: }
455:
456:
467: synchronized HTTPConnection get(String host,
468: int port,
469: boolean secure,
470: int connectionTimeout, int timeout)
471: {
472: String ttl =
473: SystemProperties.getProperty("classpath.net.http.keepAliveTTL");
474: connectionTTL = (ttl != null && ttl.length() > 0) ?
475: 1000 * Math.max(1, Integer.parseInt(ttl)) : 10000;
476:
477: String mc = SystemProperties.getProperty("http.maxConnections");
478: maxConnections = (mc != null && mc.length() > 0) ?
479: Math.max(Integer.parseInt(mc), 1) : 5;
480: if (maxConnections < 1)
481: maxConnections = 1;
482:
483: HTTPConnection c = null;
484:
485: ListIterator it = connectionPool.listIterator(0);
486: while (it.hasNext())
487: {
488: HTTPConnection cc = (HTTPConnection)it.next();
489: if (matches(cc, host, port, secure))
490: {
491: c = cc;
492: it.remove();
493: break;
494: }
495: }
496: if (c == null)
497: {
498: c = new HTTPConnection(host, port, secure, connectionTimeout, timeout);
499: c.setPool(this);
500: }
501: return c;
502: }
503:
504:
510: synchronized void put(HTTPConnection c)
511: {
512: c.timeLastUsed = System.currentTimeMillis();
513: connectionPool.addLast(c);
514:
515:
516: while (connectionPool.size() >= maxConnections)
517: removeOldest();
518:
519: if (connectionTTL > 0 && null == reaper) {
520:
521:
522:
523:
524: reaper = new Reaper();
525: Thread t = new Thread(reaper, "HTTPConnection.Reaper");
526: t.setDaemon(true);
527: t.start();
528: }
529: }
530:
531:
534: void removeOldest()
535: {
536: HTTPConnection cx = (HTTPConnection)connectionPool.removeFirst();
537: try
538: {
539: cx.closeConnection();
540: }
541: catch (IOException ioe)
542: {
543:
544: }
545: }
546: }
547:
548:
551: int useCount;
552:
553:
556: long timeLastUsed;
557:
558:
565: void setPool(Pool p)
566: {
567: pool = p;
568: }
569:
570:
575: void release()
576: {
577: if (pool != null)
578: {
579: useCount++;
580: pool.put(this);
581:
582: }
583: else
584: {
585:
586: try
587: {
588: closeConnection();
589: }
590: catch (IOException ioe)
591: {
592:
593: }
594: }
595: }
596:
597:
603: public Request newRequest(String method, String path)
604: {
605: if (method == null || method.length() == 0)
606: {
607: throw new IllegalArgumentException("method must have non-zero length");
608: }
609: if (path == null || path.length() == 0)
610: {
611: path = "/";
612: }
613: Request ret = new Request(this, method, path);
614: if ((secure && port != HTTPS_PORT) ||
615: (!secure && port != HTTP_PORT))
616: {
617: ret.setHeader("Host", hostname + ":" + port);
618: }
619: else
620: {
621: ret.setHeader("Host", hostname);
622: }
623: ret.setHeader("User-Agent", userAgent);
624: ret.setHeader("Connection", "keep-alive");
625: ret.setHeader("Accept-Encoding",
626: "chunked;q=1.0, gzip;q=0.9, deflate;q=0.8, " +
627: "identity;q=0.6, *;q=0");
628: if (cookieManager != null)
629: {
630: Cookie[] cookies = cookieManager.getCookies(hostname, secure, path);
631: if (cookies != null && cookies.length > 0)
632: {
633: StringBuilder buf = new StringBuilder();
634: buf.append("$Version=1");
635: for (int i = 0; i < cookies.length; i++)
636: {
637: buf.append(',');
638: buf.append(' ');
639: buf.append(cookies[i].toString());
640: }
641: ret.setHeader("Cookie", buf.toString());
642: }
643: }
644: return ret;
645: }
646:
647:
650: public void close()
651: throws IOException
652: {
653: closeConnection();
654: }
655:
656:
660: protected synchronized Socket getSocket()
661: throws IOException
662: {
663: if (socket == null)
664: {
665: String connectHostname = hostname;
666: int connectPort = port;
667: if (isUsingProxy())
668: {
669: connectHostname = proxyHostname;
670: connectPort = proxyPort;
671: }
672: socket = new Socket();
673: InetSocketAddress address =
674: new InetSocketAddress(connectHostname, connectPort);
675: if (connectionTimeout > 0)
676: {
677: socket.connect(address, connectionTimeout);
678: }
679: else
680: {
681: socket.connect(address);
682: }
683: if (timeout > 0)
684: {
685: socket.setSoTimeout(timeout);
686: }
687: if (secure)
688: {
689: try
690: {
691: SSLSocketFactory factory = getSSLSocketFactory();
692: SSLSocket ss =
693: (SSLSocket) factory.createSocket(socket, connectHostname,
694: connectPort, true);
695: String[] protocols = { "TLSv1", "SSLv3" };
696: ss.setEnabledProtocols(protocols);
697: ss.setUseClientMode(true);
698: synchronized (handshakeCompletedListeners)
699: {
700: if (!handshakeCompletedListeners.isEmpty())
701: {
702: for (Iterator i =
703: handshakeCompletedListeners.iterator();
704: i.hasNext(); )
705: {
706: HandshakeCompletedListener l =
707: (HandshakeCompletedListener) i.next();
708: ss.addHandshakeCompletedListener(l);
709: }
710: }
711: }
712: ss.startHandshake();
713: socket = ss;
714: }
715: catch (GeneralSecurityException e)
716: {
717: throw new IOException(e.getMessage());
718: }
719: }
720: in = socket.getInputStream();
721: in = new BufferedInputStream(in);
722: out = socket.getOutputStream();
723: out = new BufferedOutputStream(out);
724: }
725: return socket;
726: }
727:
728: SSLSocketFactory getSSLSocketFactory()
729: throws GeneralSecurityException
730: {
731: if (sslSocketFactory == null)
732: {
733: TrustManager tm = new EmptyX509TrustManager();
734: SSLContext context = SSLContext.getInstance("SSL");
735: TrustManager[] trust = new TrustManager[] { tm };
736: context.init(null, trust, null);
737: sslSocketFactory = context.getSocketFactory();
738: }
739: return sslSocketFactory;
740: }
741:
742: void setSSLSocketFactory(SSLSocketFactory factory)
743: {
744: sslSocketFactory = factory;
745: }
746:
747: protected synchronized InputStream getInputStream()
748: throws IOException
749: {
750: if (socket == null)
751: {
752: getSocket();
753: }
754: return in;
755: }
756:
757: protected synchronized OutputStream getOutputStream()
758: throws IOException
759: {
760: if (socket == null)
761: {
762: getSocket();
763: }
764: return out;
765: }
766:
767:
770: protected synchronized void closeConnection()
771: throws IOException
772: {
773: if (socket != null)
774: {
775: try
776: {
777: socket.close();
778: }
779: finally
780: {
781: socket = null;
782: }
783: }
784: }
785:
786:
790: protected String getURI()
791: {
792: StringBuilder buf = new StringBuilder();
793: buf.append(secure ? "https://" : "http://");
794: buf.append(hostname);
795: if (secure)
796: {
797: if (port != HTTPConnection.HTTPS_PORT)
798: {
799: buf.append(':');
800: buf.append(port);
801: }
802: }
803: else
804: {
805: if (port != HTTPConnection.HTTP_PORT)
806: {
807: buf.append(':');
808: buf.append(port);
809: }
810: }
811: return buf.toString();
812: }
813:
814:
818: int getNonceCount(String nonce)
819: {
820: if (nonceCounts == null)
821: {
822: return 0;
823: }
824: return((Integer) nonceCounts.get(nonce)).intValue();
825: }
826:
827:
830: void incrementNonce(String nonce)
831: {
832: int current = getNonceCount(nonce);
833: if (nonceCounts == null)
834: {
835: nonceCounts = new HashMap();
836: }
837: nonceCounts.put(nonce, new Integer(current + 1));
838: }
839:
840:
841:
842: void addHandshakeCompletedListener(HandshakeCompletedListener l)
843: {
844: synchronized (handshakeCompletedListeners)
845: {
846: handshakeCompletedListeners.add(l);
847: }
848: }
849: void removeHandshakeCompletedListener(HandshakeCompletedListener l)
850: {
851: synchronized (handshakeCompletedListeners)
852: {
853: handshakeCompletedListeners.remove(l);
854: }
855: }
856:
857: }