6.7.0 - Alpha8 - http 模块请求相关方法支持 options.client 选项, 用于配置 OkHttpClient.Builder (如 followRedirects 等) (issue #454)
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
"http 模块请求相关方法获取的 body 对象增加 stream/saveToFile/close 方法 _[`issue #452`](http://issues.autojs6.com/452)_",
|
||||
"http 模块请求相关方法支持缓存控制选项参数 (cacheBody/bodyCacheThresholdBytes)",
|
||||
"http 模块请求相关方法支持不安全选项参数 (isInsecure/insecure), 用于忽略证书相关异常 _[`issue #417`](http://issues.autojs6.com/417)_",
|
||||
"http 模块请求相关方法支持 options.client 选项, 用于配置 OkHttpClient.Builder (如 followRedirects 等) _[`issue #454`](http://issues.autojs6.com/454)_",
|
||||
"structuredClone 全局方法, 用于深拷贝 JavaScript 对象 (参阅 [MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/structuredClone))",
|
||||
"设置页面支持应用启动器图标设置选项 _[`issue #405`](http://issues.autojs6.com/405)_",
|
||||
"Scrapers 工具 (run-scrapers.mjs) 用于自动更新 Gradle 构建脚本结构化数据/README 通用数据/README 模板数据等"
|
||||
|
||||
@@ -1,15 +1,8 @@
|
||||
package org.autojs.autojs.core.http
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.util.Log
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.OkHttpClient
|
||||
import java.security.SecureRandom
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.TrustManager
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
/**
|
||||
* Created by Stardust on Apr 11, 2018.
|
||||
@@ -19,65 +12,25 @@ class MutableOkHttp : OkHttpClient() {
|
||||
|
||||
private var mTimeout = DEFAULT_TIMEOUT
|
||||
private var mIsInsecure = DEFAULT_IS_INSECURE
|
||||
private var mOkHttpClient: OkHttpClient
|
||||
private lateinit var mOkHttpClient: OkHttpClient
|
||||
private val mInterceptors: MutableList<Interceptor> = ArrayList()
|
||||
|
||||
@JvmField
|
||||
var maxRetries = DEFAULT_MAX_RETRIES
|
||||
|
||||
var timeout: Long
|
||||
get() = mTimeout
|
||||
set(timeout) {
|
||||
mTimeout = timeout
|
||||
muteClient()
|
||||
}
|
||||
|
||||
var isInsecure: Boolean
|
||||
get() = mIsInsecure
|
||||
set(isInsecure) {
|
||||
mIsInsecure = isInsecure
|
||||
muteClient()
|
||||
}
|
||||
|
||||
init {
|
||||
mOkHttpClient = newClient(Builder())
|
||||
mInterceptors.add(getRetryInterceptor())
|
||||
muteClient(Builder())
|
||||
}
|
||||
|
||||
fun client() = mOkHttpClient
|
||||
|
||||
private fun newClient(builder: Builder): OkHttpClient {
|
||||
mInterceptors.forEach { if (it !in builder.interceptors()) builder.addInterceptor(it) }
|
||||
return builder
|
||||
.readTimeout(timeout, TimeUnit.MILLISECONDS)
|
||||
.writeTimeout(timeout, TimeUnit.MILLISECONDS)
|
||||
.connectTimeout(timeout, TimeUnit.MILLISECONDS)
|
||||
.also { b ->
|
||||
if (!isInsecure) return@also
|
||||
@SuppressLint("CustomX509TrustManager")
|
||||
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
|
||||
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) = Unit
|
||||
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) = Unit
|
||||
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
|
||||
})
|
||||
val sslContext = SSLContext.getInstance("TLS").apply {
|
||||
init(null, trustAllCerts, SecureRandom())
|
||||
}
|
||||
val trustManager = trustAllCerts.first() as X509TrustManager
|
||||
b.sslSocketFactory(sslContext.socketFactory, trustManager)
|
||||
b.hostnameVerifier { _, _ -> true }
|
||||
}
|
||||
.build()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun muteClient(builder: Builder) {
|
||||
mOkHttpClient = newClient(builder)
|
||||
mInterceptors.forEach { if (it !in builder.interceptors()) builder.addInterceptor(it) }
|
||||
mOkHttpClient = builder.build()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
private fun muteClient() = muteClient(mOkHttpClient.newBuilder())
|
||||
|
||||
private fun getRetryInterceptor() = Interceptor { chain ->
|
||||
|
||||
// @Reference to stackoverflow.com by SuperMonster003 on Apr 9, 2024.
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
package org.autojs.autojs.runtime.api.augment.http
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.webkit.MimeTypeMap
|
||||
import okhttp3.Call
|
||||
import okhttp3.Callback
|
||||
@@ -9,6 +10,7 @@ import okhttp3.FormBody
|
||||
import okhttp3.MediaType
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.MultipartBody
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.RequestBody
|
||||
import okhttp3.RequestBody.Companion.asRequestBody
|
||||
@@ -47,6 +49,7 @@ import org.autojs.autojs.util.RhinoUtils.UNDEFINED
|
||||
import org.autojs.autojs.util.RhinoUtils.coerceBoolean
|
||||
import org.autojs.autojs.util.RhinoUtils.coerceIntNumber
|
||||
import org.autojs.autojs.util.RhinoUtils.coerceLongNumber
|
||||
import org.autojs.autojs.util.RhinoUtils.coerceNumber
|
||||
import org.autojs.autojs.util.RhinoUtils.coerceObject
|
||||
import org.autojs.autojs.util.RhinoUtils.coerceString
|
||||
import org.autojs.autojs.util.RhinoUtils.isUiThread
|
||||
@@ -67,7 +70,14 @@ import org.mozilla.javascript.Undefined
|
||||
import java.io.IOException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.lang.reflect.Method
|
||||
import java.net.URI
|
||||
import java.security.SecureRandom
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.TrustManager
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
@Suppress("unused", "UNUSED_PARAMETER")
|
||||
class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
|
||||
@@ -90,6 +100,7 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
|
||||
private const val METHOD_GET = "GET"
|
||||
private const val METHOD_POST = "POST"
|
||||
|
||||
private const val KEY_CLIENT = "client"
|
||||
private const val KEY_METHOD = "method"
|
||||
private const val KEY_CONTENT_TYPE = "contentType"
|
||||
private const val KEY_HEADERS = "headers"
|
||||
@@ -148,9 +159,10 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
|
||||
|
||||
val opt = coerceObject(options, newNativeObject())
|
||||
|
||||
scriptRuntime.http.okhttp.timeout = coerceLongNumber(opt.prop(KEY_TIMEOUT), DEFAULT_TIMEOUT)
|
||||
scriptRuntime.http.okhttp.maxRetries = coerceIntNumber(opt.prop(KEY_MAX_RETRIES), DEFAULT_MAX_RETRIES)
|
||||
scriptRuntime.http.okhttp.isInsecure = opt.inquire(listOf("isInsecure", "insecure"), ::coerceBoolean, false)
|
||||
scriptRuntime.http.okhttp.apply {
|
||||
maxRetries = coerceIntNumber(opt.prop(KEY_MAX_RETRIES), DEFAULT_MAX_RETRIES)
|
||||
applyOkHttpClientBuilder(opt)
|
||||
}
|
||||
|
||||
val cacheBody = opt.inquire(listOf(KEY_CACHE_BODY), ::coerceBoolean, DEFAULT_CACHE_BODY)
|
||||
val cacheThreshold = coerceLongNumber(opt.prop(KEY_BODY_CACHE_THRESHOLD_BYTES), DEFAULT_BODY_CACHE_THRESHOLD_BYTES)
|
||||
@@ -276,6 +288,100 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun MutableOkHttp.applyOkHttpClientBuilder(opt: NativeObject) {
|
||||
val clientProp = opt.prop(KEY_CLIENT)
|
||||
require(clientProp is NativeObject?) { "Argument \"client\" ${clientProp.jsBrief()} for http.request must be a JavaScript Object" }
|
||||
clientProp ?: return
|
||||
|
||||
val timeout = coerceLongNumber(opt.prop(KEY_TIMEOUT), DEFAULT_TIMEOUT)
|
||||
val isInsecure = opt.inquire(listOf("isInsecure", "insecure"), ::coerceBoolean, false)
|
||||
|
||||
val builder = this.client().newBuilder()
|
||||
.readTimeout(timeout, TimeUnit.MILLISECONDS)
|
||||
.writeTimeout(timeout, TimeUnit.MILLISECONDS)
|
||||
.connectTimeout(timeout, TimeUnit.MILLISECONDS)
|
||||
|
||||
val builderClass = OkHttpClient.Builder::class.java
|
||||
|
||||
fun coerceArg(paramType: Class<*>, value: Any?) = when {
|
||||
paramType == java.lang.Boolean.TYPE || paramType == java.lang.Boolean::class.java -> coerceBoolean(value, false)
|
||||
paramType == java.lang.Long.TYPE || paramType == java.lang.Long::class.java -> coerceLongNumber(value, 0L)
|
||||
paramType == Integer.TYPE || paramType == Integer::class.java -> coerceIntNumber(value, 0)
|
||||
paramType == java.lang.Double.TYPE || paramType == java.lang.Double::class.java -> coerceNumber(value, 0.0)
|
||||
paramType == String::class.java -> coerceString(value, "")
|
||||
paramType == TimeUnit::class.java -> when (value) {
|
||||
is TimeUnit -> value
|
||||
is String -> runCatching { TimeUnit.valueOf(value.trim().uppercase()) }.getOrElse {
|
||||
throw WrappedIllegalArgumentException("Invalid TimeUnit string: $value")
|
||||
}
|
||||
else -> throw WrappedIllegalArgumentException("Invalid TimeUnit argument: ${value.jsBrief()}")
|
||||
}
|
||||
// Java object: Proxy/Dispatcher/ConnectionPool/Authenticator/SSLSocketFactory/...
|
||||
paramType.isInstance(value) -> value
|
||||
// Let reflection verify the type matching by itself.
|
||||
// zh-CN: 让反射自行校验类型是否匹配.
|
||||
else -> value
|
||||
}
|
||||
|
||||
fun findCandidateMethods(name: String): List<Method> {
|
||||
return builderClass.methods.filter { it.name == name && it.declaringClass == builderClass }
|
||||
}
|
||||
|
||||
clientProp.forEach { entry ->
|
||||
val (rawKey, rawVal) = entry
|
||||
val methodName = coerceString(rawKey, "").trim()
|
||||
if (methodName.isEmpty()) return@forEach
|
||||
|
||||
val candidates = findCandidateMethods(methodName)
|
||||
require(candidates.isNotEmpty()) {
|
||||
"No such Builder method: $methodName on ${builderClass.name}"
|
||||
}
|
||||
|
||||
val zeroArg = candidates.firstOrNull { it.parameterCount == 0 }
|
||||
require(zeroArg == null) {
|
||||
"Builder method \"$methodName\" with 0 parameter is not allowed to be invoked via client options"
|
||||
}
|
||||
val twoArgs = candidates.firstOrNull { it.parameterCount == 2 }
|
||||
if (twoArgs != null && rawVal is List<*> && rawVal.size == 2) {
|
||||
val args2 = arrayOf(coerceArg(twoArgs.parameterTypes[0], rawVal[0]), coerceArg(twoArgs.parameterTypes[1], rawVal[1]))
|
||||
twoArgs.invoke(builder, args2[0], args2[1])
|
||||
return@forEach
|
||||
}
|
||||
|
||||
val oneArg = candidates.firstOrNull { it.parameterCount == 1 }
|
||||
if (oneArg != null) {
|
||||
val paramTypes = oneArg.parameterTypes
|
||||
val arg0 = coerceArg(paramTypes[0], rawVal)
|
||||
oneArg.invoke(builder, arg0)
|
||||
return@forEach
|
||||
}
|
||||
|
||||
val supported = candidates.joinToString { "(${it.parameterTypes.joinToString { p -> p.simpleName }})" }
|
||||
throw WrappedIllegalArgumentException(
|
||||
"Builder method \"$methodName\" is not invokable with 1 or 2 parameters via client options. Supported overloads: $supported"
|
||||
)
|
||||
}
|
||||
|
||||
if (isInsecure) {
|
||||
@SuppressLint("CustomX509TrustManager")
|
||||
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
|
||||
override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) = Unit
|
||||
override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) = Unit
|
||||
override fun getAcceptedIssuers(): Array<X509Certificate> = arrayOf()
|
||||
})
|
||||
val sslContext = SSLContext.getInstance("TLS").apply {
|
||||
init(null, trustAllCerts, SecureRandom())
|
||||
}
|
||||
val trustManager = trustAllCerts.first() as X509TrustManager
|
||||
builder.sslSocketFactory(sslContext.socketFactory, trustManager)
|
||||
builder.hostnameVerifier { _, _ -> true }
|
||||
}
|
||||
|
||||
// Apply the new Builder to the internal client.
|
||||
// zh-CN: 应用新的 Builder 到内部客户端.
|
||||
muteClient(builder)
|
||||
}
|
||||
|
||||
private class ResponseWrapper(
|
||||
private val scriptRuntime: ScriptRuntime,
|
||||
private val res: Response,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#Sun Oct 26 14:38:37 CST 2025
|
||||
BUILD_TIME=1761460717246
|
||||
#Sun Oct 26 15:58:01 CST 2025
|
||||
BUILD_TIME=1761465481857
|
||||
COMPILE_SDK_VERSION=36
|
||||
IMAGE_QUANT_CMAKE_VERSION=3.22.1
|
||||
IMAGE_QUANT_NDK_VERSION=26.1.10909125
|
||||
@@ -27,6 +27,6 @@ RAPID_OCR_OPENCV_MOBILE_LABEL_VERSION=13
|
||||
RAPID_OCR_OPENCV_MOBILE_VERSION=4.5.3
|
||||
TARGET_SDK_VERSION=36
|
||||
TARGET_SDK_VERSION_INRT=29
|
||||
VERSION_BUILD=3440
|
||||
VERSION_BUILD=3441
|
||||
VERSION_NAME=6.7.0 Alpha8
|
||||
VSCODE_EXT_REQUIRED_VERSION=1.0.8
|
||||
|
||||
Reference in New Issue
Block a user