privatevoidpromoteCalls() { if (runningAsyncCalls.size() >= maxRequests) return; // Already running max capacity. if (readyAsyncCalls.isEmpty()) return; // No ready calls to promote.
for (Iterator<AsyncCall> i= readyAsyncCalls.iterator(); i.hasNext(); ) { AsyncCallcall= i.next();
if (asyncCall.callsPerHost.get() >= this.maxRequestsPerHost) 这个条件时如何判断的呢? 主要是下面的方法,默认forWebSocket==true,走到条件里.如果AsyncCall已经存在队列中,那么就直接返回,把callsPerHost数值叠加.从而实现,上面的条件判断.
1 2 3 4 5 6 7 8 9 10
// Mutate the AsyncCall so that it shares the AtomicInteger of an existing running call to // the same host. if (!call.call.forWebSocket) { val existingCall = findExistingCallWithHost(call.host) if (existingCall != null) call.reuseCallsPerHostFrom(existingCall) }
Cached responses can be served even when the device is offline or has limited connectivity, ensuring that your app remains functional in challenging network conditions.
By default, OkHttp’s CacheInterceptor only supports caching for GET requests.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
internalfunput(response: Response): CacheRequest? { val requestMethod = response.request.method
if (HttpMethod.invalidatesCache(response.request.method)) { //This method determines whether it is not a get request try { remove(response.request) } catch (_: IOException) { // The cache cannot be written. } returnnull }
if (requestMethod != "GET") { // Don't cache non-GET responses. We're technically allowed to cache HEAD requests and some // POST requests, but the complexity of doing so is high and the benefit is low. returnnull } }
Request
RequestBody
The RequestBody is a basic request for OkHttp. It also provides FormBody and MultipartBody to construct the request body.
funcallAcquirePooledConnection( connectionUser: ConnectionUser, routes: List<Route>?, ): RealConnection? { for (connection in connections) { // In the first synchronized block, acquire the connection if it can satisfy this call. val acquired = synchronized(connection) { when { requireMultiplexed && !connection.isMultiplexed -> false !connection.isEligible(address, routes) -> false else -> { connectionUser.acquireConnectionNoEvents(connection) } } } // Confirm the connection is healthy and return it. if (connection.isHealthy(doExtensiveHealthChecks)) return connection } }
private fun connectTls( sslSocket: SSLSocket, connectionSpec: ConnectionSpec, ) { if (connectionSpec.supportsTlsExtensions) { Platform.get().configureTlsExtensions(sslSocket, address.url.host, address.protocols) } // Force handshake. This can throw! sslSocket.startHandshake() // Verify that the socket's certificates are acceptable for the target host. if (!address.hostnameVerifier!!.verify(address.url.host, sslSocketSession)) { val peerCertificates = unverifiedHandshake.peerCertificates if (peerCertificates.isNotEmpty()) { val cert = peerCertificates[0] asX509Certificate throwSSLPeerUnverifiedException( """ |Hostname ${address.url.host} not verified: | certificate: ${CertificatePinner.pin(cert)} | DN: ${cert.subjectDN.name} | subjectAltNames: ${OkHostnameVerifier.allSubjectAltNames(cert)} """.trimMargin(), ) val certificatePinner = address.certificatePinner!!
// Check that the certificate pinner is satisfied by the certificates presented. certificatePinner.check(address.url.host) { handshake.peerCertificates.map { it asX509Certificate } } } }
ChatGpt Resuming TCP connections in OkHttp is not straightforward because TCP connection resumption at the application level is generally managed by the underlying operating system and network stack rather than by application-level code. However, you can manage HTTP request retries and connection pooling to effectively handle network interruptions and failures.
Here are a few steps and strategies to handle TCP connection issues and retries in OkHttp:
Connection Pooling OkHttp manages connections in a connection pool, which can be reused for multiple requests. This reduces latency by avoiding the need to establish a new TCP connection for each request. Ensure your connection pool is properly configured.
Find the longest-idle connections in 2 categories:
1. OLD: Connections that have been idle for at least keepAliveDurationNs. We close these if
we find them, regardless of what the address policies need.
2. EVICTABLE: Connections not required by any address policy. This matches connections that
don't participate in any policy, plus connections whose policies won't be violated if the
connection is closed. We only close these if the idle connection limit is exceeded.
Also count the evictable connections to find out if we must close an EVICTABLE connection
funcloseConnections(now: Long): Long { // Compute the concurrent call capacity for each address. We won't close a connection if doing // so would violate a policy, unless it's OLD. valaddressStates=this.addressStates for(state in addressStates.values) { state.concurrentCallCapacity = 0 } for (connection in connections) { valaddressState= addressStates[connection.route.address] ?: continue synchronized(connection) { addressState.concurrentCallCapacity += connection.allocationLimit } }
// Find the longest-idle connections in 2 categories: // // 1. OLD: Connections that have been idle for at least keepAliveDurationNs. We close these if // we find them, regardless of what the address policies need. // // 2. EVICTABLE: Connections not required by any address policy. This matches connections that // don't participate in any policy, plus connections whose policies won't be violated if the // connection is closed. We only close these if the idle connection limit is exceeded. // // Also count the evictable connections to find out if we must close an EVICTABLE connection // before its keepAliveDurationNs is reached. varearliestOldIdleAtNs= (now - keepAliveDurationNs) + 1 var earliestOldConnection: RealConnection? = null varearliestEvictableIdleAtNs= Long.MAX_VALUE var earliestEvictableConnection: RealConnection? = null varinUseConnectionCount=0 varevictableConnectionCount=0 for (connection in connections) { synchronized(connection) { // If the connection is in use, keep searching. 如果该连接正在被使用,则继续遍历,使用连接数+1 if (pruneAndGetAllocationCount(connection, now) > 0) { inUseConnectionCount++ return@synchronized }
val toEvict: RealConnection? val toEvictIdleAtNs: Long when { // We had at least one OLD connection. Close the oldest one. earliestOldConnection != null -> { toEvict = earliestOldConnection toEvictIdleAtNs= earliestOldIdleAtNs }
// We have too many EVICTABLE connections. Close the oldest one. evictableConnectionCount > maxIdleConnections -> { //空闲连接个是> 配置的个数 toEvict = earliestEvictableConnection toEvictIdleAtNs= earliestEvictableIdleAtNs }
else -> { toEvict = null toEvictIdleAtNs= -1L } }
when { toEvict != null -> { // We've chosen a connection to evict. Confirm it's still okay to be evicted, then close it. synchronized(toEvict) { if (toEvict.calls.isNotEmpty()) return0L// No longer idle. if (toEvict.idleAtNs != toEvictIdleAtNs) return0L// No longer oldest. toEvict.noNewExchanges = true connections.remove(toEvict) } addressStates[toEvict.route.address]?.scheduleOpener() toEvict.socket().closeQuietly() connectionListener.connectionClosed(toEvict) if (connections.isEmpty()) cleanupQueue.cancelAll()
// Clean up again immediately. return0L }
earliestEvictableConnection != null -> { // A connection will be ready to evict soon. return earliestEvictableIdleAtNs + keepAliveDurationNs - now }
inUseConnectionCount > 0 -> { // All connections are in use. It'll be at least the keep alive duration 'til we run again. return keepAliveDurationNs }
else -> { // No connections, idle or in use. return -1 } } }
close OkhttpClient 立即关闭空闲连接,maxIdle、keepAliveDuration限制