6.7.0 - Alpha8 - http 模块请求相关方法支持 options.client 选项, 用于配置 OkHttpClient.Builder (如 followRedirects 等) (issue #454)

This commit is contained in:
SuperMonster003
2025-10-26 16:06:47 +08:00
parent 6d3395d6eb
commit bcee0d0c9a
4 changed files with 117 additions and 57 deletions

View File

@@ -15,6 +15,7 @@
"http 模块请求相关方法获取的 body 对象增加 stream/saveToFile/close 方法 _[`issue #452`](http://issues.autojs6.com/452)_", "http 模块请求相关方法获取的 body 对象增加 stream/saveToFile/close 方法 _[`issue #452`](http://issues.autojs6.com/452)_",
"http 模块请求相关方法支持缓存控制选项参数 (cacheBody/bodyCacheThresholdBytes)", "http 模块请求相关方法支持缓存控制选项参数 (cacheBody/bodyCacheThresholdBytes)",
"http 模块请求相关方法支持不安全选项参数 (isInsecure/insecure), 用于忽略证书相关异常 _[`issue #417`](http://issues.autojs6.com/417)_", "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))", "structuredClone 全局方法, 用于深拷贝 JavaScript 对象 (参阅 [MDN](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/structuredClone))",
"设置页面支持应用启动器图标设置选项 _[`issue #405`](http://issues.autojs6.com/405)_", "设置页面支持应用启动器图标设置选项 _[`issue #405`](http://issues.autojs6.com/405)_",
"Scrapers 工具 (run-scrapers.mjs) 用于自动更新 Gradle 构建脚本结构化数据/README 通用数据/README 模板数据等" "Scrapers 工具 (run-scrapers.mjs) 用于自动更新 Gradle 构建脚本结构化数据/README 通用数据/README 模板数据等"

View File

@@ -1,15 +1,8 @@
package org.autojs.autojs.core.http package org.autojs.autojs.core.http
import android.annotation.SuppressLint
import android.util.Log import android.util.Log
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.OkHttpClient 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. * Created by Stardust on Apr 11, 2018.
@@ -19,65 +12,25 @@ class MutableOkHttp : OkHttpClient() {
private var mTimeout = DEFAULT_TIMEOUT private var mTimeout = DEFAULT_TIMEOUT
private var mIsInsecure = DEFAULT_IS_INSECURE private var mIsInsecure = DEFAULT_IS_INSECURE
private var mOkHttpClient: OkHttpClient private lateinit var mOkHttpClient: OkHttpClient
private val mInterceptors: MutableList<Interceptor> = ArrayList() private val mInterceptors: MutableList<Interceptor> = ArrayList()
@JvmField @JvmField
var maxRetries = DEFAULT_MAX_RETRIES 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 { init {
mOkHttpClient = newClient(Builder())
mInterceptors.add(getRetryInterceptor()) mInterceptors.add(getRetryInterceptor())
muteClient(Builder())
} }
fun client() = mOkHttpClient 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 @Synchronized
fun muteClient(builder: Builder) { 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 -> private fun getRetryInterceptor() = Interceptor { chain ->
// @Reference to stackoverflow.com by SuperMonster003 on Apr 9, 2024. // @Reference to stackoverflow.com by SuperMonster003 on Apr 9, 2024.

View File

@@ -2,6 +2,7 @@
package org.autojs.autojs.runtime.api.augment.http package org.autojs.autojs.runtime.api.augment.http
import android.annotation.SuppressLint
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import okhttp3.Call import okhttp3.Call
import okhttp3.Callback import okhttp3.Callback
@@ -9,6 +10,7 @@ import okhttp3.FormBody
import okhttp3.MediaType import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.asRequestBody 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.coerceBoolean
import org.autojs.autojs.util.RhinoUtils.coerceIntNumber import org.autojs.autojs.util.RhinoUtils.coerceIntNumber
import org.autojs.autojs.util.RhinoUtils.coerceLongNumber 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.coerceObject
import org.autojs.autojs.util.RhinoUtils.coerceString import org.autojs.autojs.util.RhinoUtils.coerceString
import org.autojs.autojs.util.RhinoUtils.isUiThread import org.autojs.autojs.util.RhinoUtils.isUiThread
@@ -67,7 +70,14 @@ import org.mozilla.javascript.Undefined
import java.io.IOException import java.io.IOException
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import java.lang.reflect.Method
import java.net.URI 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") @Suppress("unused", "UNUSED_PARAMETER")
class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) { 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_GET = "GET"
private const val METHOD_POST = "POST" private const val METHOD_POST = "POST"
private const val KEY_CLIENT = "client"
private const val KEY_METHOD = "method" private const val KEY_METHOD = "method"
private const val KEY_CONTENT_TYPE = "contentType" private const val KEY_CONTENT_TYPE = "contentType"
private const val KEY_HEADERS = "headers" private const val KEY_HEADERS = "headers"
@@ -148,9 +159,10 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
val opt = coerceObject(options, newNativeObject()) val opt = coerceObject(options, newNativeObject())
scriptRuntime.http.okhttp.timeout = coerceLongNumber(opt.prop(KEY_TIMEOUT), DEFAULT_TIMEOUT) scriptRuntime.http.okhttp.apply {
scriptRuntime.http.okhttp.maxRetries = coerceIntNumber(opt.prop(KEY_MAX_RETRIES), DEFAULT_MAX_RETRIES) maxRetries = coerceIntNumber(opt.prop(KEY_MAX_RETRIES), DEFAULT_MAX_RETRIES)
scriptRuntime.http.okhttp.isInsecure = opt.inquire(listOf("isInsecure", "insecure"), ::coerceBoolean, false) applyOkHttpClientBuilder(opt)
}
val cacheBody = opt.inquire(listOf(KEY_CACHE_BODY), ::coerceBoolean, DEFAULT_CACHE_BODY) 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) 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 class ResponseWrapper(
private val scriptRuntime: ScriptRuntime, private val scriptRuntime: ScriptRuntime,
private val res: Response, private val res: Response,

View File

@@ -1,5 +1,5 @@
#Sun Oct 26 14:38:37 CST 2025 #Sun Oct 26 15:58:01 CST 2025
BUILD_TIME=1761460717246 BUILD_TIME=1761465481857
COMPILE_SDK_VERSION=36 COMPILE_SDK_VERSION=36
IMAGE_QUANT_CMAKE_VERSION=3.22.1 IMAGE_QUANT_CMAKE_VERSION=3.22.1
IMAGE_QUANT_NDK_VERSION=26.1.10909125 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 RAPID_OCR_OPENCV_MOBILE_VERSION=4.5.3
TARGET_SDK_VERSION=36 TARGET_SDK_VERSION=36
TARGET_SDK_VERSION_INRT=29 TARGET_SDK_VERSION_INRT=29
VERSION_BUILD=3440 VERSION_BUILD=3441
VERSION_NAME=6.7.0 Alpha8 VERSION_NAME=6.7.0 Alpha8
VSCODE_EXT_REQUIRED_VERSION=1.0.8 VSCODE_EXT_REQUIRED_VERSION=1.0.8