优化
This commit is contained in:
@@ -1,9 +1,12 @@
|
|||||||
package io.legado.app.lib.cronet
|
package io.legado.app.lib.cronet
|
||||||
|
|
||||||
import androidx.annotation.Keep
|
import androidx.annotation.Keep
|
||||||
|
import io.legado.app.help.coroutine.Coroutine
|
||||||
import io.legado.app.help.http.okHttpClient
|
import io.legado.app.help.http.okHttpClient
|
||||||
import io.legado.app.utils.DebugLog
|
import io.legado.app.utils.DebugLog
|
||||||
import io.legado.app.utils.asIOException
|
import io.legado.app.utils.asIOException
|
||||||
|
import io.legado.app.utils.splitNotBlank
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import okhttp3.*
|
import okhttp3.*
|
||||||
import okhttp3.EventListener
|
import okhttp3.EventListener
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
@@ -16,12 +19,14 @@ import org.chromium.net.CronetException
|
|||||||
import org.chromium.net.UrlRequest
|
import org.chromium.net.UrlRequest
|
||||||
import org.chromium.net.UrlResponseInfo
|
import org.chromium.net.UrlResponseInfo
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.net.ProtocolException
|
||||||
import java.nio.ByteBuffer
|
import java.nio.ByteBuffer
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ArrayBlockingQueue
|
import java.util.concurrent.ArrayBlockingQueue
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
|
|
||||||
|
|
||||||
@Keep
|
@Keep
|
||||||
abstract class AbsCallBack(
|
abstract class AbsCallBack(
|
||||||
val originalRequest: Request,
|
val originalRequest: Request,
|
||||||
@@ -34,8 +39,10 @@ abstract class AbsCallBack(
|
|||||||
private var followCount = 0
|
private var followCount = 0
|
||||||
private var request: UrlRequest? = null
|
private var request: UrlRequest? = null
|
||||||
private var finished = AtomicBoolean(false)
|
private var finished = AtomicBoolean(false)
|
||||||
|
private val canceled = AtomicBoolean(false)
|
||||||
private val callbackResults = ArrayBlockingQueue<CallbackResult>(2)
|
private val callbackResults = ArrayBlockingQueue<CallbackResult>(2)
|
||||||
private val urlResponseInfoChain = arrayListOf<UrlResponseInfo>()
|
private val urlResponseInfoChain = arrayListOf<UrlResponseInfo>()
|
||||||
|
private var cancelJob: Coroutine<*>? = null
|
||||||
|
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
@@ -87,14 +94,30 @@ abstract class AbsCallBack(
|
|||||||
|
|
||||||
override fun onResponseStarted(request: UrlRequest, info: UrlResponseInfo) {
|
override fun onResponseStarted(request: UrlRequest, info: UrlResponseInfo) {
|
||||||
this.request = request
|
this.request = request
|
||||||
val contentLength = info.allHeaders["Content-Length"]?.lastOrNull()?.toLongOrNull() ?: -1
|
|
||||||
val contentType = (info.allHeaders["content-type"]?.lastOrNull()
|
cancelJob = Coroutine.async {
|
||||||
?: "text/plain; charset=\"utf-8\"").toMediaTypeOrNull()
|
while (!mCall.isCanceled()) {
|
||||||
val responseBody = CronetBodySource().buffer().asResponseBody(contentType, contentLength)
|
delay(1000)
|
||||||
|
}
|
||||||
|
request.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
val responseBuilder: Response.Builder
|
||||||
|
try {
|
||||||
|
responseBuilder = createResponse(
|
||||||
|
originalRequest,
|
||||||
|
info,
|
||||||
|
CronetBodySource()
|
||||||
|
)
|
||||||
|
} catch (e: IOException) {
|
||||||
|
request.cancel()
|
||||||
|
cancelJob?.cancel()
|
||||||
|
onError(e)
|
||||||
|
return
|
||||||
|
}
|
||||||
val newRequest = originalRequest.newBuilder().url(info.url).build()
|
val newRequest = originalRequest.newBuilder().url(info.url).build()
|
||||||
val response = createResponse(originalRequest, info)
|
val response = responseBuilder
|
||||||
.request(newRequest)
|
.request(newRequest)
|
||||||
.body(responseBody)
|
|
||||||
.priorResponse(buildPriorResponse(originalRequest, urlResponseInfoChain, info.urlChain))
|
.priorResponse(buildPriorResponse(originalRequest, urlResponseInfoChain, info.urlChain))
|
||||||
.build()
|
.build()
|
||||||
mResponse = response
|
mResponse = response
|
||||||
@@ -126,6 +149,7 @@ abstract class AbsCallBack(
|
|||||||
|
|
||||||
override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) {
|
override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) {
|
||||||
callbackResults.add(CallbackResult(CallbackStep.ON_SUCCESS))
|
callbackResults.add(CallbackResult(CallbackStep.ON_SUCCESS))
|
||||||
|
cancelJob?.cancel()
|
||||||
eventListener?.responseBodyEnd(mCall, info.receivedByteCount)
|
eventListener?.responseBodyEnd(mCall, info.receivedByteCount)
|
||||||
//DebugLog.i(javaClass.simpleName, "end[${info.negotiatedProtocol}]${info.url}")
|
//DebugLog.i(javaClass.simpleName, "end[${info.negotiatedProtocol}]${info.url}")
|
||||||
|
|
||||||
@@ -143,6 +167,7 @@ abstract class AbsCallBack(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCanceled(request: UrlRequest?, info: UrlResponseInfo?) {
|
override fun onCanceled(request: UrlRequest?, info: UrlResponseInfo?) {
|
||||||
|
canceled.set(true)
|
||||||
callbackResults.add(CallbackResult(CallbackStep.ON_CANCELED))
|
callbackResults.add(CallbackResult(CallbackStep.ON_CANCELED))
|
||||||
eventListener?.callEnd(mCall)
|
eventListener?.callEnd(mCall)
|
||||||
//onError(IOException("Cronet Request Canceled"))
|
//onError(IOException("Cronet Request Canceled"))
|
||||||
@@ -161,6 +186,8 @@ abstract class AbsCallBack(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val MAX_FOLLOW_COUNT = 20
|
const val MAX_FOLLOW_COUNT = 20
|
||||||
|
private val encodingsHandledByCronet = setOf("br", "deflate", "gzip", "x-gzip")
|
||||||
|
|
||||||
private fun protocolFromNegotiatedProtocol(responseInfo: UrlResponseInfo): Protocol {
|
private fun protocolFromNegotiatedProtocol(responseInfo: UrlResponseInfo): Protocol {
|
||||||
val negotiatedProtocol = responseInfo.negotiatedProtocol.lowercase(Locale.getDefault())
|
val negotiatedProtocol = responseInfo.negotiatedProtocol.lowercase(Locale.getDefault())
|
||||||
return when {
|
return when {
|
||||||
@@ -191,13 +218,20 @@ abstract class AbsCallBack(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun headersFromResponse(responseInfo: UrlResponseInfo): Headers {
|
private fun headersFromResponse(
|
||||||
|
responseInfo: UrlResponseInfo,
|
||||||
|
keepEncodingAffectedHeaders: Boolean
|
||||||
|
): Headers {
|
||||||
|
|
||||||
val headers = responseInfo.allHeadersAsList
|
val headers = responseInfo.allHeadersAsList
|
||||||
return Headers.Builder().apply {
|
return Headers.Builder().apply {
|
||||||
for ((key, value) in headers) {
|
for ((key, value) in headers) {
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if (key.equals("content-encoding", ignoreCase = true)) {
|
if (!keepEncodingAffectedHeaders
|
||||||
|
&& (key.equals("content-encoding", ignoreCase = true)
|
||||||
|
|| key.equals("Content-Length", ignoreCase = true))
|
||||||
|
) {
|
||||||
// Strip all content encoding headers as decoding is done handled by cronet
|
// Strip all content encoding headers as decoding is done handled by cronet
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -212,12 +246,39 @@ abstract class AbsCallBack(
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
private fun createResponse(
|
private fun createResponse(
|
||||||
request: Request,
|
request: Request,
|
||||||
responseInfo: UrlResponseInfo
|
responseInfo: UrlResponseInfo,
|
||||||
|
bodySource: Source? = null
|
||||||
): Response.Builder {
|
): Response.Builder {
|
||||||
val protocol = protocolFromNegotiatedProtocol(responseInfo)
|
val protocol = protocolFromNegotiatedProtocol(responseInfo)
|
||||||
val headers = headersFromResponse(responseInfo)
|
|
||||||
|
val contentEncodingHeaders =
|
||||||
|
responseInfo.allHeaders.getOrDefault("content-encoding", emptyList())
|
||||||
|
val contentEncodingItems = contentEncodingHeaders.flatMap {
|
||||||
|
it.splitNotBlank(",").toList()
|
||||||
|
}
|
||||||
|
val keepEncodingAffectedHeaders = contentEncodingItems.isEmpty()
|
||||||
|
|| !encodingsHandledByCronet.containsAll(contentEncodingItems)
|
||||||
|
|
||||||
|
val headers = headersFromResponse(responseInfo, keepEncodingAffectedHeaders)
|
||||||
|
val contentLength = if (keepEncodingAffectedHeaders) {
|
||||||
|
responseInfo.allHeaders["Content-Length"]?.lastOrNull()
|
||||||
|
} else null
|
||||||
|
val contentType = responseInfo.allHeaders["content-type"]?.lastOrNull()
|
||||||
|
?: "text/plain; charset=\"utf-8\""
|
||||||
|
|
||||||
|
val responseBody = bodySource?.let {
|
||||||
|
createResponseBody(
|
||||||
|
request,
|
||||||
|
responseInfo.httpStatusCode,
|
||||||
|
contentType,
|
||||||
|
contentLength,
|
||||||
|
bodySource
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return Response.Builder()
|
return Response.Builder()
|
||||||
.request(request)
|
.request(request)
|
||||||
.receivedResponseAtMillis(System.currentTimeMillis())
|
.receivedResponseAtMillis(System.currentTimeMillis())
|
||||||
@@ -225,6 +286,7 @@ abstract class AbsCallBack(
|
|||||||
.code(responseInfo.httpStatusCode)
|
.code(responseInfo.httpStatusCode)
|
||||||
.message(responseInfo.httpStatusText)
|
.message(responseInfo.httpStatusText)
|
||||||
.headers(headers)
|
.headers(headers)
|
||||||
|
.body(responseBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun buildPriorResponse(
|
private fun buildPriorResponse(
|
||||||
@@ -247,6 +309,32 @@ abstract class AbsCallBack(
|
|||||||
}
|
}
|
||||||
return priorResponse
|
return priorResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private fun createResponseBody(
|
||||||
|
request: Request,
|
||||||
|
httpStatusCode: Int,
|
||||||
|
contentType: String?,
|
||||||
|
contentLengthString: String?,
|
||||||
|
bodySource: Source
|
||||||
|
): ResponseBody {
|
||||||
|
|
||||||
|
// Ignore content-length header for HEAD requests (consistency with OkHttp)
|
||||||
|
val contentLength: Long = if (request.method == "HEAD") {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
contentLengthString?.toLongOrNull() ?: -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for absence of body in No Content / Reset Content responses (OkHttp consistency)
|
||||||
|
if ((httpStatusCode == 204 || httpStatusCode == 205) && contentLength > 0) {
|
||||||
|
throw ProtocolException(
|
||||||
|
"HTTP $httpStatusCode had non-zero Content-Length: $contentLengthString"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return bodySource.buffer()
|
||||||
|
.asResponseBody(contentType?.toMediaTypeOrNull(), contentLength)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
inner class CronetBodySource : Source {
|
inner class CronetBodySource : Source {
|
||||||
@@ -254,7 +342,9 @@ abstract class AbsCallBack(
|
|||||||
private var buffer = ByteBuffer.allocateDirect(32 * 1024)
|
private var buffer = ByteBuffer.allocateDirect(32 * 1024)
|
||||||
private var closed = false
|
private var closed = false
|
||||||
private val timeout = mCall.timeout().timeoutNanos()
|
private val timeout = mCall.timeout().timeoutNanos()
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
|
cancelJob?.cancel()
|
||||||
if (closed) {
|
if (closed) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -266,13 +356,12 @@ abstract class AbsCallBack(
|
|||||||
|
|
||||||
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
|
@Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
|
||||||
override fun read(sink: Buffer, byteCount: Long): Long {
|
override fun read(sink: Buffer, byteCount: Long): Long {
|
||||||
if (mCall.isCanceled()) {
|
if (canceled.get()) {
|
||||||
throw IOException("Request Canceled")
|
throw IOException("Request Canceled")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (closed) {
|
require(byteCount >= 0L) { "byteCount < 0: $byteCount" }
|
||||||
throw IOException("Source Closed")
|
check(!closed) { "closed" }
|
||||||
}
|
|
||||||
|
|
||||||
if (finished.get()) {
|
if (finished.get()) {
|
||||||
return -1
|
return -1
|
||||||
@@ -287,7 +376,7 @@ abstract class AbsCallBack(
|
|||||||
val result = callbackResults.poll(timeout, TimeUnit.NANOSECONDS)
|
val result = callbackResults.poll(timeout, TimeUnit.NANOSECONDS)
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
request?.cancel()
|
request?.cancel()
|
||||||
throw IOException("Request Timeout")
|
throw IOException("Body Read Timeout")
|
||||||
}
|
}
|
||||||
|
|
||||||
return when (result.callbackStep) {
|
return when (result.callbackStep) {
|
||||||
|
|||||||
Reference in New Issue
Block a user