| /* |
| * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| package jdk.incubator.http; |
| |
| import java.net.InetSocketAddress; |
| import java.util.HashMap; |
| import java.util.LinkedList; |
| import java.util.ListIterator; |
| import java.util.Objects; |
| import jdk.incubator.http.internal.common.Utils; |
| |
| /** |
| * Http 1.1 connection pool. |
| */ |
| final class ConnectionPool { |
| |
| static final long KEEP_ALIVE = Utils.getIntegerNetProperty( |
| "jdk.httpclient.keepalive.timeout", 1200); // seconds |
| |
| // Pools of idle connections |
| |
| final HashMap<CacheKey,LinkedList<HttpConnection>> plainPool; |
| final HashMap<CacheKey,LinkedList<HttpConnection>> sslPool; |
| CacheCleaner cleaner; |
| |
| /** |
| * Entries in connection pool are keyed by destination address and/or |
| * proxy address: |
| * case 1: plain TCP not via proxy (destination only) |
| * case 2: plain TCP via proxy (proxy only) |
| * case 3: SSL not via proxy (destination only) |
| * case 4: SSL over tunnel (destination and proxy) |
| */ |
| static class CacheKey { |
| final InetSocketAddress proxy; |
| final InetSocketAddress destination; |
| |
| CacheKey(InetSocketAddress destination, InetSocketAddress proxy) { |
| this.proxy = proxy; |
| this.destination = destination; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (obj == null) { |
| return false; |
| } |
| if (getClass() != obj.getClass()) { |
| return false; |
| } |
| final CacheKey other = (CacheKey) obj; |
| if (!Objects.equals(this.proxy, other.proxy)) { |
| return false; |
| } |
| if (!Objects.equals(this.destination, other.destination)) { |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(proxy, destination); |
| } |
| } |
| |
| static class ExpiryEntry { |
| final HttpConnection connection; |
| final long expiry; // absolute time in seconds of expiry time |
| ExpiryEntry(HttpConnection connection, long expiry) { |
| this.connection = connection; |
| this.expiry = expiry; |
| } |
| } |
| |
| final LinkedList<ExpiryEntry> expiryList; |
| |
| /** |
| * There should be one of these per HttpClient. |
| */ |
| ConnectionPool() { |
| plainPool = new HashMap<>(); |
| sslPool = new HashMap<>(); |
| expiryList = new LinkedList<>(); |
| } |
| |
| void start() { |
| } |
| |
| static CacheKey cacheKey(InetSocketAddress destination, |
| InetSocketAddress proxy) { |
| return new CacheKey(destination, proxy); |
| } |
| |
| synchronized HttpConnection getConnection(boolean secure, |
| InetSocketAddress addr, |
| InetSocketAddress proxy) { |
| CacheKey key = new CacheKey(addr, proxy); |
| HttpConnection c = secure ? findConnection(key, sslPool) |
| : findConnection(key, plainPool); |
| //System.out.println ("getConnection returning: " + c); |
| return c; |
| } |
| |
| /** |
| * Returns the connection to the pool. |
| */ |
| synchronized void returnToPool(HttpConnection conn) { |
| if (conn instanceof PlainHttpConnection) { |
| putConnection(conn, plainPool); |
| } else { |
| putConnection(conn, sslPool); |
| } |
| addToExpiryList(conn); |
| //System.out.println("Return to pool: " + conn); |
| } |
| |
| private HttpConnection |
| findConnection(CacheKey key, |
| HashMap<CacheKey,LinkedList<HttpConnection>> pool) { |
| LinkedList<HttpConnection> l = pool.get(key); |
| if (l == null || l.size() == 0) { |
| return null; |
| } else { |
| HttpConnection c = l.removeFirst(); |
| removeFromExpiryList(c); |
| return c; |
| } |
| } |
| |
| /* called from cache cleaner only */ |
| private void |
| removeFromPool(HttpConnection c, |
| HashMap<CacheKey,LinkedList<HttpConnection>> pool) { |
| //System.out.println("cacheCleaner removing: " + c); |
| LinkedList<HttpConnection> l = pool.get(c.cacheKey()); |
| assert l != null; |
| boolean wasPresent = l.remove(c); |
| assert wasPresent; |
| } |
| |
| private void |
| putConnection(HttpConnection c, |
| HashMap<CacheKey,LinkedList<HttpConnection>> pool) { |
| CacheKey key = c.cacheKey(); |
| LinkedList<HttpConnection> l = pool.get(key); |
| if (l == null) { |
| l = new LinkedList<>(); |
| pool.put(key, l); |
| } |
| l.add(c); |
| } |
| |
| // only runs while entries exist in cache |
| |
| final class CacheCleaner extends Thread { |
| |
| volatile boolean stopping; |
| |
| CacheCleaner() { |
| super(null, null, "HTTP-Cache-cleaner", 0, false); |
| setDaemon(true); |
| } |
| |
| synchronized boolean stopping() { |
| return stopping; |
| } |
| |
| synchronized void stopCleaner() { |
| stopping = true; |
| } |
| |
| @Override |
| public void run() { |
| while (!stopping()) { |
| try { |
| Thread.sleep(3000); |
| } catch (InterruptedException e) {} |
| cleanCache(); |
| } |
| } |
| } |
| |
| synchronized void removeFromExpiryList(HttpConnection c) { |
| if (c == null) { |
| return; |
| } |
| ListIterator<ExpiryEntry> li = expiryList.listIterator(); |
| while (li.hasNext()) { |
| ExpiryEntry e = li.next(); |
| if (e.connection.equals(c)) { |
| li.remove(); |
| return; |
| } |
| } |
| if (expiryList.isEmpty()) { |
| cleaner.stopCleaner(); |
| cleaner = null; |
| } |
| } |
| |
| private void cleanCache() { |
| long now = System.currentTimeMillis() / 1000; |
| LinkedList<HttpConnection> closelist = new LinkedList<>(); |
| |
| synchronized (this) { |
| ListIterator<ExpiryEntry> li = expiryList.listIterator(); |
| while (li.hasNext()) { |
| ExpiryEntry entry = li.next(); |
| if (entry.expiry <= now) { |
| li.remove(); |
| HttpConnection c = entry.connection; |
| closelist.add(c); |
| if (c instanceof PlainHttpConnection) { |
| removeFromPool(c, plainPool); |
| } else { |
| removeFromPool(c, sslPool); |
| } |
| } |
| } |
| } |
| for (HttpConnection c : closelist) { |
| //System.out.println ("KAC: closing " + c); |
| c.close(); |
| } |
| } |
| |
| private synchronized void addToExpiryList(HttpConnection conn) { |
| long now = System.currentTimeMillis() / 1000; |
| long then = now + KEEP_ALIVE; |
| |
| if (expiryList.isEmpty()) { |
| cleaner = new CacheCleaner(); |
| cleaner.start(); |
| } |
| |
| ListIterator<ExpiryEntry> li = expiryList.listIterator(); |
| while (li.hasNext()) { |
| ExpiryEntry entry = li.next(); |
| |
| if (then > entry.expiry) { |
| li.previous(); |
| // insert here |
| li.add(new ExpiryEntry(conn, then)); |
| return; |
| } |
| } |
| // first element of list |
| expiryList.add(new ExpiryEntry(conn, then)); |
| } |
| } |