/** * Returns a recycled connection to {@code address}, or null if no such connection exists. The * route is null if the address has not yet been routed. */ @Nullable RealConnection get(Address address, StreamAllocation streamAllocation, Route route) { assert (Thread.holdsLock(this)); for (RealConnection connection : connections) { if (connection.isEligible(address, route)) { streamAllocation.acquire(connection, true); return connection; } } returnnull; }
/** Current streams carried by this connection. */ publicfinal List<Reference<StreamAllocation>> allocations = newArrayList<>();
/** * Returns true if this connection can carry a stream allocation to {@code address}. If non-null * {@code route} is the resolved route for a connection. */ publicbooleanisEligible(Address address, @Nullable Route route) { // If this connection is not accepting new streams, we're done. if (allocations.size() >= allocationLimit || noNewStreams) returnfalse;
// If the non-host fields of the address don't overlap, we're done. if (!Internal.instance.equalsNonHost(this.route.address(), address)) returnfalse;
// If the host exactly matches, we're done: this connection can carry the address. if (address.url().host().equals(this.route().address().url().host())) { returntrue; // This connection is a perfect match. }
// At this point we don't have a hostname match. But we still be able to carry the request if // our connection coalescing requirements are met. See also: // https://hpbn.co/optimizing-application-delivery/#eliminate-domain-sharding // https://daniel.haxx.se/blog/2016/08/18/http2-connection-coalescing/
// 1. This connection must be HTTP/2. if (http2Connection == null) returnfalse;
// 2. The routes must share an IP address. This requires us to have a DNS address for both // hosts, which only happens after route planning. We can't coalesce connections that use a // proxy, since proxies don't tell us the origin server's IP address. if (route == null) returnfalse; if (route.proxy().type() != Proxy.Type.DIRECT) returnfalse; if (this.route.proxy().type() != Proxy.Type.DIRECT) returnfalse; if (!this.route.socketAddress().equals(route.socketAddress())) returnfalse;
// 3. This connection's server certificate's must cover the new host. if (route.address().hostnameVerifier() != OkHostnameVerifier.INSTANCE) returnfalse; if (!supportsUrl(address.url())) returnfalse;
// 4. Certificate pinning must match the host. try { address.certificatePinner().check(address.url().host(), handshake().peerCertificates()); } catch (SSLPeerUnverifiedException e) { returnfalse; }
returntrue; // The caller's address can be carried by this connection. }
/** * Finds a connection and returns it if it is healthy. If it is unhealthy the process is repeated * until a healthy connection is found. */ private RealConnection findHealthyConnection(int connectTimeout, int readTimeout, int writeTimeout, int pingIntervalMillis, boolean connectionRetryEnabled, boolean doExtensiveHealthChecks)throws IOException { while (true) { RealConnectioncandidate= findConnection(connectTimeout, readTimeout, writeTimeout, pingIntervalMillis, connectionRetryEnabled);
// If this is a brand new connection, we can skip the extensive health checks. synchronized (connectionPool) { if (candidate.successCount == 0) { return candidate; } }
// Do a (potentially slow) check to confirm that the pooled connection is still good. If it // isn't, take it out of the pool and start again. if (!candidate.isHealthy(doExtensiveHealthChecks)) { noNewStreams(); continue; }
/** Returns true if this connection is ready to host new streams. */ publicbooleanisHealthy(boolean doExtensiveChecks) { if (socket.isClosed() || socket.isInputShutdown() || socket.isOutputShutdown()) { returnfalse; }
if (http2Connection != null) { return !http2Connection.isShutdown(); }
if (doExtensiveChecks) { try { intreadTimeout= socket.getSoTimeout(); try { socket.setSoTimeout(1); if (source.exhausted()) { returnfalse; // Stream is exhausted; socket is closed. } returntrue; } finally { socket.setSoTimeout(readTimeout); } } catch (SocketTimeoutException ignored) { // Read timed out; socket is good. } catch (IOException e) { returnfalse; // Couldn't read; socket is closed. } }