/
-UtilJvm.kt
370 lines (329 loc) · 10.5 KB
/
-UtilJvm.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@file:Suppress("ktlint:standard:filename")
package okhttp3.internal
import java.io.IOException
import java.io.InterruptedIOException
import java.net.InetSocketAddress
import java.net.ServerSocket
import java.net.Socket
import java.net.SocketTimeoutException
import java.nio.charset.Charset
import java.util.Collections
import java.util.Locale
import java.util.TimeZone
import java.util.concurrent.ThreadFactory
import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.ReentrantLock
import kotlin.text.Charsets.UTF_16BE
import kotlin.text.Charsets.UTF_16LE
import kotlin.text.Charsets.UTF_32BE
import kotlin.text.Charsets.UTF_32LE
import kotlin.text.Charsets.UTF_8
import kotlin.time.Duration
import okhttp3.EventListener
import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.defaultPort
import okhttp3.OkHttpClient
import okhttp3.RequestBody
import okhttp3.Response
import okhttp3.ResponseBody
import okhttp3.internal.http2.Header
import okio.Buffer
import okio.BufferedSource
import okio.Source
@JvmField
internal val EMPTY_HEADERS: Headers = commonEmptyHeaders
@JvmField
internal val EMPTY_REQUEST: RequestBody = commonEmptyRequestBody
@JvmField
internal val EMPTY_RESPONSE: ResponseBody = commonEmptyResponse
/** GMT and UTC are equivalent for our purposes. */
@JvmField
internal val UTC: TimeZone = TimeZone.getTimeZone("GMT")!!
internal fun threadFactory(
name: String,
daemon: Boolean,
): ThreadFactory =
ThreadFactory { runnable ->
Thread(runnable, name).apply {
isDaemon = daemon
}
}
internal fun HttpUrl.toHostHeader(includeDefaultPort: Boolean = false): String {
val host =
if (":" in host) {
"[$host]"
} else {
host
}
return if (includeDefaultPort || port != defaultPort(scheme)) {
"$host:$port"
} else {
host
}
}
/** Returns a [Locale.US] formatted [String]. */
internal fun format(
format: String,
vararg args: Any,
): String {
return String.format(Locale.US, format, *args)
}
@Throws(IOException::class)
internal fun BufferedSource.readBomAsCharset(default: Charset): Charset {
return when (select(UNICODE_BOMS)) {
0 -> UTF_8
1 -> UTF_16BE
2 -> UTF_16LE
3 -> UTF_32BE
4 -> UTF_32LE
-1 -> default
else -> throw AssertionError()
}
}
internal fun checkDuration(
name: String,
duration: Long,
unit: TimeUnit,
): Int {
check(duration >= 0L) { "$name < 0" }
val millis = unit.toMillis(duration)
require(millis <= Integer.MAX_VALUE) { "$name too large" }
require(millis != 0L || duration <= 0L) { "$name too small" }
return millis.toInt()
}
internal fun checkDuration(
name: String,
duration: Duration,
): Int {
check(!duration.isNegative()) { "$name < 0" }
val millis = duration.inWholeMilliseconds
require(millis <= Integer.MAX_VALUE) { "$name too large" }
require(millis != 0L || !duration.isPositive()) { "$name too small" }
return millis.toInt()
}
internal fun List<Header>.toHeaders(): Headers {
val builder = Headers.Builder()
for ((name, value) in this) {
builder.addLenient(name.utf8(), value.utf8())
}
return builder.build()
}
internal fun Headers.toHeaderList(): List<Header> =
(0 until size).map {
Header(name(it), value(it))
}
/** Returns true if an HTTP request for this URL and [other] can reuse a connection. */
internal fun HttpUrl.canReuseConnectionFor(other: HttpUrl): Boolean =
host == other.host &&
port == other.port &&
scheme == other.scheme
internal fun EventListener.asFactory() = EventListener.Factory { this }
/**
* Reads until this is exhausted or the deadline has been reached. This is careful to not extend the
* deadline if one exists already.
*/
@Throws(IOException::class)
internal fun Source.skipAll(
duration: Int,
timeUnit: TimeUnit,
): Boolean {
val nowNs = System.nanoTime()
val originalDurationNs =
if (timeout().hasDeadline()) {
timeout().deadlineNanoTime() - nowNs
} else {
Long.MAX_VALUE
}
timeout().deadlineNanoTime(nowNs + minOf(originalDurationNs, timeUnit.toNanos(duration.toLong())))
return try {
val skipBuffer = Buffer()
while (read(skipBuffer, 8192) != -1L) {
skipBuffer.clear()
}
true // Success! The source has been exhausted.
} catch (_: InterruptedIOException) {
false // We ran out of time before exhausting the source.
} finally {
if (originalDurationNs == Long.MAX_VALUE) {
timeout().clearDeadline()
} else {
timeout().deadlineNanoTime(nowNs + originalDurationNs)
}
}
}
/**
* Attempts to exhaust this, returning true if successful. This is useful when reading a complete
* source is helpful, such as when doing so completes a cache body or frees a socket connection for
* reuse.
*/
internal fun Source.discard(
timeout: Int,
timeUnit: TimeUnit,
): Boolean =
try {
this.skipAll(timeout, timeUnit)
} catch (_: IOException) {
false
}
internal fun Socket.peerName(): String {
val address = remoteSocketAddress
return if (address is InetSocketAddress) address.hostName else address.toString()
}
/**
* Returns true if new reads and writes should be attempted on this.
*
* Unfortunately Java's networking APIs don't offer a good health check, so we go on our own by
* attempting to read with a short timeout. If the fails immediately we know the socket is
* unhealthy.
*
* @param source the source used to read bytes from the socket.
*/
internal fun Socket.isHealthy(source: BufferedSource): Boolean {
return try {
val readTimeout = soTimeout
try {
soTimeout = 1
!source.exhausted()
} finally {
soTimeout = readTimeout
}
} catch (_: SocketTimeoutException) {
true // Read timed out; socket is good.
} catch (_: IOException) {
false // Couldn't read; socket is closed.
}
}
internal inline fun threadName(
name: String,
block: () -> Unit,
) {
val currentThread = Thread.currentThread()
val oldName = currentThread.name
currentThread.name = name
try {
block()
} finally {
currentThread.name = oldName
}
}
/** Returns the Content-Length as reported by the response headers. */
internal fun Response.headersContentLength(): Long {
return headers["Content-Length"]?.toLongOrDefault(-1L) ?: -1L
}
/** Returns an immutable copy of this. */
internal fun <T> List<T>.toImmutableList(): List<T> {
return Collections.unmodifiableList(toMutableList())
}
/** Returns an immutable list containing [elements]. */
@SafeVarargs
internal fun <T> immutableListOf(vararg elements: T): List<T> {
return Collections.unmodifiableList(listOf(*elements.clone()))
}
/** Closes this, ignoring any checked exceptions. */
internal fun Socket.closeQuietly() {
try {
close()
} catch (e: AssertionError) {
throw e
} catch (rethrown: RuntimeException) {
if (rethrown.message == "bio == null") {
// Conscrypt in Android 10 and 11 may throw closing an SSLSocket. This is safe to ignore.
// https://issuetracker.google.com/issues/177450597
return
}
throw rethrown
} catch (_: Exception) {
}
}
/** Closes this, ignoring any checked exceptions. */
internal fun ServerSocket.closeQuietly() {
try {
close()
} catch (rethrown: RuntimeException) {
throw rethrown
} catch (_: Exception) {
}
}
internal fun Long.toHexString(): String = java.lang.Long.toHexString(this)
internal fun Int.toHexString(): String = Integer.toHexString(this)
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "NOTHING_TO_INLINE")
internal inline fun Any.wait() = (this as Object).wait()
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "NOTHING_TO_INLINE")
internal inline fun Any.notify() = (this as Object).notify()
@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "NOTHING_TO_INLINE")
internal inline fun Any.notifyAll() = (this as Object).notifyAll()
internal fun <T> readFieldOrNull(
instance: Any,
fieldType: Class<T>,
fieldName: String,
): T? {
var c: Class<*> = instance.javaClass
while (c != Any::class.java) {
try {
val field = c.getDeclaredField(fieldName)
field.isAccessible = true
val value = field.get(instance)
return if (!fieldType.isInstance(value)) null else fieldType.cast(value)
} catch (_: NoSuchFieldException) {
}
c = c.superclass
}
// Didn't find the field we wanted. As a last gasp attempt,
// try to find the value on a delegate.
if (fieldName != "delegate") {
val delegate = readFieldOrNull(instance, Any::class.java, "delegate")
if (delegate != null) return readFieldOrNull(delegate, fieldType, fieldName)
}
return null
}
@JvmField
internal val assertionsEnabled: Boolean = OkHttpClient::class.java.desiredAssertionStatus()
/**
* Returns the string "OkHttp" unless the library has been shaded for inclusion in another library,
* or obfuscated with tools like R8 or ProGuard. In such cases it'll return a longer string like
* "com.example.shaded.okhttp3.OkHttp". In large applications it's possible to have multiple OkHttp
* instances; this makes it clear which is which.
*/
@JvmField
internal val okHttpName: String =
OkHttpClient::class.java.name.removePrefix("okhttp3.").removeSuffix("Client")
@Suppress("NOTHING_TO_INLINE")
internal inline fun ReentrantLock.assertHeld() {
if (assertionsEnabled && !this.isHeldByCurrentThread) {
throw AssertionError("Thread ${Thread.currentThread().name} MUST hold lock on $this")
}
}
@Suppress("NOTHING_TO_INLINE")
internal inline fun Any.assertThreadHoldsLock() {
if (assertionsEnabled && !Thread.holdsLock(this)) {
throw AssertionError("Thread ${Thread.currentThread().name} MUST hold lock on $this")
}
}
@Suppress("NOTHING_TO_INLINE")
internal inline fun ReentrantLock.assertNotHeld() {
if (assertionsEnabled && this.isHeldByCurrentThread) {
throw AssertionError("Thread ${Thread.currentThread().name} MUST NOT hold lock on $this")
}
}
@Suppress("NOTHING_TO_INLINE")
internal inline fun Any.assertThreadDoesntHoldLock() {
if (assertionsEnabled && Thread.holdsLock(this)) {
throw AssertionError("Thread ${Thread.currentThread().name} MUST NOT hold lock on $this")
}
}