Use Apache5 as fallback HTTP engine (#1752)
* Use Apache5 as fallback HTTP engine * Fix R8 * Depolymorphism CronetEngine context * Drop Conscrypt, don't enforce anything * Limit to 1 io thread count * Fix H2 support below A10 --------- Co-authored-by: Dude so hot <djohn@fbi.gov>
This commit is contained in:
1 parent
2bb387eeaf
commit
c3b60f1eac
9 files changed
+163
-51
No files matched your search
@@ -136,6 +136,9 @@ android {
|
||||
dex {
|
||||
useLegacyPackaging = false
|
||||
}
|
||||
resources {
|
||||
excludes += "META-INF/DEPENDENCIES"
|
||||
}
|
||||
}
|
||||
|
||||
dependenciesInfo.includeInApk = false
|
||||
@@ -245,7 +248,7 @@ dependencies {
|
||||
|
||||
implementation(libs.telephoto.zoomable)
|
||||
|
||||
implementation(libs.ktor.client.core)
|
||||
implementation(libs.ktor.client.apache5)
|
||||
|
||||
implementation(libs.bundles.kotlinx.serialization)
|
||||
|
||||
@@ -255,8 +258,6 @@ dependencies {
|
||||
|
||||
coreLibraryDesugaring(libs.desugar)
|
||||
|
||||
implementation(libs.cronet.embedded)
|
||||
|
||||
implementation(libs.androidx.profileinstaller)
|
||||
"baselineProfile"(project(":benchmark"))
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ import com.hippo.ehviewer.coil.HardwareBitmapInterceptor
|
||||
import com.hippo.ehviewer.coil.MapExtraInfoInterceptor
|
||||
import com.hippo.ehviewer.coil.MergeInterceptor
|
||||
import com.hippo.ehviewer.coil.QrCodeInterceptor
|
||||
import com.hippo.ehviewer.cronet.cronetHttpClient
|
||||
import com.hippo.ehviewer.dailycheck.checkDawn
|
||||
import com.hippo.ehviewer.dao.SearchDatabase
|
||||
import com.hippo.ehviewer.download.DownloadManager
|
||||
@@ -51,6 +50,7 @@ import com.hippo.ehviewer.download.DownloadsFilterMode
|
||||
import com.hippo.ehviewer.ktbuilder.diskCache
|
||||
import com.hippo.ehviewer.ktbuilder.imageLoader
|
||||
import com.hippo.ehviewer.ktor.Cronet
|
||||
import com.hippo.ehviewer.ktor.configureClient
|
||||
import com.hippo.ehviewer.ui.keepNoMediaFileStatus
|
||||
import com.hippo.ehviewer.ui.lockObserver
|
||||
import com.hippo.ehviewer.ui.screen.detailCache
|
||||
@@ -64,11 +64,13 @@ import com.hippo.ehviewer.util.OSUtils
|
||||
import com.hippo.ehviewer.util.isAtLeastO
|
||||
import com.hippo.ehviewer.util.isAtLeastP
|
||||
import com.hippo.ehviewer.util.isAtLeastS
|
||||
import com.hippo.ehviewer.util.isAtLeastSExtension7
|
||||
import eu.kanade.tachiyomi.util.lang.launchIO
|
||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import io.ktor.client.HttpClient
|
||||
import io.ktor.client.engine.apache5.Apache5
|
||||
import io.ktor.client.plugins.cookies.HttpCookies
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -193,12 +195,23 @@ class EhApplication :
|
||||
|
||||
companion object {
|
||||
val ktorClient by lazy {
|
||||
HttpClient(Cronet) {
|
||||
engine {
|
||||
client = cronetHttpClient
|
||||
if (isAtLeastSExtension7) {
|
||||
HttpClient(Cronet) {
|
||||
engine {
|
||||
configureClient()
|
||||
}
|
||||
install(HttpCookies) {
|
||||
storage = EhCookieStore
|
||||
}
|
||||
}
|
||||
install(HttpCookies) {
|
||||
storage = EhCookieStore
|
||||
} else {
|
||||
HttpClient(Apache5) {
|
||||
engine {
|
||||
configureClient()
|
||||
}
|
||||
install(HttpCookies) {
|
||||
storage = EhCookieStore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.hippo.ehviewer.cronet
|
||||
|
||||
import com.hippo.ehviewer.Settings
|
||||
import java.io.File
|
||||
import org.chromium.net.CronetEngine
|
||||
import splitties.init.appCtx
|
||||
|
||||
val cronetHttpClient: CronetEngine = CronetEngine.Builder(appCtx).apply {
|
||||
enableBrotli(true)
|
||||
setUserAgent(Settings.userAgent)
|
||||
|
||||
// Cache Quic hint only since the real cache mechanism should on Ktor layer
|
||||
val cache = File(appCtx.cacheDir, "http_cache").apply { mkdirs() }
|
||||
setStoragePath(cache.path)
|
||||
enableHttpCache(CronetEngine.Builder.HTTP_CACHE_DISK_NO_HTTP, 4096)
|
||||
|
||||
addQuicHint("e-hentai.org", 443, 443)
|
||||
addQuicHint("forums.e-hentai.org", 443, 443)
|
||||
addQuicHint("exhentai.org", 443, 443)
|
||||
}.build()
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.hippo.ehviewer.ktor
|
||||
|
||||
import com.hippo.ehviewer.util.isAtLeastQ
|
||||
import io.ktor.client.engine.apache5.Apache5EngineConfig
|
||||
import java.net.SocketAddress
|
||||
import moe.tarsin.kt.unreachable
|
||||
import org.apache.hc.client5.http.config.ConnectionConfig
|
||||
import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder
|
||||
import org.apache.hc.core5.concurrent.FutureCallback
|
||||
import org.apache.hc.core5.http.HttpHost
|
||||
import org.apache.hc.core5.http.nio.ssl.TlsStrategy
|
||||
import org.apache.hc.core5.http.ssl.TLS
|
||||
import org.apache.hc.core5.http2.ssl.ApplicationProtocol
|
||||
import org.apache.hc.core5.http2.ssl.H2TlsSupport
|
||||
import org.apache.hc.core5.net.NamedEndpoint
|
||||
import org.apache.hc.core5.reactor.IOReactorConfig
|
||||
import org.apache.hc.core5.reactor.ssl.SSLBufferMode
|
||||
import org.apache.hc.core5.reactor.ssl.TransportSecurityLayer
|
||||
import org.apache.hc.core5.ssl.SSLContexts
|
||||
import org.apache.hc.core5.util.Timeout
|
||||
|
||||
fun Apache5EngineConfig.configureClient() {
|
||||
customizeClient {
|
||||
setConnectionManager(
|
||||
PoolingAsyncClientConnectionManagerBuilder.create().apply {
|
||||
setMaxConnPerRoute(2)
|
||||
setDefaultConnectionConfig(
|
||||
ConnectionConfig.custom().apply {
|
||||
setConnectTimeout(Timeout.ofMilliseconds(connectTimeout))
|
||||
setSocketTimeout(Timeout.ofMilliseconds(socketTimeout.toLong()))
|
||||
}.build(),
|
||||
)
|
||||
setIOReactorConfig(
|
||||
IOReactorConfig.custom().apply {
|
||||
setIoThreadCount(1)
|
||||
}.build(),
|
||||
)
|
||||
if (!isAtLeastQ) {
|
||||
// TLS v1.3 support started from Android 10
|
||||
val tlsVersions = arrayOf(TLS.V_1_2.id)
|
||||
val protocols = arrayOf(ApplicationProtocol.HTTP_2.id, ApplicationProtocol.HTTP_1_1.id)
|
||||
val context = SSLContexts.createSystemDefault()
|
||||
setTlsStrategy(
|
||||
object : TlsStrategy {
|
||||
override fun upgrade(s: TransportSecurityLayer, h: HttpHost?, l: SocketAddress?, r: SocketAddress?, a: Any?, t: Timeout?) = unreachable()
|
||||
|
||||
override fun upgrade(session: TransportSecurityLayer, endpoint: NamedEndpoint?, attachment: Any?, timeout: Timeout?, callback: FutureCallback<TransportSecurityLayer?>) {
|
||||
session.startTls(
|
||||
context,
|
||||
endpoint,
|
||||
SSLBufferMode.STATIC,
|
||||
{ _, engine ->
|
||||
val params = engine.sslParameters
|
||||
params.protocols = tlsVersions
|
||||
H2TlsSupport.setApplicationProtocols(params, protocols)
|
||||
H2TlsSupport.setEnableRetransmissions(params, false)
|
||||
engine.sslParameters = params
|
||||
},
|
||||
{ _, _ -> null },
|
||||
timeout,
|
||||
callback,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}.build(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,12 @@
|
||||
@file:RequiresExtension(extension = Build.VERSION_CODES.S, version = 7)
|
||||
|
||||
package com.hippo.ehviewer.ktor
|
||||
|
||||
import io.ktor.client.engine.HttpClientEngine
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresExtension
|
||||
import io.ktor.client.engine.HttpClientEngineFactory
|
||||
import splitties.init.appCtx
|
||||
|
||||
object Cronet : HttpClientEngineFactory<CronetConfig> {
|
||||
override fun create(block: CronetConfig.() -> Unit): HttpClientEngine = CronetEngine(CronetConfig().apply(block))
|
||||
override fun create(block: CronetConfig.() -> Unit) = CronetEngine(CronetConfig(appCtx).apply(block))
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
@file:RequiresExtension(extension = Build.VERSION_CODES.S, version = 7)
|
||||
|
||||
package com.hippo.ehviewer.ktor
|
||||
|
||||
import android.content.Context
|
||||
import android.net.http.HttpEngine
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresExtension
|
||||
import io.ktor.client.engine.HttpClientEngineConfig
|
||||
import org.chromium.net.CronetEngine
|
||||
|
||||
class CronetConfig : HttpClientEngineConfig() {
|
||||
var client: CronetEngine? = null
|
||||
|
||||
fun config(context: Context, block: CronetEngine.Builder.() -> Unit) {
|
||||
client = CronetEngine.Builder(context).apply(block).build()
|
||||
}
|
||||
class CronetConfig(val context: Context) : HttpClientEngineConfig() {
|
||||
var config: HttpEngine.Builder.() -> Unit = {}
|
||||
}
|
||||
@@ -1,5 +1,15 @@
|
||||
@file:RequiresExtension(extension = Build.VERSION_CODES.S, version = 7)
|
||||
|
||||
package com.hippo.ehviewer.ktor
|
||||
|
||||
import android.net.http.HttpEngine
|
||||
import android.net.http.HttpException
|
||||
import android.net.http.UploadDataProvider
|
||||
import android.net.http.UploadDataSink
|
||||
import android.net.http.UrlRequest
|
||||
import android.net.http.UrlResponseInfo
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresExtension
|
||||
import io.ktor.client.engine.HttpClientEngineBase
|
||||
import io.ktor.client.engine.callContext
|
||||
import io.ktor.client.request.HttpRequestData
|
||||
@@ -27,17 +37,15 @@ import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.consumeEach
|
||||
import kotlinx.coroutines.job
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import org.chromium.net.CronetException
|
||||
import org.chromium.net.UrlRequest
|
||||
import org.chromium.net.UrlResponseInfo
|
||||
import org.chromium.net.apihelpers.UploadDataProviders
|
||||
|
||||
class CronetEngine(override val config: CronetConfig) : HttpClientEngineBase("Cronet") {
|
||||
// Limit thread to 1 since we are async & non-blocking
|
||||
override val dispatcher = Dispatchers.Default.limitedParallelism(1)
|
||||
private val executor = dispatcher.asExecutor()
|
||||
private val pool = DirectByteBufferPool(32)
|
||||
private val client = config.client ?: error("Cronet client is not configured")
|
||||
private val client by lazy {
|
||||
with(config) { HttpEngine.Builder(context).apply(config).build() }
|
||||
}
|
||||
|
||||
@InternalAPI
|
||||
override suspend fun execute(data: HttpRequestData) = executeHttpRequest(callContext(), data)
|
||||
@@ -48,7 +56,7 @@ class CronetEngine(override val config: CronetConfig) : HttpClientEngineBase("Cr
|
||||
) = suspendCancellableCoroutine { continuation ->
|
||||
val requestTime = GMTDate()
|
||||
|
||||
val callback = object : UrlRequest.Callback() {
|
||||
val callback = object : UrlRequest.Callback {
|
||||
val chunkChan = Channel<ByteBuffer>()
|
||||
override fun onRedirectReceived(request: UrlRequest, info: UrlResponseInfo, newLocationUrl: String) {
|
||||
continuation.resume(
|
||||
@@ -87,16 +95,18 @@ class CronetEngine(override val config: CronetConfig) : HttpClientEngineBase("Cr
|
||||
chunkChan.close()
|
||||
}
|
||||
|
||||
override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) {
|
||||
override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: HttpException) {
|
||||
if (continuation.isActive) {
|
||||
continuation.resumeWithException(error)
|
||||
} else {
|
||||
chunkChan.close(error)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCanceled(request: UrlRequest, info: UrlResponseInfo?) = Unit
|
||||
}
|
||||
|
||||
client.newUrlRequestBuilder(data.url.toString(), callback, executor).apply {
|
||||
client.newUrlRequestBuilder(data.url.toString(), executor, callback).apply {
|
||||
setHttpMethod(data.method.value)
|
||||
data.headers.flattenForEach { key, value -> addHeader(key, value) }
|
||||
data.body.contentType?.let { addHeader(HttpHeaders.ContentType, it.toString()) }
|
||||
@@ -117,7 +127,7 @@ private fun UrlResponseInfo.toHttpResponseData(
|
||||
statusCode = HttpStatusCode.fromValue(httpStatusCode),
|
||||
requestTime = requestTime,
|
||||
headers = headers {
|
||||
allHeaders.forEach { (key, value) ->
|
||||
headers.asMap.forEach { (key, value) ->
|
||||
appendAll(key, value)
|
||||
}
|
||||
},
|
||||
@@ -133,6 +143,17 @@ private fun UrlResponseInfo.toHttpResponseData(
|
||||
|
||||
private fun OutgoingContent.toUploadDataProvider() = when (this) {
|
||||
is OutgoingContent.NoContent -> null
|
||||
is OutgoingContent.ByteArrayContent -> UploadDataProviders.create(bytes())
|
||||
is OutgoingContent.ByteArrayContent -> object : UploadDataProvider() {
|
||||
val buffer = ByteBuffer.wrap(bytes()).slice()
|
||||
override fun getLength() = buffer.limit().toLong()
|
||||
override fun read(uploadDataSink: UploadDataSink, byteBuffer: ByteBuffer) {
|
||||
byteBuffer.put(buffer)
|
||||
uploadDataSink.onReadSucceeded(false)
|
||||
}
|
||||
override fun rewind(uploadDataSink: UploadDataSink) {
|
||||
buffer.position(0)
|
||||
uploadDataSink.onRewindSucceeded()
|
||||
}
|
||||
}
|
||||
else -> error("UnsupportedContentType $this")
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
@file:RequiresExtension(extension = Build.VERSION_CODES.S, version = 7)
|
||||
|
||||
package com.hippo.ehviewer.ktor
|
||||
|
||||
import android.net.http.HttpEngine
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresExtension
|
||||
import com.hippo.ehviewer.Settings
|
||||
import java.io.File
|
||||
|
||||
fun CronetConfig.configureClient() {
|
||||
config = {
|
||||
setEnableBrotli(true)
|
||||
setUserAgent(Settings.userAgent)
|
||||
|
||||
// Cache Quic hint only since the real cache mechanism should on Ktor layer
|
||||
val cache = File(context.cacheDir, "http_cache").apply { mkdirs() }
|
||||
setStoragePath(cache.path)
|
||||
setEnableHttpCache(HttpEngine.Builder.HTTP_CACHE_DISK_NO_HTTP, 4096)
|
||||
|
||||
addQuicHint("e-hentai.org", 443, 443)
|
||||
addQuicHint("forums.e-hentai.org", 443, 443)
|
||||
addQuicHint("exhentai.org", 443, 443)
|
||||
}
|
||||
}
|
||||
@@ -81,8 +81,6 @@ compose-destinations-compiler = { module = "io.github.raamcosta.compose-destinat
|
||||
|
||||
material-motion-core = "io.github.fornewid:material-motion-compose-core:2.0.1"
|
||||
|
||||
cronet-embedded = "org.chromium.net:cronet-embedded:119.6045.31"
|
||||
|
||||
desugar = "com.android.tools:desugar_jdk_libs_nio:2.1.2"
|
||||
|
||||
diff = "io.github.petertrr:kotlin-multiplatform-diff:0.7.0"
|
||||
@@ -93,7 +91,7 @@ kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.
|
||||
|
||||
ktlint = "com.pinterest:ktlint:1.4.0-kotlin-dev-SNAPSHOT"
|
||||
|
||||
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
|
||||
ktor-client-apache5 = { module = "io.ktor:ktor-client-apache5", version.ref = "ktor" }
|
||||
|
||||
logcat = "com.squareup.logcat:logcat:0.1"
|
||||
|
||||
|
||||
Reference in new issue
Block a user