6.7.0 - Alpha8 - http 模块 body 对象增加 stream/saveToFile/close 方法, cacheBody 选项参数
This commit is contained in:
@@ -12,6 +12,9 @@
|
|||||||
"s13n.bytes 方法, 用于标准化字节数据 (参阅 项目文档 > [标准化](https://docs.autojs6.com/#/s13n))",
|
"s13n.bytes 方法, 用于标准化字节数据 (参阅 项目文档 > [标准化](https://docs.autojs6.com/#/s13n))",
|
||||||
"UiObject#isShifted 方法, 用于检测控件位置变化",
|
"UiObject#isShifted 方法, 用于检测控件位置变化",
|
||||||
"device.getSharedDeviceId 方法, 用于跨应用获取统一共享设备 ID _[`issue #455`](http://issues.autojs6.com/455)_",
|
"device.getSharedDeviceId 方法, 用于跨应用获取统一共享设备 ID _[`issue #455`](http://issues.autojs6.com/455)_",
|
||||||
|
"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)_",
|
||||||
"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 模板数据等"
|
||||||
@@ -45,7 +48,6 @@
|
|||||||
],
|
],
|
||||||
"improvement": [
|
"improvement": [
|
||||||
"console.log 等方法打印全局对象 (images, app, ocr 等) 时支持显示详细信息",
|
"console.log 等方法打印全局对象 (images, app, ocr 等) 时支持显示详细信息",
|
||||||
"http 模块相关方法支持不安全选项参数 (isInsecure/insecure), 用于忽略证书相关异常 _[`issue #417`](http://issues.autojs6.com/417)_",
|
|
||||||
"android.graphics.Paint#setColor 支持正常解析 ColorInt/ColorHex/ColorName 等颜色参数",
|
"android.graphics.Paint#setColor 支持正常解析 ColorInt/ColorHex/ColorName 等颜色参数",
|
||||||
"内置模块相关方法实参类型的异常消息增加类型摘要信息",
|
"内置模块相关方法实参类型的异常消息增加类型摘要信息",
|
||||||
"文件管理器浮动按钮展开后点击菜单项时优化菜单收起时机",
|
"文件管理器浮动按钮展开后点击菜单项时优化菜单收起时机",
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@file:Suppress("MayBeConstant")
|
||||||
|
|
||||||
package org.autojs.autojs.runtime.api.augment.http
|
package org.autojs.autojs.runtime.api.augment.http
|
||||||
|
|
||||||
import android.webkit.MimeTypeMap
|
import android.webkit.MimeTypeMap
|
||||||
@@ -21,6 +23,7 @@ import org.autojs.autojs.core.http.MutableOkHttp
|
|||||||
import org.autojs.autojs.extension.AnyExtensions.isJsFunction
|
import org.autojs.autojs.extension.AnyExtensions.isJsFunction
|
||||||
import org.autojs.autojs.extension.AnyExtensions.isJsNullish
|
import org.autojs.autojs.extension.AnyExtensions.isJsNullish
|
||||||
import org.autojs.autojs.extension.AnyExtensions.jsBrief
|
import org.autojs.autojs.extension.AnyExtensions.jsBrief
|
||||||
|
import org.autojs.autojs.extension.AnyExtensions.toRuntimePath
|
||||||
import org.autojs.autojs.extension.ArrayExtensions.toNativeArray
|
import org.autojs.autojs.extension.ArrayExtensions.toNativeArray
|
||||||
import org.autojs.autojs.extension.FlexibleArray
|
import org.autojs.autojs.extension.FlexibleArray
|
||||||
import org.autojs.autojs.extension.FlexibleArray.Companion.component1
|
import org.autojs.autojs.extension.FlexibleArray.Companion.component1
|
||||||
@@ -33,9 +36,11 @@ import org.autojs.autojs.pio.PFile
|
|||||||
import org.autojs.autojs.pio.PFileInterface
|
import org.autojs.autojs.pio.PFileInterface
|
||||||
import org.autojs.autojs.runtime.ScriptRuntime
|
import org.autojs.autojs.runtime.ScriptRuntime
|
||||||
import org.autojs.autojs.runtime.api.Mime
|
import org.autojs.autojs.runtime.api.Mime
|
||||||
|
import org.autojs.autojs.runtime.api.StringReadable
|
||||||
import org.autojs.autojs.runtime.api.augment.Augmentable
|
import org.autojs.autojs.runtime.api.augment.Augmentable
|
||||||
import org.autojs.autojs.runtime.api.augment.continuation.Continuation
|
import org.autojs.autojs.runtime.api.augment.continuation.Continuation
|
||||||
import org.autojs.autojs.runtime.api.augment.continuation.Creator
|
import org.autojs.autojs.runtime.api.augment.continuation.Creator
|
||||||
|
import org.autojs.autojs.runtime.api.augment.converter.core.Bytes
|
||||||
import org.autojs.autojs.runtime.exception.WrappedIllegalArgumentException
|
import org.autojs.autojs.runtime.exception.WrappedIllegalArgumentException
|
||||||
import org.autojs.autojs.util.RhinoUtils
|
import org.autojs.autojs.util.RhinoUtils
|
||||||
import org.autojs.autojs.util.RhinoUtils.UNDEFINED
|
import org.autojs.autojs.util.RhinoUtils.UNDEFINED
|
||||||
@@ -58,7 +63,10 @@ import org.mozilla.javascript.Scriptable
|
|||||||
import org.mozilla.javascript.ScriptableObject.DONTENUM
|
import org.mozilla.javascript.ScriptableObject.DONTENUM
|
||||||
import org.mozilla.javascript.ScriptableObject.PERMANENT
|
import org.mozilla.javascript.ScriptableObject.PERMANENT
|
||||||
import org.mozilla.javascript.ScriptableObject.READONLY
|
import org.mozilla.javascript.ScriptableObject.READONLY
|
||||||
|
import org.mozilla.javascript.Undefined
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
|
|
||||||
@Suppress("unused", "UNUSED_PARAMETER")
|
@Suppress("unused", "UNUSED_PARAMETER")
|
||||||
@@ -89,6 +97,8 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
|
|||||||
private const val KEY_BODY = "body"
|
private const val KEY_BODY = "body"
|
||||||
private const val KEY_TIMEOUT = "timeout"
|
private const val KEY_TIMEOUT = "timeout"
|
||||||
private const val KEY_MAX_RETRIES = "maxRetries"
|
private const val KEY_MAX_RETRIES = "maxRetries"
|
||||||
|
private const val KEY_CACHE_BODY = "cacheBody"
|
||||||
|
private const val KEY_BODY_CACHE_THRESHOLD_BYTES = "bodyCacheThresholdBytes"
|
||||||
|
|
||||||
private val DEFAULT_CONTENT_TYPE = Mime.APPLICATION_X_WWW_FORM_URLENCODED
|
private val DEFAULT_CONTENT_TYPE = Mime.APPLICATION_X_WWW_FORM_URLENCODED
|
||||||
|
|
||||||
@@ -98,19 +108,25 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
|
|||||||
@JvmField
|
@JvmField
|
||||||
val DEFAULT_MAX_RETRIES = MutableOkHttp.DEFAULT_MAX_RETRIES
|
val DEFAULT_MAX_RETRIES = MutableOkHttp.DEFAULT_MAX_RETRIES
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val DEFAULT_CACHE_BODY = false
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val DEFAULT_BODY_CACHE_THRESHOLD_BYTES = 8L * 1024 * 1024
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@RhinoRuntimeFunctionInterface
|
@RhinoRuntimeFunctionInterface
|
||||||
fun buildRequest(scriptRuntime: ScriptRuntime, args: Array<out Any?>): Request = ensureArgumentsLengthInRange(args, 1..2) {
|
fun buildRequest(scriptRuntime: ScriptRuntime, args: Array<out Any?>): Request = ensureArgumentsLengthInRange(args, 1..2) {
|
||||||
val (url, options) = it
|
val (url, options) = it
|
||||||
buildRequestRhino(url, options)
|
buildRequestRhinoWithRuntime(scriptRuntime, url, options)
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
@RhinoFunctionBody
|
@RhinoFunctionBody
|
||||||
fun buildRequestRhino(url: Any?, options: Any? = null): Request = when {
|
fun buildRequestRhinoWithRuntime(scriptRuntime: ScriptRuntime, url: Any?, options: Any? = null): Request = when {
|
||||||
options.isJsNullish() -> RequestBuilder(url).build()
|
options.isJsNullish() -> RequestBuilder(scriptRuntime, url).build()
|
||||||
options is NativeObject -> RequestBuilder(url, options).build()
|
options is NativeObject -> RequestBuilder(scriptRuntime, url, options).build()
|
||||||
else -> throw WrappedIllegalArgumentException("Argument options ${options.jsBrief()} must be a JavaScript Object")
|
else -> throw WrappedIllegalArgumentException("Argument options ${options.jsBrief()} must be a JavaScript Object")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,16 +152,19 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
|
|||||||
scriptRuntime.http.okhttp.maxRetries = coerceIntNumber(opt.prop(KEY_MAX_RETRIES), DEFAULT_MAX_RETRIES)
|
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.isInsecure = opt.inquire(listOf("isInsecure", "insecure"), ::coerceBoolean, false)
|
||||||
|
|
||||||
val newCall = scriptRuntime.http.client().newCall(buildRequestRhino(url, options))
|
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 newCall = scriptRuntime.http.client().newCall(buildRequestRhinoWithRuntime(scriptRuntime, url, options))
|
||||||
|
|
||||||
if (!callback.isJsFunction() && cont == null) {
|
if (!callback.isJsFunction() && cont == null) {
|
||||||
return ResponseWrapper(newCall.execute()).wrap()
|
return ResponseWrapper(scriptRuntime, newCall.execute(), cacheBody, cacheThreshold).wrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
scriptRuntime.loopers.waitWhenIdle(true)
|
scriptRuntime.loopers.waitWhenIdle(true)
|
||||||
newCall.enqueue(object : Callback {
|
newCall.enqueue(object : Callback {
|
||||||
override fun onResponse(call: Call, response: Response) {
|
override fun onResponse(call: Call, response: Response) {
|
||||||
val wrappedResponse = ResponseWrapper(response).wrap()
|
val wrappedResponse = ResponseWrapper(scriptRuntime, response, cacheBody, cacheThreshold).wrap()
|
||||||
cont?.resume(wrappedResponse)
|
cont?.resume(wrappedResponse)
|
||||||
if (callback is BaseFunction) {
|
if (callback is BaseFunction) {
|
||||||
scriptRuntime.bridges.call(callback, callback, arrayOf(wrappedResponse, null))
|
scriptRuntime.bridges.call(callback, callback, arrayOf(wrappedResponse, null))
|
||||||
@@ -257,8 +276,12 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ResponseWrapper(private val res: Response) {
|
private class ResponseWrapper(
|
||||||
|
private val scriptRuntime: ScriptRuntime,
|
||||||
|
private val res: Response,
|
||||||
|
private val cacheBody: Boolean,
|
||||||
|
private val cacheThresholdBytes: Long,
|
||||||
|
) {
|
||||||
private val mRequest = res.request
|
private val mRequest = res.request
|
||||||
|
|
||||||
var resBodyString: String? = null
|
var resBodyString: String? = null
|
||||||
@@ -281,8 +304,8 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
|
|||||||
// or returned from Call.execute.
|
// or returned from Call.execute.
|
||||||
val resBody = res.body!!
|
val resBody = res.body!!
|
||||||
|
|
||||||
return ResponseBodyNativeObject(resBody, this).also {
|
return ResponseBodyNativeObject(scriptRuntime, resBody, this, cacheBody, cacheThresholdBytes).also {
|
||||||
it.defineFunctionProperties(arrayOf("string", "bytes", "json"), it.javaClass, READONLY or PERMANENT)
|
it.defineFunctionProperties(arrayOf("string", "bytes", "json", "stream", "saveToFile", "close"), it.javaClass, READONLY or PERMANENT)
|
||||||
it.defineProperty(KEY_CONTENT_TYPE, { resBody.contentType() }, null, READONLY or PERMANENT)
|
it.defineProperty(KEY_CONTENT_TYPE, { resBody.contentType() }, null, READONLY or PERMANENT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -312,11 +335,91 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("unused")
|
class HttpSaveResult @JvmOverloads constructor(
|
||||||
private class ResponseBodyNativeObject(val resBody: ResponseBody, val responseWrapper: ResponseWrapper) : NativeObject() {
|
private val resultCode: Int,
|
||||||
|
private val outPath: String?,
|
||||||
|
private val bytesCopied: Long,
|
||||||
|
private val error: Throwable? = null,
|
||||||
|
) : NativeObject(), StringReadable {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
RhinoUtils.initNativeObjectPrototype(this)
|
RhinoUtils.initNativeObjectPrototype(this)
|
||||||
|
defineProperty("code", { resultCode }, null, READONLY or PERMANENT)
|
||||||
|
defineProperty("path", { outPath }, null, READONLY or PERMANENT)
|
||||||
|
defineProperty("bytesCopied", { bytesCopied }, null, READONLY or PERMANENT)
|
||||||
|
defineProperty("success", { resultCode == RESULT_OK }, null, READONLY or PERMANENT)
|
||||||
|
defineProperty("error", { error }, null, READONLY or PERMANENT)
|
||||||
|
defineFunctionProperties(arrayOf("isSuccess"), javaClass, READONLY or PERMANENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toStringReadable(): String = listOf(
|
||||||
|
"${HttpSaveResult::class.java.simpleName} {",
|
||||||
|
" code: ${resultCode},",
|
||||||
|
" bytesCopied: $bytesCopied (${Bytes.string(bytesCopied.toDouble(), useSpace = true, strict = true)}),",
|
||||||
|
" path: '${outPath}',",
|
||||||
|
" error: ${error?.message?.take(256)?.let { "'$it'" }},",
|
||||||
|
"}",
|
||||||
|
).joinToString("\n")
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
@JvmField
|
||||||
|
val RESULT_OK = 0
|
||||||
|
|
||||||
|
private const val RESULT_FAILED_GENERIC = -1
|
||||||
|
|
||||||
|
fun ok(path: String, bytesCopied: Long) =
|
||||||
|
HttpSaveResult(RESULT_OK, path, bytesCopied, null)
|
||||||
|
|
||||||
|
fun fail(path: String?, bytesCopied: Long, e: Throwable?) =
|
||||||
|
HttpSaveResult(RESULT_FAILED_GENERIC, path, bytesCopied, e)
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@RhinoStandardFunctionInterface
|
||||||
|
fun isSuccess(cx: Context, thisObj: Scriptable, args: Array<Any?>, funObj: Function): Boolean = ensureArgumentsIsEmpty(args) {
|
||||||
|
val o = thisObj as HttpSaveResult
|
||||||
|
o.resultCode == RESULT_OK
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("unused")
|
||||||
|
private class ResponseBodyNativeObject(
|
||||||
|
val scriptRuntime: ScriptRuntime,
|
||||||
|
val resBody: ResponseBody,
|
||||||
|
val responseWrapper: ResponseWrapper,
|
||||||
|
private val cacheBody: Boolean,
|
||||||
|
private val cacheThresholdBytes: Long,
|
||||||
|
) : NativeObject() {
|
||||||
|
|
||||||
|
init {
|
||||||
|
RhinoUtils.initNativeObjectPrototype(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Record whether it has been explicitly closed.
|
||||||
|
// zh-CN: 记录是否已显式关闭.
|
||||||
|
@Volatile
|
||||||
|
private var closed = false
|
||||||
|
|
||||||
|
private fun ensureOpen() {
|
||||||
|
if (closed) throw IllegalStateException("Response body already closed")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun autoCloseIfNeeded() {
|
||||||
|
// Close immediately after reading the complete content to avoid resource leaks;
|
||||||
|
// stream() close is handled by the caller.
|
||||||
|
// zh-CN: 读取完整内容后立即关闭, 避免资源泄露; stream() 由调用者负责 close().
|
||||||
|
if (!closed) {
|
||||||
|
runCatching { resBody.close() }
|
||||||
|
closed = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun shouldCache(lengthHint: Long?): Boolean {
|
||||||
|
if (!cacheBody) return false
|
||||||
|
if (lengthHint == null || lengthHint < 0) return true
|
||||||
|
return lengthHint <= cacheThresholdBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object : FlexibleArray() {
|
companion object : FlexibleArray() {
|
||||||
@@ -325,32 +428,120 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
|
|||||||
@RhinoStandardFunctionInterface
|
@RhinoStandardFunctionInterface
|
||||||
fun string(cx: Context, thisObj: Scriptable, args: Array<Any?>, funObj: Function): String = ensureArgumentsIsEmpty(args) {
|
fun string(cx: Context, thisObj: Scriptable, args: Array<Any?>, funObj: Function): String = ensureArgumentsIsEmpty(args) {
|
||||||
val o = thisObj as ResponseBodyNativeObject
|
val o = thisObj as ResponseBodyNativeObject
|
||||||
o.responseWrapper.resBodyString ?: o.resBody.string().also { o.responseWrapper.resBodyString = it }
|
o.responseWrapper.resBodyString?.let { return@ensureArgumentsIsEmpty it }
|
||||||
|
o.ensureOpen()
|
||||||
|
val contentLength = runCatching {
|
||||||
|
o.resBody.contentLength()
|
||||||
|
}.getOrDefault(-1L)
|
||||||
|
val str = o.resBody.string()
|
||||||
|
if (o.shouldCache(contentLength)) {
|
||||||
|
o.responseWrapper.resBodyString = str
|
||||||
|
}
|
||||||
|
// string() can safely close after consuming the stream.
|
||||||
|
// zh-CN: string() 消费流后可安全关闭.
|
||||||
|
o.autoCloseIfNeeded()
|
||||||
|
return@ensureArgumentsIsEmpty str
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@RhinoStandardFunctionInterface
|
@RhinoStandardFunctionInterface
|
||||||
fun bytes(cx: Context, thisObj: Scriptable, args: Array<Any?>, funObj: Function): ByteArray = ensureArgumentsIsEmpty(args) {
|
fun bytes(cx: Context, thisObj: Scriptable, args: Array<Any?>, funObj: Function): ByteArray = ensureArgumentsIsEmpty(args) {
|
||||||
val o = thisObj as ResponseBodyNativeObject
|
val o = thisObj as ResponseBodyNativeObject
|
||||||
o.responseWrapper.resBodyBytes ?: o.resBody.bytes().also { o.responseWrapper.resBodyBytes = it }
|
o.responseWrapper.resBodyBytes?.let { return@ensureArgumentsIsEmpty it }
|
||||||
|
o.ensureOpen()
|
||||||
|
val contentLength = runCatching {
|
||||||
|
o.resBody.contentLength()
|
||||||
|
}.getOrDefault(-1L)
|
||||||
|
val data = o.resBody.bytes()
|
||||||
|
if (o.shouldCache(contentLength)) {
|
||||||
|
o.responseWrapper.resBodyBytes = data
|
||||||
|
}
|
||||||
|
// bytes() 消费流后可安全关闭
|
||||||
|
o.autoCloseIfNeeded()
|
||||||
|
return@ensureArgumentsIsEmpty data
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@RhinoStandardFunctionInterface
|
@RhinoStandardFunctionInterface
|
||||||
fun json(cx: Context, thisObj: Scriptable, args: Array<Any?>, funObj: Function): Any? = ensureArgumentsIsEmpty(args) {
|
fun json(cx: Context, thisObj: Scriptable, args: Array<Any?>, funObj: Function): Any? = ensureArgumentsIsEmpty(args) {
|
||||||
val str = string(cx, thisObj, args, funObj)
|
val str = string(cx, thisObj, args, funObj)
|
||||||
try {
|
runCatching {
|
||||||
js_json_parse(str)
|
return@ensureArgumentsIsEmpty js_json_parse(str)
|
||||||
} catch (_: Exception) {
|
}.onFailure {
|
||||||
throw IllegalStateException("Failed to parse JSON. Body string may be not in JSON format")
|
throw IllegalStateException("Failed to parse JSON. Body string may be not in JSON format")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@RhinoStandardFunctionInterface
|
||||||
|
fun stream(cx: Context, thisObj: Scriptable, args: Array<Any?>, funObj: Function): InputStream = ensureArgumentsIsEmpty(args) {
|
||||||
|
val o = thisObj as ResponseBodyNativeObject
|
||||||
|
o.ensureOpen()
|
||||||
|
// Don't auto-close; let the caller handle it, supporting streaming copy.
|
||||||
|
// zh-CN: 不自动关闭; 交给调用者处理, 支持流式拷贝.
|
||||||
|
o.resBody.byteStream()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save directly to file (avoid loading large responses into memory).
|
||||||
|
// zh-CN: 直接保存到文件 (避免将大型响应加载到内存中).
|
||||||
|
@JvmStatic
|
||||||
|
@RhinoStandardFunctionInterface
|
||||||
|
fun saveToFile(cx: Context, thisObj: Scriptable, args: Array<Any?>, funObj: Function): HttpSaveResult = ensureArgumentsLengthInRange(args, 1..2) { argList ->
|
||||||
|
val o = thisObj as ResponseBodyNativeObject
|
||||||
|
o.ensureOpen()
|
||||||
|
|
||||||
|
val (pathRaw, bufSizeRaw) = argList
|
||||||
|
val path = coerceString(pathRaw, "").toRuntimePath(o.scriptRuntime)
|
||||||
|
val bufSize = coerceIntNumber(bufSizeRaw, 0).let {
|
||||||
|
if (it > 0) it else 8192
|
||||||
|
}
|
||||||
|
|
||||||
|
val file = PFile(path)
|
||||||
|
val isDirectory = file.isDirectory || path.endsWith("/")
|
||||||
|
if (isDirectory) {
|
||||||
|
throw WrappedIllegalArgumentException("Path \"$path\" must be a file path instead of a directory path")
|
||||||
|
}
|
||||||
|
val buffer = ByteArray(bufSize)
|
||||||
|
var copied = 0L
|
||||||
|
var input: InputStream? = null
|
||||||
|
var output: OutputStream? = null
|
||||||
|
return@ensureArgumentsLengthInRange try {
|
||||||
|
input = o.resBody.byteStream()
|
||||||
|
output = file.outputStream()
|
||||||
|
while (true) {
|
||||||
|
val read = input.read(buffer)
|
||||||
|
if (read == -1) break
|
||||||
|
output.write(buffer, 0, read)
|
||||||
|
copied += read
|
||||||
|
}
|
||||||
|
output.flush()
|
||||||
|
HttpSaveResult.ok(path, copied)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
HttpSaveResult.fail(path, copied, e)
|
||||||
|
} finally {
|
||||||
|
runCatching { input?.close() }
|
||||||
|
runCatching { output?.close() }
|
||||||
|
o.autoCloseIfNeeded()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicit close.
|
||||||
|
// zh-CN: 显式关闭.
|
||||||
|
@JvmStatic
|
||||||
|
@RhinoStandardFunctionInterface
|
||||||
|
fun close(cx: Context, thisObj: Scriptable, args: Array<Any?>, funObj: Function): Undefined = ensureArgumentsIsEmpty(args) {
|
||||||
|
val o = thisObj as ResponseBodyNativeObject
|
||||||
|
if (!o.closed) {
|
||||||
|
runCatching { o.resBody.close() }
|
||||||
|
o.closed = true
|
||||||
|
}
|
||||||
|
UNDEFINED
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class RequestBuilder(private val url: Any?, options: NativeObject = newNativeObject()) {
|
private class RequestBuilder(private val scriptRuntime: ScriptRuntime, private val url: Any?, options: NativeObject = newNativeObject()) {
|
||||||
|
|
||||||
private val mRequest = Request.Builder()
|
private val mRequest = Request.Builder()
|
||||||
private val mRequestBuilderHelper = RequestBuilderHelper(options)
|
private val mRequestBuilderHelper = RequestBuilderHelper(options)
|
||||||
@@ -358,7 +549,7 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
|
|||||||
fun build(): Request {
|
fun build(): Request {
|
||||||
mRequest.url(mRequestBuilderHelper.getUrl(coerceString(url)))
|
mRequest.url(mRequestBuilderHelper.getUrl(coerceString(url)))
|
||||||
mRequestBuilderHelper.setHeaders(mRequest)
|
mRequestBuilderHelper.setHeaders(mRequest)
|
||||||
mRequestBuilderHelper.setMethod(mRequest)
|
mRequestBuilderHelper.setMethod(scriptRuntime, mRequest)
|
||||||
return mRequest.build()
|
return mRequest.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -385,7 +576,7 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setMethod(request: Request.Builder) {
|
fun setMethod(scriptRuntime: ScriptRuntime, request: Request.Builder) {
|
||||||
val method = coerceString(options.prop(KEY_METHOD))
|
val method = coerceString(options.prop(KEY_METHOD))
|
||||||
// require(method is String) { "Property method is required for header options" }
|
// require(method is String) { "Property method is required for header options" }
|
||||||
when {
|
when {
|
||||||
@@ -393,7 +584,7 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
|
|||||||
request.method(method, parseBody())
|
request.method(method, parseBody())
|
||||||
}
|
}
|
||||||
!options.prop(KEY_FILES).isJsNullish() -> {
|
!options.prop(KEY_FILES).isJsNullish() -> {
|
||||||
request.method(method, parseMultipart())
|
request.method(method, parseMultipart(scriptRuntime))
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
request.method(method, null)
|
request.method(method, null)
|
||||||
@@ -422,7 +613,7 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
|
|||||||
else -> throw WrappedIllegalArgumentException("Unknown type of body for header options")
|
else -> throw WrappedIllegalArgumentException("Unknown type of body for header options")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parseMultipart(): MultipartBody {
|
fun parseMultipart(scriptRuntime: ScriptRuntime): MultipartBody {
|
||||||
val builder = MultipartBody.Builder().setType(MultipartBody.FORM)
|
val builder = MultipartBody.Builder().setType(MultipartBody.FORM)
|
||||||
|
|
||||||
val files = options.prop(KEY_FILES)
|
val files = options.prop(KEY_FILES)
|
||||||
@@ -438,14 +629,14 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
|
|||||||
is NativeArray -> when (value.length) {
|
is NativeArray -> when (value.length) {
|
||||||
2L -> {
|
2L -> {
|
||||||
val (fileName, path) = value
|
val (fileName, path) = value
|
||||||
val file = if (path is URI) PFile(path) else PFile(coerceString(path))
|
val file = if (path is URI) PFile(path) else PFile(path.toRuntimePath(scriptRuntime))
|
||||||
val mimeType = parseMimeType(file.extension)
|
val mimeType = parseMimeType(file.extension)
|
||||||
val requestBody = file.asRequestBody(mimeType.toMediaTypeOrNull())
|
val requestBody = file.asRequestBody(mimeType.toMediaTypeOrNull())
|
||||||
processFile(builder, key, fileName, requestBody)
|
processFile(builder, key, fileName, requestBody)
|
||||||
}
|
}
|
||||||
3L -> {
|
3L -> {
|
||||||
val (fileName, mimeType, path) = value
|
val (fileName, mimeType, path) = value
|
||||||
val file = if (path is URI) PFile(path) else PFile(coerceString(path))
|
val file = if (path is URI) PFile(path) else PFile(path.toRuntimePath(scriptRuntime))
|
||||||
val requestBody = file.asRequestBody(coerceString(mimeType).toMediaTypeOrNull())
|
val requestBody = file.asRequestBody(coerceString(mimeType).toMediaTypeOrNull())
|
||||||
processFile(builder, key, fileName, requestBody)
|
processFile(builder, key, fileName, requestBody)
|
||||||
}
|
}
|
||||||
@@ -457,7 +648,7 @@ class Http(scriptRuntime: ScriptRuntime) : Augmentable(scriptRuntime) {
|
|||||||
}
|
}
|
||||||
is PFileInterface -> {
|
is PFileInterface -> {
|
||||||
val path = value.path
|
val path = value.path
|
||||||
val file = PFile(path)
|
val file = PFile(path.toRuntimePath(scriptRuntime))
|
||||||
val fileName = file.name
|
val fileName = file.name
|
||||||
val mimeType = parseMimeType(file.extension)
|
val mimeType = parseMimeType(file.extension)
|
||||||
val requestBody = file.asRequestBody(mimeType.toMediaTypeOrNull())
|
val requestBody = file.asRequestBody(mimeType.toMediaTypeOrNull())
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#Sun Oct 26 14:23:07 CST 2025
|
#Sun Oct 26 14:38:37 CST 2025
|
||||||
BUILD_TIME=1761459787547
|
BUILD_TIME=1761460717246
|
||||||
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
|
||||||
|
|||||||
Reference in New Issue
Block a user