6.7.0 - Alpha14 - 新增 http.put/del(ete)/head 方法; 新增 http.xxxAsync 方法, 用于异步网络请求 (issue #423)

This commit is contained in:
SuperMonster003
2026-01-13 00:10:03 +08:00
parent 9e1d3b65cc
commit 62504d6608
4 changed files with 325 additions and 20 deletions

View File

@@ -32,9 +32,11 @@ import org.autojs.autojs.util.RhinoUtils.coerceIntNumber
import org.autojs.autojs.util.RhinoUtils.coerceLongNumber
import org.autojs.autojs.util.RhinoUtils.coerceObject
import org.autojs.autojs.util.RhinoUtils.coerceString
import org.autojs.autojs.util.RhinoUtils.handleAsyncOperation
import org.autojs.autojs.util.RhinoUtils.isUiThread
import org.autojs.autojs.util.RhinoUtils.js_json_stringify
import org.autojs.autojs.util.RhinoUtils.newNativeObject
import org.autojs.autojs.util.RhinoUtils.withRhinoContext
import org.mozilla.javascript.BaseFunction
import org.mozilla.javascript.Context
import org.mozilla.javascript.NativeObject
@@ -53,10 +55,23 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
override val selfAssignmentFunctions = listOf(
::buildRequest.name,
::request.name,
::requestAsync.name,
::get.name,
::getAsync.name,
::head.name,
::headAsync.name,
::post.name,
::postAsync.name,
::postJson.name,
::postJsonAsync.name,
::postMultipart.name,
::postMultipartAsync.name,
"put",
::putAsync.name,
::delete.name,
::del.name,
::deleteAsync.name,
::delAsync.name,
)
companion object : Augmentable() {
@@ -74,7 +89,10 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
private const val KEY_BODY_CACHE_THRESHOLD_BYTES = "bodyCacheThresholdBytes"
private const val METHOD_GET = "GET"
private const val METHOD_HEAD = "HEAD"
private const val METHOD_POST = "POST"
private const val METHOD_PUT = "PUT"
private const val METHOD_DELETE = "DELETE"
private val DEFAULT_CONTENT_TYPE = Mime.APPLICATION_X_WWW_FORM_URLENCODED
@@ -117,31 +135,22 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
@JvmOverloads
@RhinoFunctionBody
fun requestRhinoWithRuntime(scriptRuntime: ScriptRuntime, url: Any?, options: Any? = null, callback: Any? = null): Any {
val cont: Creator? = when {
callback.isJsFunction() && isUiThread() && Continuation.isEnabled(scriptRuntime) -> Continuation.createRhinoWithRuntime(scriptRuntime)
else -> null
}
val opt = coerceObject(options, newNativeObject())
scriptRuntime.http.okhttp.apply {
maxRetries = coerceIntNumber(opt.prop(KEY_MAX_RETRIES), DEFAULT_MAX_RETRIES)
applyOkHttpClientBuilder(opt)
}
val cacheBody = opt.inquire(KEY_CACHE_BODY, ::coerceBoolean, DEFAULT_CACHE_BODY)
val cacheThreshold = coerceLongNumber(opt.prop(KEY_BODY_CACHE_THRESHOLD_BYTES), DEFAULT_BODY_CACHE_THRESHOLD_BYTES)
val newCall = scriptRuntime.http.client().newCall(buildRequestRhinoWithRuntime(scriptRuntime, url, options))
val prepared = prepareRequest(scriptRuntime, url, options)
if (!callback.isJsFunction() && cont == null) {
return ResponseWrapper(scriptRuntime, newCall.execute(), cacheBody, cacheThreshold).wrap()
return wrapResponse(scriptRuntime, prepared.call.execute(), prepared.cacheBody, prepared.cacheThreshold)
}
scriptRuntime.loopers.waitWhenIdle(true)
newCall.enqueue(object : Callback {
prepared.call.enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
val wrappedResponse = ResponseWrapper(scriptRuntime, response, cacheBody, cacheThreshold).wrap()
val wrappedResponse = wrapResponse(scriptRuntime, response, prepared.cacheBody, prepared.cacheThreshold)
cont?.resume(wrappedResponse)
if (callback is BaseFunction) {
scriptRuntime.bridges.call(callback, callback, arrayOf(wrappedResponse, null))
@@ -157,10 +166,49 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
scriptRuntime.loopers.waitWhenIdle(false)
}
})
cont?.await()
return UNDEFINED
}
@JvmStatic
@RhinoRuntimeFunctionInterface
fun requestAsync(scriptRuntime: ScriptRuntime, args: Array<out Any?>): NativeObject = ensureArgumentsLengthInRange(args, 1..3) {
val (url, options, callback) = it
requestAsyncRhinoWithRuntime(scriptRuntime, url, options, callback)
}
@JvmStatic
@JvmOverloads
@RhinoFunctionBody
fun requestAsyncRhinoWithRuntime(scriptRuntime: ScriptRuntime, url: Any?, options: Any? = null, callback: Any? = null): NativeObject {
val prepared = prepareRequest(scriptRuntime, url, options)
return handleAsyncOperation(
scriptRuntime,
operation = {
// Execute network call in background thread.
// zh-CN: 在后台线程执行网络请求.
prepared.call.execute()
},
uiMapper = { response ->
// Wrap response into Rhino object on UI thread with Rhino Context.
// zh-CN: 在 UI 线程进入 Rhino Context, 将 Response 包装为 Rhino 对象.
withRhinoContext {
val wrapped = wrapResponse(scriptRuntime, response, prepared.cacheBody, prepared.cacheThreshold)
// Optional: if user provided callback, call it on UI thread.
// zh-CN: 可选: 如果用户提供了 callback, 则在 UI 线程调用.
if (callback is BaseFunction) {
scriptRuntime.bridges.call(callback, callback, arrayOf(wrapped, null))
}
wrapped
}
},
)
}
@JvmStatic
@RhinoRuntimeFunctionInterface
fun get(scriptRuntime: ScriptRuntime, args: Array<out Any?>): Any = ensureArgumentsLengthInRange(args, 1..3) {
@@ -168,6 +216,57 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
getRhinoWithRuntime(scriptRuntime, url, options, callback)
}
@JvmStatic
@RhinoRuntimeFunctionInterface
fun head(scriptRuntime: ScriptRuntime, args: Array<out Any?>): Any = ensureArgumentsLengthInRange(args, 1..3) {
val (url, options, callback) = it
headRhinoWithRuntime(scriptRuntime, url, options, callback)
}
@JvmStatic
@RhinoRuntimeFunctionInterface
fun headAsync(scriptRuntime: ScriptRuntime, args: Array<out Any?>): NativeObject = ensureArgumentsLengthInRange(args, 1..3) {
val (url, options, callback) = it
headAsyncRhinoWithRuntime(scriptRuntime, url, options, callback)
}
@JvmStatic
@JvmOverloads
@RhinoFunctionBody
fun headRhinoWithRuntime(scriptRuntime: ScriptRuntime, url: Any?, options: Any? = null, callback: Any? = null): Any {
val niceOptions = if (options.isJsNullish()) newNativeObject() else options
require(niceOptions is NativeObject) { "Argument \"options\" ${options.jsBrief()} for http.head must be a JavaScript Object" }
put(niceOptions, KEY_METHOD to METHOD_HEAD)
return requestRhinoWithRuntime(scriptRuntime, url, niceOptions, callback)
}
@JvmStatic
@JvmOverloads
@RhinoFunctionBody
fun headAsyncRhinoWithRuntime(scriptRuntime: ScriptRuntime, url: Any?, options: Any? = null, callback: Any? = null): NativeObject {
val niceOptions = if (options.isJsNullish()) newNativeObject() else options
require(niceOptions is NativeObject) { "Argument \"options\" ${options.jsBrief()} for http.headAsync must be a JavaScript Object" }
put(niceOptions, KEY_METHOD to METHOD_HEAD)
return requestAsyncRhinoWithRuntime(scriptRuntime, url, niceOptions, callback)
}
@JvmStatic
@RhinoRuntimeFunctionInterface
fun getAsync(scriptRuntime: ScriptRuntime, args: Array<out Any?>): NativeObject = ensureArgumentsLengthInRange(args, 1..3) {
val (url, options, callback) = it
getAsyncRhinoWithRuntime(scriptRuntime, url, options, callback)
}
@JvmStatic
@JvmOverloads
@RhinoFunctionBody
fun getAsyncRhinoWithRuntime(scriptRuntime: ScriptRuntime, url: Any?, options: Any? = null, callback: Any? = null): NativeObject {
val niceOptions = if (options.isJsNullish()) newNativeObject() else options
require(niceOptions is NativeObject) { "Argument \"options\" ${options.jsBrief()} for http.getAsync must be a JavaScript Object" }
put(niceOptions, KEY_METHOD to METHOD_GET)
return requestAsyncRhinoWithRuntime(scriptRuntime, url, niceOptions, callback)
}
@JvmStatic
@JvmOverloads
@RhinoFunctionBody
@@ -178,6 +277,96 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
return requestRhinoWithRuntime(scriptRuntime, url, niceOptions, callback)
}
@JvmStatic
@RhinoRuntimeFunctionInterface
fun put(scriptRuntime: ScriptRuntime, args: Array<out Any?>): Any = ensureArgumentsLengthInRange(args, 1..4) {
val (url, data, options, callback) = it
putRhinoWithRuntime(scriptRuntime, url, data, options, callback)
}
@JvmStatic
@RhinoRuntimeFunctionInterface
fun putAsync(scriptRuntime: ScriptRuntime, args: Array<out Any?>): NativeObject = ensureArgumentsLengthInRange(args, 1..4) {
val (url, data, options, callback) = it
putAsyncRhinoWithRuntime(scriptRuntime, url, data, options, callback)
}
@JvmStatic
@JvmOverloads
@RhinoFunctionBody
fun putRhinoWithRuntime(scriptRuntime: ScriptRuntime, url: Any?, data: Any? = null, options: Any? = null, callback: Any? = null): Any {
val niceOptions = if (options.isJsNullish()) newNativeObject() else options
require(niceOptions is NativeObject) { "Argument \"options\" ${options.jsBrief()} for http.put must be a JavaScript Object" }
put(niceOptions, KEY_METHOD to METHOD_PUT)
putIfAbsent(niceOptions, KEY_CONTENT_TYPE to DEFAULT_CONTENT_TYPE)
fillPostData(niceOptions, data)
return requestRhinoWithRuntime(scriptRuntime, url, niceOptions, callback)
}
@JvmStatic
@JvmOverloads
@RhinoFunctionBody
fun putAsyncRhinoWithRuntime(scriptRuntime: ScriptRuntime, url: Any?, data: Any? = null, options: Any? = null, callback: Any? = null): NativeObject {
val niceOptions = if (options.isJsNullish()) newNativeObject() else options
require(niceOptions is NativeObject) { "Argument \"options\" ${options.jsBrief()} for http.putAsync must be a JavaScript Object" }
put(niceOptions, KEY_METHOD to METHOD_PUT)
putIfAbsent(niceOptions, KEY_CONTENT_TYPE to DEFAULT_CONTENT_TYPE)
fillPostData(niceOptions, data)
return requestAsyncRhinoWithRuntime(scriptRuntime, url, niceOptions, callback)
}
@JvmStatic
@RhinoRuntimeFunctionInterface
fun delete(scriptRuntime: ScriptRuntime, args: Array<out Any?>): Any = ensureArgumentsLengthInRange(args, 1..4) {
val (url, data, options, callback) = it
deleteRhinoWithRuntime(scriptRuntime, url, data, options, callback)
}
@JvmStatic
@RhinoRuntimeFunctionInterface
fun del(scriptRuntime: ScriptRuntime, args: Array<out Any?>): Any = ensureArgumentsLengthInRange(args, 1..4) {
val (url, data, options, callback) = it
deleteRhinoWithRuntime(scriptRuntime, url, data, options, callback)
}
@JvmStatic
@RhinoRuntimeFunctionInterface
fun deleteAsync(scriptRuntime: ScriptRuntime, args: Array<out Any?>): NativeObject = ensureArgumentsLengthInRange(args, 1..4) {
val (url, data, options, callback) = it
deleteAsyncRhinoWithRuntime(scriptRuntime, url, data, options, callback)
}
@JvmStatic
@RhinoRuntimeFunctionInterface
fun delAsync(scriptRuntime: ScriptRuntime, args: Array<out Any?>): NativeObject = ensureArgumentsLengthInRange(args, 1..4) {
val (url, data, options, callback) = it
deleteAsyncRhinoWithRuntime(scriptRuntime, url, data, options, callback)
}
@JvmStatic
@JvmOverloads
@RhinoFunctionBody
fun deleteRhinoWithRuntime(scriptRuntime: ScriptRuntime, url: Any?, data: Any? = null, options: Any? = null, callback: Any? = null): Any {
val niceOptions = if (options.isJsNullish()) newNativeObject() else options
require(niceOptions is NativeObject) { "Argument \"options\" ${options.jsBrief()} for http.delete must be a JavaScript Object" }
put(niceOptions, KEY_METHOD to METHOD_DELETE)
putIfAbsent(niceOptions, KEY_CONTENT_TYPE to DEFAULT_CONTENT_TYPE)
fillPostData(niceOptions, data)
return requestRhinoWithRuntime(scriptRuntime, url, niceOptions, callback)
}
@JvmStatic
@JvmOverloads
@RhinoFunctionBody
fun deleteAsyncRhinoWithRuntime(scriptRuntime: ScriptRuntime, url: Any?, data: Any? = null, options: Any? = null, callback: Any? = null): NativeObject {
val niceOptions = if (options.isJsNullish()) newNativeObject() else options
require(niceOptions is NativeObject) { "Argument \"options\" ${options.jsBrief()} for http.deleteAsync must be a JavaScript Object" }
put(niceOptions, KEY_METHOD to METHOD_DELETE)
putIfAbsent(niceOptions, KEY_CONTENT_TYPE to DEFAULT_CONTENT_TYPE)
fillPostData(niceOptions, data)
return requestAsyncRhinoWithRuntime(scriptRuntime, url, niceOptions, callback)
}
@JvmStatic
@RhinoRuntimeFunctionInterface
fun post(scriptRuntime: ScriptRuntime, args: Array<out Any?>): Any = ensureArgumentsLengthInRange(args, 1..4) {
@@ -185,6 +374,25 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
postRhinoWithRuntime(scriptRuntime, url, data, options, callback)
}
@JvmStatic
@RhinoRuntimeFunctionInterface
fun postAsync(scriptRuntime: ScriptRuntime, args: Array<out Any?>): NativeObject = ensureArgumentsLengthInRange(args, 1..4) {
val (url, data, options, callback) = it
postAsyncRhinoWithRuntime(scriptRuntime, url, data, options, callback)
}
@JvmStatic
@JvmOverloads
@RhinoFunctionBody
fun postAsyncRhinoWithRuntime(scriptRuntime: ScriptRuntime, url: Any?, data: Any? = null, options: Any? = null, callback: Any? = null): NativeObject {
val niceOptions = if (options.isJsNullish()) newNativeObject() else options
require(niceOptions is NativeObject) { "Argument \"options\" ${options.jsBrief()} for http.postAsync must be a JavaScript Object" }
put(niceOptions, KEY_METHOD to METHOD_POST)
putIfAbsent(niceOptions, KEY_CONTENT_TYPE to DEFAULT_CONTENT_TYPE)
fillPostData(niceOptions, data)
return requestAsyncRhinoWithRuntime(scriptRuntime, url, niceOptions, callback)
}
@JvmStatic
@JvmOverloads
@RhinoFunctionBody
@@ -204,6 +412,23 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
postJsonRhinoWithRuntime(scriptRuntime, url, data, options, callback)
}
@JvmStatic
@RhinoRuntimeFunctionInterface
fun postJsonAsync(scriptRuntime: ScriptRuntime, args: Array<out Any?>): NativeObject = ensureArgumentsLengthInRange(args, 1..4) {
val (url, data, options, callback) = it
postJsonAsyncRhinoWithRuntime(scriptRuntime, url, data, options, callback)
}
@JvmStatic
@JvmOverloads
@RhinoFunctionBody
fun postJsonAsyncRhinoWithRuntime(scriptRuntime: ScriptRuntime, url: Any?, data: Any?, options: Any? = null, callback: Any? = null): NativeObject {
val niceOptions = if (options.isJsNullish()) newNativeObject() else options
require(niceOptions is NativeObject) { "Argument \"options\" ${options.jsBrief()} for http.postJsonAsync must be a JavaScript Object" }
put(niceOptions, KEY_CONTENT_TYPE to Mime.APPLICATION_JSON)
return postAsyncRhinoWithRuntime(scriptRuntime, url, data, niceOptions, callback)
}
@JvmStatic
@JvmOverloads
@RhinoFunctionBody
@@ -221,6 +446,27 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
postMultipartRhinoWithRuntime(scriptRuntime, url, files, options, callback)
}
@JvmStatic
@RhinoRuntimeFunctionInterface
fun postMultipartAsync(scriptRuntime: ScriptRuntime, args: Array<out Any?>): NativeObject = ensureArgumentsLengthInRange(args, 1..4) {
val (url, files, options, callback) = it
postMultipartAsyncRhinoWithRuntime(scriptRuntime, url, files, options, callback)
}
@JvmStatic
@JvmOverloads
@RhinoFunctionBody
fun postMultipartAsyncRhinoWithRuntime(scriptRuntime: ScriptRuntime, url: Any?, files: Any?, options: Any? = null, callback: Any? = null): NativeObject {
val niceOptions = if (options.isJsNullish()) newNativeObject() else options
require(niceOptions is NativeObject) { "Argument \"options\" ${options.jsBrief()} for http.postMultipartAsync must be a JavaScript Object" }
listOf(
KEY_METHOD to METHOD_POST,
KEY_CONTENT_TYPE to Mime.MULTIPART_FORM_DATA,
KEY_FILES to files,
).let { list -> put(niceOptions, list) }
return requestAsyncRhinoWithRuntime(scriptRuntime, url, niceOptions, callback)
}
@JvmStatic
@JvmOverloads
@RhinoFunctionBody
@@ -253,6 +499,49 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
}
}
private data class PreparedRequest(
val call: Call,
val cacheBody: Boolean,
val cacheThreshold: Long,
)
private fun prepareRequest(scriptRuntime: ScriptRuntime, url: Any?, options: Any?): PreparedRequest {
// Normalize options as a NativeObject.
// zh-CN: 将 options 规范化为 NativeObject.
val opt = coerceObject(options, newNativeObject())
// Apply OkHttp runtime configs (retries/client builder).
// zh-CN: 应用 OkHttp 运行时配置 (重试/Client Builder).
scriptRuntime.http.okhttp.apply {
maxRetries = coerceIntNumber(opt.prop(KEY_MAX_RETRIES), DEFAULT_MAX_RETRIES)
applyOkHttpClientBuilder(opt)
}
// Cache policy for response body wrapper.
// zh-CN: Response body wrapper 的缓存策略.
val cacheBody = opt.inquire(KEY_CACHE_BODY, ::coerceBoolean, DEFAULT_CACHE_BODY)
val cacheThreshold = coerceLongNumber(opt.prop(KEY_BODY_CACHE_THRESHOLD_BYTES), DEFAULT_BODY_CACHE_THRESHOLD_BYTES)
// Build request with normalized options and create call.
// zh-CN: 使用规范化后的 options 构建 Request 并创建 Call.
val request = buildRequestRhinoWithRuntime(scriptRuntime, url, opt)
val call = scriptRuntime.http.client().newCall(request)
return PreparedRequest(
call = call,
cacheBody = cacheBody,
cacheThreshold = cacheThreshold,
)
}
private fun wrapResponse(scriptRuntime: ScriptRuntime, response: Response, cacheBody: Boolean, cacheThreshold: Long): Any {
// Wrap okhttp3.Response into a Rhino-friendly object.
// zh-CN: 将 okhttp3.Response 包装为 Rhino 可用对象.
return ResponseWrapper(scriptRuntime, response, cacheBody, cacheThreshold).wrap()
}
}
}

View File

@@ -913,6 +913,14 @@ object RhinoUtils {
}
fun handleAsyncOperation(scriptRuntime: ScriptRuntime, func: () -> Any?): NativeObject {
return handleAsyncOperation(scriptRuntime, func) { it }
}
fun <T> handleAsyncOperation(
scriptRuntime: ScriptRuntime,
operation: () -> T,
uiMapper: (T) -> Any?,
): NativeObject {
val promiseAdapter = ScriptPromiseAdapter()
// Use Android Thread directly to avoid coupling with JS threads/loopers here.
@@ -921,12 +929,19 @@ object RhinoUtils {
try {
// Do the blocking work (e.g. network request) in background thread.
// zh-CN: 在后台线程执行阻塞式操作 (如网络请求).
val result = func.invoke()
val result = operation.invoke()
// Resolve on UI thread so that UI scripts can safely touch `ui.*` in then().
// zh-CN: 在 UI 线程 resolve, 让 UI 脚本在 then() 中可安全操作 `ui.*`.
Handler(Looper.getMainLooper()).post {
promiseAdapter.resolve(result)
try {
// Map result on UI thread (e.g. wrap Java objects into Rhino objects).
// zh-CN: 在 UI 线程映射结果 (例如将 Java 对象包装为 Rhino 对象).
val mapped = uiMapper.invoke(result)
promiseAdapter.resolve(mapped)
} catch (t: Throwable) {
promiseAdapter.reject(t)
}
}
} catch (t: Throwable) {
// Reject on UI thread for consistent callback threading.
@@ -942,7 +957,6 @@ object RhinoUtils {
return callFunction(scriptRuntime, scriptRuntime.js_ResultAdapter, "promise", arrayOf(promiseAdapter)) as NativeObject
}
@JvmStatic
fun Class<*>.getRhinoStandardFunctionMethods(): List<Method> {
return this.declaredMethods.filter {