1:
37:
38:
39: package ;
40:
41: import ;
42:
43: import ;
44: import ;
45:
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: import ;
61: import ;
62:
63: import ;
64: import ;
65: import ;
66: import ;
67: import ;
68:
69:
74: public class HTTPConnection
75: {
76:
77:
80: public static final int HTTP_PORT = 80;
81:
82:
85: public static final int HTTPS_PORT = 443;
86:
87: private static final String userAgent = SystemProperties.getProperty("http.agent");
88:
89:
92: protected final String hostname;
93:
94:
97: protected final int port;
98:
99:
102: protected final boolean secure;
103:
104:
107: protected final int connectionTimeout;
108:
109:
112: protected final int timeout;
113:
114:
117: protected String proxyHostname;
118:
119:
122: protected int proxyPort;
123:
124:
127: protected int majorVersion;
128:
129:
132: protected int minorVersion;
133:
134: private final List<HandshakeCompletedListener> handshakeCompletedListeners;
135:
136:
139: protected Socket socket;
140:
141:
144: private SSLSocketFactory sslSocketFactory;
145:
146:
149: protected InputStream in;
150:
151:
154: protected OutputStream out;
155:
156:
159: private Map<String, Integer> nonceCounts;
160:
161:
164: protected CookieManager cookieManager;
165:
166:
167:
170: private Pool pool;
171:
172:
176: public HTTPConnection(String hostname)
177: {
178: this(hostname, HTTP_PORT, false, 0, 0);
179: }
180:
181:
186: public HTTPConnection(String hostname, boolean secure)
187: {
188: this(hostname, secure ? HTTPS_PORT : HTTP_PORT, secure, 0, 0);
189: }
190:
191:
198: public HTTPConnection(String hostname, boolean secure,
199: int connectionTimeout, int timeout)
200: {
201: this(hostname, secure ? HTTPS_PORT : HTTP_PORT, secure,
202: connectionTimeout, timeout);
203: }
204:
205:
210: public HTTPConnection(String hostname, int port)
211: {
212: this(hostname, port, false, 0, 0);
213: }
214:
215:
221: public HTTPConnection(String hostname, int port, boolean secure)
222: {
223: this(hostname, port, secure, 0, 0);
224: }
225:
226:
237: public HTTPConnection(String hostname, int port, boolean secure,
238: int connectionTimeout, int timeout)
239: {
240: if (connectionTimeout < 0 || timeout < 0)
241: throw new IllegalArgumentException();
242:
243: this.hostname = hostname;
244: this.port = port;
245: this.secure = secure;
246: this.connectionTimeout = connectionTimeout;
247: this.timeout = timeout;
248: majorVersion = minorVersion = 1;
249: handshakeCompletedListeners
250: = new ArrayList<HandshakeCompletedListener>(2);
251: }
252:
253:
256: public String getHostName()
257: {
258: return hostname;
259: }
260:
261:
264: public int getPort()
265: {
266: return port;
267: }
268:
269:
272: public boolean isSecure()
273: {
274: return secure;
275: }
276:
277:
282: public String getVersion()
283: {
284: return "HTTP/" + majorVersion + '.' + minorVersion;
285: }
286:
287:
292: public void setVersion(int majorVersion, int minorVersion)
293: {
294: if (majorVersion != 1)
295: {
296: throw new IllegalArgumentException("major version not supported: " +
297: majorVersion);
298: }
299: if (minorVersion < 0 || minorVersion > 1)
300: {
301: throw new IllegalArgumentException("minor version not supported: " +
302: minorVersion);
303: }
304: this.majorVersion = majorVersion;
305: this.minorVersion = minorVersion;
306: }
307:
308:
313: public void setProxy(String hostname, int port)
314: {
315: proxyHostname = hostname;
316: proxyPort = port;
317: }
318:
319:
322: public boolean isUsingProxy()
323: {
324: return (proxyHostname != null && proxyPort > 0);
325: }
326:
327:
331: public void setCookieManager(CookieManager cookieManager)
332: {
333: this.cookieManager = cookieManager;
334: }
335:
336:
339: public CookieManager getCookieManager()
340: {
341: return cookieManager;
342: }
343:
344:
353: static class Pool
354: {
355:
358: static Pool instance = new Pool();
359:
360:
363: final LinkedList<HTTPConnection> connectionPool
364: = new LinkedList<HTTPConnection>();
365:
366:
369: int maxConnections;
370:
371:
375: int connectionTTL;
376:
377:
380: class Reaper
381: implements Runnable
382: {
383: public void run()
384: {
385: synchronized (Pool.this)
386: {
387: try
388: {
389: do
390: {
391: while (connectionPool.size() > 0)
392: {
393: long currentTime = System.currentTimeMillis();
394:
395: HTTPConnection c =
396: (HTTPConnection)connectionPool.getFirst();
397:
398: long waitTime = c.timeLastUsed
399: + connectionTTL - currentTime;
400:
401: if (waitTime <= 0)
402: removeOldest();
403: else
404: try
405: {
406: Pool.this.wait(waitTime);
407: }
408: catch (InterruptedException _)
409: {
410:
411: }
412: }
413:
414:
415:
416:
417:
418:
419:
420:
421:
422:
423: try
424: {
425: Pool.this.wait(connectionTTL);
426: }
427: catch (InterruptedException _)
428: {
429:
430: }
431: }
432: while (connectionPool.size() > 0);
433: }
434: finally
435: {
436: reaper = null;
437: }
438: }
439: }
440: }
441:
442: Reaper reaper;
443:
444:
447: private Pool()
448: {
449: }
450:
451:
461: private static boolean matches(HTTPConnection c,
462: String h, int p, boolean sec)
463: {
464: return h.equals(c.hostname) && (p == c.port) && (sec == c.secure);
465: }
466:
467:
478: synchronized HTTPConnection get(String host,
479: int port,
480: boolean secure,
481: int connectionTimeout, int timeout)
482: {
483: String ttl =
484: SystemProperties.getProperty("classpath.net.http.keepAliveTTL");
485: connectionTTL = 10000;
486: if (ttl != null && ttl.length() > 0)
487: try
488: {
489: int v = 1000 * Integer.parseInt(ttl);
490: if (v >= 0)
491: connectionTTL = v;
492: }
493: catch (NumberFormatException _)
494: {
495:
496: }
497:
498: String mc = SystemProperties.getProperty("http.maxConnections");
499: maxConnections = 5;
500: if (mc != null && mc.length() > 0)
501: try
502: {
503: int v = Integer.parseInt(mc);
504: if (v > 0)
505: maxConnections = v;
506: }
507: catch (NumberFormatException _)
508: {
509:
510: }
511:
512: HTTPConnection c = null;
513:
514: ListIterator it = connectionPool.listIterator(0);
515: while (it.hasNext())
516: {
517: HTTPConnection cc = (HTTPConnection)it.next();
518: if (matches(cc, host, port, secure))
519: {
520: c = cc;
521: it.remove();
522:
523: if (c.socket != null)
524: try
525: {
526: c.socket.setSoTimeout(timeout);
527: }
528: catch (SocketException _)
529: {
530:
531: }
532: break;
533: }
534: }
535: if (c == null)
536: {
537: c = new HTTPConnection(host, port, secure,
538: connectionTimeout, timeout);
539: c.setPool(this);
540: }
541: return c;
542: }
543:
544:
550: synchronized void put(HTTPConnection c)
551: {
552: c.timeLastUsed = System.currentTimeMillis();
553: connectionPool.addLast(c);
554:
555:
556: while (connectionPool.size() >= maxConnections)
557: removeOldest();
558:
559: if (connectionTTL > 0 && null == reaper) {
560:
561:
562:
563:
564: reaper = new Reaper();
565: Thread t = new Thread(reaper, "HTTPConnection.Reaper");
566: t.setDaemon(true);
567: t.start();
568: }
569: }
570:
571:
574: void removeOldest()
575: {
576: HTTPConnection cx = (HTTPConnection)connectionPool.removeFirst();
577: try
578: {
579: cx.closeConnection();
580: }
581: catch (IOException ioe)
582: {
583:
584: }
585: }
586: }
587:
588:
591: int useCount;
592:
593:
596: long timeLastUsed;
597:
598:
605: void setPool(Pool p)
606: {
607: pool = p;
608: }
609:
610:
615: void release()
616: {
617: if (pool != null)
618: {
619: useCount++;
620: pool.put(this);
621:
622: }
623: else
624: {
625:
626: try
627: {
628: closeConnection();
629: }
630: catch (IOException ioe)
631: {
632:
633: }
634: }
635: }
636:
637:
643: public Request newRequest(String method, String path)
644: {
645: if (method == null || method.length() == 0)
646: {
647: throw new IllegalArgumentException("method must have non-zero length");
648: }
649: if (path == null || path.length() == 0)
650: {
651: path = "/";
652: }
653: Request ret = new Request(this, method, path);
654: if ((secure && port != HTTPS_PORT) ||
655: (!secure && port != HTTP_PORT))
656: {
657: ret.setHeader("Host", hostname + ":" + port);
658: }
659: else
660: {
661: ret.setHeader("Host", hostname);
662: }
663: ret.setHeader("User-Agent", userAgent);
664: ret.setHeader("Connection", "keep-alive");
665: ret.setHeader("Accept-Encoding",
666: "chunked;q=1.0, gzip;q=0.9, deflate;q=0.8, " +
667: "identity;q=0.6, *;q=0");
668: if (cookieManager != null)
669: {
670: Cookie[] cookies = cookieManager.getCookies(hostname, secure, path);
671: if (cookies != null && cookies.length > 0)
672: {
673: CPStringBuilder buf = new CPStringBuilder();
674: buf.append("$Version=1");
675: for (int i = 0; i < cookies.length; i++)
676: {
677: buf.append(',');
678: buf.append(' ');
679: buf.append(cookies[i].toString());
680: }
681: ret.setHeader("Cookie", buf.toString());
682: }
683: }
684: return ret;
685: }
686:
687:
690: public void close()
691: throws IOException
692: {
693: closeConnection();
694: }
695:
696:
700: protected synchronized Socket getSocket()
701: throws IOException
702: {
703: if (socket == null)
704: {
705: String connectHostname = hostname;
706: int connectPort = port;
707: if (isUsingProxy())
708: {
709: connectHostname = proxyHostname;
710: connectPort = proxyPort;
711: }
712: socket = new Socket();
713: InetSocketAddress address =
714: new InetSocketAddress(connectHostname, connectPort);
715: if (connectionTimeout > 0)
716: {
717: socket.connect(address, connectionTimeout);
718: }
719: else
720: {
721: socket.connect(address);
722: }
723: if (timeout > 0)
724: {
725: socket.setSoTimeout(timeout);
726: }
727: if (secure)
728: {
729: try
730: {
731: SSLSocketFactory factory = getSSLSocketFactory();
732: SSLSocket ss =
733: (SSLSocket) factory.createSocket(socket, connectHostname,
734: connectPort, true);
735: String[] protocols = { "TLSv1", "SSLv3" };
736: ss.setEnabledProtocols(protocols);
737: ss.setUseClientMode(true);
738: synchronized (handshakeCompletedListeners)
739: {
740: if (!handshakeCompletedListeners.isEmpty())
741: {
742: for (Iterator i =
743: handshakeCompletedListeners.iterator();
744: i.hasNext(); )
745: {
746: HandshakeCompletedListener l =
747: (HandshakeCompletedListener) i.next();
748: ss.addHandshakeCompletedListener(l);
749: }
750: }
751: }
752: ss.startHandshake();
753: socket = ss;
754: }
755: catch (GeneralSecurityException e)
756: {
757: throw new IOException(e.getMessage());
758: }
759: }
760: in = socket.getInputStream();
761: in = new BufferedInputStream(in);
762: out = socket.getOutputStream();
763: out = new BufferedOutputStream(out);
764: }
765: return socket;
766: }
767:
768: SSLSocketFactory getSSLSocketFactory()
769: throws GeneralSecurityException
770: {
771: if (sslSocketFactory == null)
772: {
773: TrustManager tm = new EmptyX509TrustManager();
774: SSLContext context = SSLContext.getInstance("SSL");
775: TrustManager[] trust = new TrustManager[] { tm };
776: context.init(null, trust, null);
777: sslSocketFactory = context.getSocketFactory();
778: }
779: return sslSocketFactory;
780: }
781:
782: void setSSLSocketFactory(SSLSocketFactory factory)
783: {
784: sslSocketFactory = factory;
785: }
786:
787: protected synchronized InputStream getInputStream()
788: throws IOException
789: {
790: if (socket == null)
791: {
792: getSocket();
793: }
794: return in;
795: }
796:
797: protected synchronized OutputStream getOutputStream()
798: throws IOException
799: {
800: if (socket == null)
801: {
802: getSocket();
803: }
804: return out;
805: }
806:
807:
810: protected synchronized void closeConnection()
811: throws IOException
812: {
813: if (socket != null)
814: {
815: try
816: {
817: socket.close();
818: }
819: finally
820: {
821: socket = null;
822: }
823: }
824: }
825:
826:
830: protected String getURI()
831: {
832: CPStringBuilder buf = new CPStringBuilder();
833: buf.append(secure ? "https://" : "http://");
834: buf.append(hostname);
835: if (secure)
836: {
837: if (port != HTTPConnection.HTTPS_PORT)
838: {
839: buf.append(':');
840: buf.append(port);
841: }
842: }
843: else
844: {
845: if (port != HTTPConnection.HTTP_PORT)
846: {
847: buf.append(':');
848: buf.append(port);
849: }
850: }
851: return buf.toString();
852: }
853:
854:
858: int getNonceCount(String nonce)
859: {
860: if (nonceCounts == null)
861: {
862: return 0;
863: }
864: return nonceCounts.get(nonce).intValue();
865: }
866:
867:
870: void incrementNonce(String nonce)
871: {
872: int current = getNonceCount(nonce);
873: if (nonceCounts == null)
874: {
875: nonceCounts = new HashMap<String, Integer>();
876: }
877: nonceCounts.put(nonce, new Integer(current + 1));
878: }
879:
880:
881:
882: void addHandshakeCompletedListener(HandshakeCompletedListener l)
883: {
884: synchronized (handshakeCompletedListeners)
885: {
886: handshakeCompletedListeners.add(l);
887: }
888: }
889: void removeHandshakeCompletedListener(HandshakeCompletedListener l)
890: {
891: synchronized (handshakeCompletedListeners)
892: {
893: handshakeCompletedListeners.remove(l);
894: }
895: }
896:
897: }