Http客户端
HTTP 客户端是发起网络请求的起点。您可以创建多个客户端,每个客户端都有自己独立的配置(如 Cookie 存储、代理等)。
创建 HTTP 客户端
创建一个默认配置的客户端
最简单的用法是创建一个不带任何特殊配置的客户端。
import com.m8test.script.GlobalVariables.*
// 确保脚本在后台运行,等待网络请求完成
_threads.getMain().setBackground(true)
// 创建一个新的 HTTP 客户端
val httpClient = _httpClients.newClient(null)
_console.log("成功创建了一个 HTTP 客户端:", httpClient)
// 当不再需要时,可以销毁客户端以释放资源
// 在脚本结束时,所有客户端也会被自动销毁
val deleted = _httpClients.deleteClient(httpClient)
_console.log("客户端销毁状态:", deleted)
// 确保脚本在后台运行,等待网络请求完成
$threads.getMain().setBackground(true)
// 创建一个新的 HTTP 客户端
def httpClient = $httpClients.newClient(null)
$console.log("成功创建了一个 HTTP 客户端:", httpClient)
// 当不再需要时,可以销毁客户端以释放资源
// 在脚本结束时,所有客户端也会被自动销毁
def deleted = $httpClients.deleteClient(httpClient)
$console.log("客户端销毁状态:", deleted)
// 确保脚本在后台运行,等待网络请求完成
$threads.getMain().setBackground(true);
// 创建一个新的 HTTP 客户端
const httpClient = $httpClients.newClient(null);
$console.log("成功创建了一个 HTTP 客户端:", httpClient);
// 当不再需要时,可以销毁客户端以释放资源
// 在脚本结束时,所有客户端也会被自动销毁
const deleted = $httpClients.deleteClient(httpClient);
$console.log("客户端销毁状态:", deleted);
-- 确保脚本在后台运行,等待网络请求完成
_threads:getMain():setBackground(true)
-- 创建一个新的 HTTP 客户端
local httpClient = _httpClients:newClient(nil)
_console:log("成功创建了一个 HTTP 客户端:", httpClient)
-- 当不再需要时,可以销毁客户端以释放资源
-- 在脚本结束时,所有客户端也会被自动销毁
local deleted = _httpClients:deleteClient(httpClient)
_console:log("客户端销毁状态:", deleted)
<?php
/** @var m8test_java\com\m8test\script\core\api\thread\Threads $threads */
global $threads;
/** @var m8test_java\com\m8test\script\core\api\net\http\HttpClients $httpClients */
global $httpClients;
/** @var m8test_java\com\m8test\script\core\api\console\Console $console */
global $console;
// 确保脚本在后台运行,等待网络请求完成
$threads->getMain()->setBackground(true);
// 创建一个新的 HTTP 客户端
$httpClient = $httpClients->newClient(null);
$console->log(javaString("成功创建了一个 HTTP 客户端:"), $httpClient);
// 当不再需要时,可以销毁客户端以释放资源
// 在脚本结束时,所有客户端也会被自动销毁
$deleted = $httpClients->deleteClient($httpClient);
$console->log(javaString("客户端销毁状态:"), $deleted);
# 导入所需的全局变量
from m8test_java.com.m8test.script.GlobalVariables import _threads
from m8test_java.com.m8test.script.GlobalVariables import _httpClients
from m8test_java.com.m8test.script.GlobalVariables import _console
# 确保脚本在后台运行,等待网络请求完成
_threads.getMain().setBackground(True)
# 创建一个新的 HTTP 客户端
httpClient = _httpClients.newClient(None)
_console.log("成功创建了一个 HTTP 客户端:", httpClient)
# 当不再需要时,可以销毁客户端以释放资源
# 在脚本结束时,所有客户端也会被自动销毁
deleted = _httpClients.deleteClient(httpClient)
_console.log("客户端销毁状态:", deleted)
# encoding: utf-8
# 确保脚本在后台运行,等待网络请求完成
$threads.getMain.setBackground(true)
# 创建一个新的 HTTP 客户端
httpClient = $httpClients.newClient(nil)
$console.log("成功创建了一个 HTTP 客户端:", httpClient)
# 当不再需要时,可以销毁客户端以释放资源
# 在脚本结束时,所有客户端也会被自动销毁
deleted = $httpClients.deleteClient(httpClient)
$console.log("客户端销毁状态:", deleted)
创建一个自定义配置的客户端
您可以为客户端配置代理、请求重定向等策略。
import com.m8test.script.GlobalVariables.*
// 确保脚本在后台运行
_threads.getMain().setBackground(true)
// 创建一个配置了 HTTP 代理且禁用了自动重定向的客户端
// newClient 的配置块通常是 Receiver 模式
val customHttpClient = _httpClients.newClient {
// 设置代理服务器
setProxy("192.168.31.243", 10809, null) // 第三个参数为 null 表示使用默认的 HTTP 代理
// 禁用自动处理 3xx 重定向
setFollowRedirects(false)
}
_console.log("成功创建了自定义配置的 HTTP 客户端:", customHttpClient)
// ... 使用 customHttpClient 发起请求 ...
// 确保脚本在后台运行
$threads.getMain().setBackground(true)
// 创建一个配置了 HTTP 代理且禁用了自动重定向的客户端
def customHttpClient = $httpClients.newClient({ config ->
// 设置代理服务器
config.setProxy("192.168.31.243", 10809, null) // 第三个参数为 null 表示使用默认的 HTTP 代理
// 禁用自动处理 3xx 重定向
config.setFollowRedirects(false)
})
$console.log("成功创建了自定义配置的 HTTP 客户端:", customHttpClient)
// ... 使用 customHttpClient 发起请求 ...
// 确保脚本在后台运行
$threads.getMain().setBackground(true);
// 创建一个配置了 HTTP 代理且禁用了自动重定向的客户端
const customHttpClient = $httpClients.newClient(config => {
// 设置代理服务器
config.setProxy("192.168.31.243", 10809, null); // 第三个参数为 null 表示使用默认的 HTTP 代理
// 禁用自动处理 3xx 重定向
config.setFollowRedirects(false);
});
$console.log("成功创建了自定义配置的 HTTP 客户端:", customHttpClient);
// ... 使用 customHttpClient 发起请求 ...
-- 确保脚本在后台运行
_threads:getMain():setBackground(true)
-- 创建一个配置了 HTTP 代理且禁用了自动重定向的客户端
local customHttpClient = _httpClients:newClient(function(config)
-- 设置代理服务器
config:setProxy("192.168.31.243", 10809, nil) -- 第三个参数为 nil 表示使用默认的 HTTP 代理
-- 禁用自动处理 3xx 重定向
config:setFollowRedirects(false)
end)
_console:log("成功创建了自定义配置的 HTTP 客户端:", customHttpClient)
-- ... 使用 customHttpClient 发起请求 ...
<?php
/** @var m8test_java\com\m8test\script\core\api\thread\Threads $threads */
global $threads;
/** @var m8test_java\com\m8test\script\core\api\net\http\HttpClients $httpClients */
global $httpClients;
/** @var m8test_java\com\m8test\script\core\api\console\Console $console */
global $console;
// 确保脚本在后台运行
$threads->getMain()->setBackground(true);
// 创建一个配置了 HTTP 代理且禁用了自动重定向的客户端
$customHttpClient = $httpClients->newClient(function ($config) {
// 设置代理服务器
$config->setProxy(javaString("192.168.31.243"), 10809, null); // 第三个参数为 null 表示使用默认的 HTTP 代理
// 禁用自动处理 3xx 重定向
$config->setFollowRedirects(false);
});
$console->log(javaString("成功创建了自定义配置的 HTTP 客户端:"), $customHttpClient);
// ... 使用 customHttpClient 发起请求 ...
# 导入所需的全局变量
from m8test_java.com.m8test.script.GlobalVariables import _threads
from m8test_java.com.m8test.script.GlobalVariables import _httpClients
from m8test_java.com.m8test.script.GlobalVariables import _console
# 确保脚本在后台运行
_threads.getMain().setBackground(True)
# 创建一个配置了 HTTP 代理且禁用了自动重定向的客户端
customHttpClient = _httpClients.newClient(lambda config: (
# 设置代理服务器
# Groovy的 { config -> ... } 转换为 lambda config: ...
# Groovy的 null 转换为 Python的 None
config.setProxy("192.168.31.243", 10809, None), # 第三个参数为 None 表示使用默认的 HTTP 代理
# 禁用自动处理 3xx 重定向
# Groovy的 false 转换为 Python的 False
config.setFollowRedirects(False)
))
# 为了安全打印,将 customHttpClient 对象转换为字符串
_console.log("成功创建了自定义配置的 HTTP 客户端: " + str(customHttpClient))
# ... 使用 customHttpClient 发起请求 ...
# encoding: utf-8
# 确保脚本在后台运行
$threads.getMain.setBackground(true)
# 创建一个配置了 HTTP 代理且禁用了自动重定向的客户端
customHttpClient = $httpClients.newClient do |config|
# 设置代理服务器
config.setProxy("192.168.31.243", 10809, nil) # 第三个参数为 null 表示使用默认的 HTTP 代理
# 禁用自动处理 3xx 重定向
config.setFollowRedirects(false)
end
$console.log("成功创建了自定义配置的 HTTP 客户端:", customHttpClient)
# ... 使用 customHttpClient 发起请求 ...
发起请求
所有请求方法都返回一个 Deferred 对象,它代表一个未来的结果。您应该使用 .then(), .onError() 和 .onComplete() 来处理异步结果。
发起 GET 请求
GET 请求通常用于从服务器获取数据。
import com.m8test.script.GlobalVariables.*
// 确保脚本在后台运行
_threads.getMain().setBackground(true)
// 在脚本顶层创建一个协程作用域,供所有异步操作复用
val scope = _coroutines.newScope(null)
val httpClient = _httpClients.newClient(null)
val url = "http://httpbin.org/get"
// 示例 1: 简单的 GET 请求
httpClient.get(scope, url, null, null)
.then { s, response ->
_console.log("简单 GET 请求成功,状态码:", response.getStatusCode().getValue())
// 返回一个新的 Deferred,其结果是响应体文本
response.getTextBody(s)
}
.then { s, bodyText ->
_console.log("响应体:", bodyText)
// 链的最后一步,返回一个已完成的 Deferred 以正确结束链条
s.resolve(null)
}
.onError { error ->
_console.error("简单 GET 请求失败:", error)
}
// 示例 2: 带查询参数和自定义请求头的 GET 请求
val queryParams = mapOf(
"user" to "M8Test",
"id" to "12345"
)
// request 配置块通常是 Receiver 模式
httpClient.get(scope, url, queryParams) {
// 添加自定义请求头
addHeader("X-Custom-Header", "MyValue")
setUserAgent("M8Test-Script-Client/1.0")
}
.then { s, response ->
_console.log("带参数的 GET 请求成功,状态码:", response.getStatusCode().getValue())
response.getTextBody(s)
}
.then { s, bodyText ->
_console.log("响应体:", bodyText)
s.resolve(null)
}
.onError { error ->
_console.error("带参数的 GET 请求失败:", error)
}
// 确保脚本在后台运行
$threads.getMain().setBackground(true)
// 在脚本顶层创建一个协程作用域,供所有异步操作复用
def scope = $coroutines.newScope(null)
def httpClient = $httpClients.newClient(null)
def url = "http://httpbin.org/get"
// 示例 1: 简单的 GET 请求
httpClient.get(scope, url, null, null)
.then({ s, response ->
$console.log("简单 GET 请求成功,状态码:", response.getStatusCode().getValue())
// 返回一个新的 Deferred,其结果是响应体文本
response.getTextBody(s)
})
.then({ s, bodyText ->
$console.log("响应体:", bodyText)
// 链的最后一步,返回一个已完成的 Deferred 以正确结束链条
s.resolve(null)
})
.onError({ error ->
$console.error("简单 GET 请求失败:", error)
})
// 示例 2: 带查询参数和自定义请求头的 GET 请求
def queryParams = [
"user": "M8Test",
"id": "12345"
]
httpClient.get(scope, url, queryParams, { request ->
// 添加自定义请求头
request.addHeader("X-Custom-Header", "MyValue")
request.setUserAgent("M8Test-Script-Client/1.0")
})
.then({ s, response ->
$console.log("带参数的 GET 请求成功,状态码:", response.getStatusCode().getValue())
response.getTextBody(s)
})
.then({ s, bodyText ->
$console.log("响应体:", bodyText)
s.resolve(null)
})
.onError({ error ->
$console.error("带参数的 GET 请求失败:", error)
})
// 确保脚本在后台运行
$threads.getMain().setBackground(true);
// 在脚本顶层创建一个协程作用域,供所有异步操作复用
const scope = $coroutines.newScope(null);
const httpClient = $httpClients.newClient(null);
const url = "http://httpbin.org/get";
// 示例 1: 简单的 GET 请求
httpClient.get(scope, url, null, null)
.then((s, response) => {
$console.log("简单 GET 请求成功,状态码:", response.getStatusCode().getValue());
// 返回一个新的 Deferred,其结果是响应体文本
return response.getTextBody(s);
})
.then((s, bodyText) => {
$console.log("响应体:", bodyText);
// 链的最后一步,返回一个已完成的 Deferred 以正确结束链条
return s.resolve(null);
})
.onError(error => {
$console.error("简单 GET 请求失败:", error);
});
// 示例 2: 带查询参数和自定义请求头的 GET 请求
const queryParams = {
"user": "M8Test",
"id": "12345"
};
httpClient.get(scope, url, queryParams, request => {
// 添加自定义请求头
request.addHeader("X-Custom-Header", "MyValue");
request.setUserAgent("M8Test-Script-Client/1.0");
})
.then((s, response) => {
$console.log("带参数的 GET 请求成功,状态码:", response.getStatusCode().getValue());
return response.getTextBody(s);
})
.then((s, bodyText) => {
$console.log("响应体:", bodyText);
return s.resolve(null);
})
.onError(error => {
$console.error("带参数的 GET 请求失败:", error);
});
-- 确保脚本在后台运行
_threads:getMain():setBackground(true)
-- 在脚本顶层创建一个协程作用域,供所有异步操作复用
local scope = _coroutines:newScope(nil)
local httpClient = _httpClients:newClient(nil)
local url = "http://httpbin.org/get"
-- 示例 1: 简单的 GET 请求
httpClient:get(scope, url, nil, nil)
:_then(function(s, response)
_console:log("简单 GET 请求成功,状态码:", response:getStatusCode():getValue())
-- 返回一个新的 Deferred,其结果是响应体文本
return response:getTextBody(s)
end)
:_then(function(s, bodyText)
_console:log("响应体:", bodyText)
-- 链的最后一步,返回一个已完成的 Deferred 以正确结束链条
return s:resolve(nil)
end)
:onError(function(error)
-- 在调用方法前检查 error 是否为 nil
if error ~= nil then
_console:error("简单 GET 请求失败:", error:stackTraceToString())
else
_console:error("简单 GET 请求失败,但未提供错误详情。")
end
end)
-- 示例 2: 带查询参数和自定义请求头的 GET 请求
-- 修正:使用 _maps API 显式创建 Java Map 对象
local queryParams = _maps:mapOf(
_maps:pairOf("user", "M8Test"),
_maps:pairOf("id", "12345")
)
httpClient:get(scope, url, queryParams, function(request)
-- 添加自定义请求头
request:addHeader("X-Custom-Header", "MyValue")
request:setUserAgent("M8Test-Script-Client/1.0")
end)
:_then(function(s, response)
_console:log("带参数的 GET 请求成功,状态码:", response:getStatusCode():getValue())
return response:getTextBody(s)
end)
:_then(function(s, bodyText)
_console:log("响应体:", bodyText)
return s:resolve(nil)
end)
:onError(function(error)
-- 在调用方法前检查 error 是否为 nil
if error ~= nil then
_console:error("带参数的 GET 请求失败:", error:stackTraceToString())
else
_console:error("带参数的 GET 请求失败,但未提供错误详情。")
end
end)
<?php
/** @var m8test_java\com\m8test\script\core\api\thread\Threads $threads */
global $threads;
/** @var m8test_java\com\m8test\script\core\api\coroutines\Coroutines $coroutines */
global $coroutines;
/** @var m8test_java\com\m8test\script\core\api\net\http\HttpClients $httpClients */
global $httpClients;
/** @var m8test_java\com\m8test\script\core\api\console\Console $console */
global $console;
// 确保脚本在后台运行
$threads->getMain()->setBackground(true);
// 在脚本顶层创建一个协程作用域,供所有异步操作复用
$scope = $coroutines->newScope(function ($context) {
$context->setDispatcher(function ($dispatchers) {
return $dispatchers->getScriptMain();
});
});
$httpClient = $httpClients->newClient(null);
$url = javaString("http://httpbin.org/get");
// 示例 1: 简单的 GET 请求
$httpClient->get($scope, $url, null, null)
->then(function ($s, $response) {
global $console;
$console->log(javaString("简单 GET 请求成功,状态码:"), $response->getStatusCode()->getValue());
// 返回一个新的 Deferred,其结果是响应体文本
return $response->getTextBody($s);
})
->then(function ($s, $bodyText) {
global $console;
$console->log(javaString("响应体:"), $bodyText);
// 链的最后一步,返回一个已完成的 Deferred 以正确结束链条
return $s->resolve(null);
})
->onError(function ($error) {
global $console;
$console->error(javaString("简单 GET 请求失败:"), $error);
});
// 示例 2: 带查询参数和自定义请求头的 GET 请求
$queryParams = [
"user" => javaString("M8Test"),
"id" => javaString("12345")
];
$httpClient->get($scope, $url, $queryParams, function ($request) {
// 添加自定义请求头
$request->addHeader(javaString("X-Custom-Header"), javaString("MyValue"));
$request->setUserAgent(javaString("M8Test-Script-Client/1.0"));
})
->then(function ($s, $response) {
global $console;
$console->log(javaString("带参数的 GET 请求成功,状态码:"), $response->getStatusCode()->getValue());
return $response->getTextBody($s);
})
->then(function ($s, $bodyText) {
global $console;
$console->log(javaString("响应体:"), $bodyText);
return $s->resolve(null);
})
->onError(function ($error) {
global $console;
$console->error(javaString("带参数的 GET 请求失败:"), $error);
});
# 导入所需的全局变量
from m8test_java.com.m8test.script.GlobalVariables import _threads
from m8test_java.com.m8test.script.GlobalVariables import _coroutines
from m8test_java.com.m8test.script.GlobalVariables import _httpClients
from m8test_java.com.m8test.script.GlobalVariables import _console
# 确保脚本在后台运行
_threads.getMain().setBackground(True)
# 在脚本顶层创建一个协程作用域,供所有异步操作复用
scope = _coroutines.newScope(lambda it: it.setDispatcher(lambda d: d.getScriptMain()))
httpClient = _httpClients.newClient(None)
url = "http://httpbin.org/get"
# 示例 1: 简单的 GET 请求
httpClient.get(scope, url, None, None).then(lambda s, response: (
# Groovy 的多参数 log 转换为 Python 的 f-string 格式化
_console.log(f"简单 GET 请求成功,状态码: {response.getStatusCode().getValue()}"),
# 返回一个新的 Deferred,其结果是响应体文本
response.getTextBody(s)
# 此处有两个表达式 (log 和 return),因此使用 [-1] 是正确的
)[-1]).then(lambda s, bodyText: (
_console.log(f"响应体: {bodyText}"),
# 链的最后一步,返回一个已完成的 Deferred 以正确结束链条
s.resolve(None)
# 此处有两个表达式 (log 和 return),因此使用 [-1] 是正确的
)[-1]).onError(lambda error:
# 此处只有一个表达式,直接返回,不使用 [-1]
_console.error(f"简单 GET 请求失败: {error}")
)
# 示例 2: 带查询参数和自定义请求头的 GET 请求
# Groovy 的 map 转换为 Python 的 dictionary
queryParams = {
"user": "M8Test",
"id": "12345"
}
# Groovy 中方法最后一个闭包参数可以写在括号外,Python 中则作为常规参数传入
httpClient.get(scope, url, queryParams, lambda request: (
# 添加自定义请求头
request.addHeader("X-Custom-Header", "MyValue"),
request.setUserAgent("M8Test-Script-Client/1.0")
)).then(lambda s, response: (
_console.log(f"带参数的 GET 请求成功,状态码: {response.getStatusCode().getValue()}"),
response.getTextBody(s)
)[-1]).then(lambda s, bodyText: (
_console.log(f"响应体: {bodyText}"),
s.resolve(None)
)[-1]).onError(lambda error:
_console.error(f"带参数的 GET 请求失败: {error}")
)
# encoding: utf-8
# 确保脚本在后台运行
$threads.getMain.setBackground(true)
# 在脚本顶层创建一个协程作用域,供所有异步操作复用
scope = $coroutines.newScope(nil)
httpClient = $httpClients.newClient(nil)
url = "http://httpbin.org/get"
# 示例 1: 简单的 GET 请求
httpClient.get(scope, url, nil, nil)
.then do |s, response|
$console.log("简单 GET 请求成功,状态码:", response.getStatusCode.getValue)
# 返回一个新的 Deferred,其结果是响应体文本
response.getTextBody(s)
end
.then do |s, bodyText|
$console.log("响应体:", bodyText)
# 链的最后一步,返回一个已完成的 Deferred 以正确结束链条
s.resolve(nil)
end
.onError do |error|
$console.error("简单 GET 请求失败:", error)
end
# 示例 2: 带查询参数和自定义请求头的 GET 请求
# Ruby 中 Hash 使用 {} 且键值对之间用 => 连接
queryParams = {
"user" => "M8Test",
"id" => "12345"
}
httpClient.get(scope, url, queryParams) do |request|
# 添加自定义请求头
request.addHeader("X-Custom-Header", "MyValue")
request.setUserAgent("M8Test-Script-Client/1.0")
end.then do |s, response|
$console.log("带参数的 GET 请求成功,状态码:", response.getStatusCode.getValue)
response.getTextBody(s)
end.then do |s, bodyText|
$console.log("响应体:", bodyText)
s.resolve(nil)
end.onError do |error|
$console.error("带参数的 GET 请求失败:", error)
end
发起 POST 请求
这常用于模拟网页表单提交。
import com.m8test.script.GlobalVariables.*
// 确保脚本在后台运行
_threads.getMain().setBackground(true)
val scope = _coroutines.newScope(null)
val httpClient = _httpClients.newClient(null)
val url = "http://httpbin.org/post"
val formData = mapOf(
"username" to "testuser",
"password" to "secretpassword"
)
httpClient.post(scope, url, formData, null)
.then { s, response ->
_console.log("Form POST 请求成功,状态码:", response.getStatusCode().getValue())
response.getTextBody(s)
}
.then { s, bodyText ->
_console.log("响应体:", bodyText)
s.resolve(null)
}
.onError { error ->
_console.error("Form POST 请求失败:", error)
}
// 确保脚本在后台运行
$threads.getMain().setBackground(true)
def scope = $coroutines.newScope(null)
def httpClient = $httpClients.newClient(null)
def url = "http://httpbin.org/post"
def formData = [
"username": "testuser",
"password": "secretpassword"
]
httpClient.post(scope, url, formData, null)
.then({ s, response ->
$console.log("Form POST 请求成功,状态码:", response.getStatusCode().getValue())
response.getTextBody(s)
})
.then({ s, bodyText ->
$console.log("响应体:", bodyText)
s.resolve(null)
})
.onError({ error ->
$console.error("Form POST 请求失败:", error)
})
// 确保脚本在后台运行
$threads.getMain().setBackground(true);
const scope = $coroutines.newScope(null);
const httpClient = $httpClients.newClient(null);
const url = "http://httpbin.org/post";
const formData = {
"username": "testuser",
"password": "secretpassword"
};
httpClient.post(scope, url, formData, null)
.then((s, response) => {
$console.log("Form POST 请求成功,状态码:", response.getStatusCode().getValue());
return response.getTextBody(s);
})
.then((s, bodyText) => {
$console.log("响应体:", bodyText);
return s.resolve(null);
})
.onError(error => {
$console.error("Form POST 请求失败:", error);
});
-- 确保脚本在后台运行
_threads:getMain():setBackground(true)
local scope = _coroutines:newScope(nil)
local httpClient = _httpClients:newClient(nil)
local url = "http://httpbin.org/post"
-- 修正:使用 _maps API 显式创建 Java Map 对象
local formData = _maps:mapOf(
_maps:pairOf("username", "testuser"),
_maps:pairOf("password", "secretpassword")
)
httpClient:post(scope, url, formData, nil)
:_then(function(s, response)
_console:log("Form POST 请求成功,状态码:", response:getStatusCode():getValue())
return response:getTextBody(s)
end)
:_then(function(s, bodyText)
_console:log("响应体:", bodyText)
return s:resolve(nil)
end)
:onError(function(error)
if error ~= nil then
_console:error("Form POST 请求失败:", error:stackTraceToString())
else
_console:error("Form POST 请求失败,但未提供错误详情。")
end
end)
<?php
/** @var m8test_java\com\m8test\script\core\api\thread\Threads $threads */
global $threads;
/** @var m8test_java\com\m8test\script\core\api\coroutines\Coroutines $coroutines */
global $coroutines;
/** @var m8test_java\com\m8test\script\core\api\net\http\HttpClients $httpClients */
global $httpClients;
/** @var m8test_java\com\m8test\script\core\api\console\Console $console */
global $console;
// 确保脚本在后台运行
$threads->getMain()->setBackground(true);
$scope = $coroutines->newScope(function ($context) {
$context->setDispatcher(function ($dispatchers) {
return $dispatchers->getScriptMain();
});
});
$httpClient = $httpClients->newClient(null);
$url = javaString("http://httpbin.org/post");
$formData = [
"username" => javaString("testuser"),
"password" => javaString("secretpassword")
];
$httpClient->post($scope, $url, $formData, null)
->then(function ($s, $response) {
global $console;
$console->log(javaString("Form POST 请求成功,状态码:"), $response->getStatusCode()->getValue());
return $response->getTextBody($s);
})
->then(function ($s, $bodyText) {
global $console;
$console->log(javaString("响应体:"), $bodyText);
return $s->resolve(null);
})
->onError(function ($error) {
global $console;
$console->error(javaString("Form POST 请求失败:"), $error);
});
# 导入所需的全局变量
from m8test_java.com.m8test.script.GlobalVariables import _threads
from m8test_java.com.m8test.script.GlobalVariables import _coroutines
from m8test_java.com.m8test.script.GlobalVariables import _httpClients
from m8test_java.com.m8test.script.GlobalVariables import _console
# 确保脚本在后台运行
_threads.getMain().setBackground(True)
# 使用您指定的 scope 创建方式
scope = _coroutines.newScope(lambda it:
it.setDispatcher(lambda dispatchers: dispatchers.getScriptMain())
)
httpClient = _httpClients.newClient(None)
url = "http://httpbin.org/post"
# Groovy 的 Map [key: value] 被转换为 Python 的字典 {key: value}
formData = {
"username": "testuser",
"password": "secretpassword"
}
httpClient.post(scope, url, formData, None).then(lambda s, response: (
# _console.log 的多参数在 Python 中需要拼接为单个字符串
_console.log("Form POST 请求成功,状态码: " + str(response.getStatusCode().getValue())),
# 返回下一个链条需要的值
response.getTextBody(s)
# then 回调中包含日志和返回值,是典型的 (...)[-1] 使用场景
)[-1]).then(lambda s, bodyText: (
_console.log("响应体: " + str(bodyText)),
s.resolve(None)
# 同样,日志 + 返回值,使用 (...)[-1]
)[-1]).onError(lambda error: (
_console.error("Form POST 请求失败: " + str(error))
))
# encoding: utf-8
# 确保脚本在后台运行
$threads.getMain.setBackground(true)
scope = $coroutines.newScope(nil)
httpClient = $httpClients.newClient(nil)
url = "http://httpbin.org/post"
formData = {
"username" => "testuser",
"password" => "secretpassword"
}
httpClient.post(scope, url, formData, nil)
.then do |s, response|
$console.log("Form POST 请求成功,状态码:", response.getStatusCode.getValue)
response.getTextBody(s)
end
.then do |s, bodyText|
$console.log("响应体:", bodyText)
s.resolve(nil)
end
.onError do |error|
$console.error("Form POST 请求失败:", error)
end
现代 API 通信中最常见的方式。
import com.m8test.script.GlobalVariables.*
// 确保脚本在后台运行
_threads.getMain().setBackground(true)
val scope = _coroutines.newScope(null)
val httpClient = _httpClients.newClient(null)
val url = "http://httpbin.org/post"
// 注意:json 参数需要是一个字符串
// Kotlin 中可以使用 """ 原生字符串,或者直接字符串拼接
val jsonString = """{"name": "M8Test", "version": "1.0", "features": ["http", "automation"]}"""
httpClient.postJson(scope, url, jsonString, null)
.then { s, response ->
_console.log("JSON POST 请求成功,状态码:", response.getStatusCode().getValue())
// 获取响应头
_console.log("响应 Content-Type:", response.getHeaders()["Content-Type"])
response.getTextBody(s)
}
.then { s, bodyText ->
_console.log("响应体:", bodyText)
s.resolve(null)
}
.onError { error ->
_console.error("JSON POST 请求失败:", error)
}
// 确保脚本在后台运行
$threads.getMain().setBackground(true)
def scope = $coroutines.newScope(null)
def httpClient = $httpClients.newClient(null)
def url = "http://httpbin.org/post"
// 注意:json 参数需要是一个字符串
def jsonString = '{"name": "M8Test", "version": "1.0", "features": ["http", "automation"]}'
httpClient.postJson(scope, url, jsonString, null)
.then({ s, response ->
$console.log("JSON POST 请求成功,状态码:", response.getStatusCode().getValue())
// 获取响应头
$console.log("响应 Content-Type:", response.getHeaders()["Content-Type"])
response.getTextBody(s)
})
.then({ s, bodyText ->
$console.log("响应体:", bodyText)
s.resolve(null)
})
.onError({ error ->
$console.error("JSON POST 请求失败:", error)
})
// 确保脚本在后台运行
$threads.getMain().setBackground(true);
const scope = $coroutines.newScope(null);
const httpClient = $httpClients.newClient(null);
const url = "http://httpbin.org/post";
// 注意:json 参数需要是一个字符串
const jsonString = '{"name": "M8Test", "version": "1.0", "features": ["http", "automation"]}';
httpClient.postJson(scope, url, jsonString, null)
.then((s, response) => {
$console.log("JSON POST 请求成功,状态码:", response.getStatusCode().getValue());
// 获取响应头
$console.log("响应 Content-Type:", response.getHeaders()["Content-Type"]);
return response.getTextBody(s);
})
.then((s, bodyText) => {
$console.log("响应体:", bodyText);
return s.resolve(null);
})
.onError(error => {
$console.error("JSON POST 请求失败:", error);
});
-- 确保脚本在后台运行
_threads:getMain():setBackground(true)
local scope = _coroutines:newScope(nil)
local httpClient = _httpClients:newClient(nil)
local url = "http://httpbin.org/post"
-- 注意:json 参数需要是一个字符串
local jsonString = '{"name": "M8Test", "version": "1.0", "features": ["http", "automation"]}'
httpClient:postJson(scope, url, jsonString, nil)
:_then(function(s, response)
_console:log("JSON POST 请求成功,状态码:", response:getStatusCode():getValue())
-- 获取响应头
_console:log("响应 Content-Type:", response:getHeaders():get("Content-Type"))
return response:getTextBody(s)
end)
:_then(function(s, bodyText)
_console:log("响应体:", bodyText)
return s:resolve(nil)
end)
:onError(function(error)
if error ~= nil then
_console:error("JSON POST 请求失败:", error:stackTraceToString())
else
_console:error("JSON POST 请求失败,但未提供错误详情。")
end
end)
<?php
/** @var m8test_java\com\m8test\script\core\api\thread\Threads $threads */
global $threads;
/** @var m8test_java\com\m8test\script\core\api\coroutines\Coroutines $coroutines */
global $coroutines;
/** @var m8test_java\com\m8test\script\core\api\net\http\HttpClients $httpClients */
global $httpClients;
/** @var m8test_java\com\m8test\script\core\api\console\Console $console */
global $console;
// 确保脚本在后台运行
$threads->getMain()->setBackground(true);
$scope = $coroutines->newScope(function ($context) {
$context->setDispatcher(function ($dispatchers) {
return $dispatchers->getScriptMain();
});
});
$httpClient = $httpClients->newClient(null);
$url = javaString("http://httpbin.org/post");
// 注意:json 参数需要是一个字符串
$jsonString = javaString('{"name": "M8Test", "version": "1.0", "features": ["http", "automation"]}');
$httpClient->postJson($scope, $url, $jsonString, null)
->then(function ($s, $response) {
global $console;
$console->log(javaString("JSON POST 请求成功,状态码:"), $response->getStatusCode()->getValue());
// 获取响应头
$console->log(javaString("响应 Content-Type:"), $response->getHeaders()[javaString("Content-Type")]);
return $response->getTextBody($s);
})
->then(function ($s, $bodyText) {
global $console;
$console->log(javaString("响应体:"), $bodyText);
return $s->resolve(null);
})
->onError(function ($error) {
global $console;
$console->error(javaString("JSON POST 请求失败:"), $error);
});
# 导入所需的全局变量
from m8test_java.com.m8test.script.GlobalVariables import _threads
from m8test_java.com.m8test.script.GlobalVariables import _coroutines
from m8test_java.com.m8test.script.GlobalVariables import _httpClients
from m8test_java.com.m8test.script.GlobalVariables import _console
# 确保脚本在后台运行
_threads.getMain().setBackground(True)
# 使用您指定的 scope 创建方式
scope = _coroutines.newScope(lambda it: it.setDispatcher(lambda d: d.getScriptMain()))
httpClient = _httpClients.newClient(None)
url = "http://httpbin.org/post"
# 注意:json 参数需要是一个字符串
jsonString = '{"name": "M8Test", "version": "1.0", "features": ["http", "automation"]}'
httpClient.postJson(scope, url, jsonString, None).then(
# 这个lambda包含多个操作(两个log)和一个返回值,因此使用 (...)[-1]
lambda s, response: (
_console.log("JSON POST 请求成功,状态码:", response.getStatusCode().getValue()),
# 获取响应头
_console.log("响应 Content-Type:", response.getHeaders()["Content-Type"]),
# response.getTextBody(s) 返回一个新的 Deferred, 作为元组的最后一个元素
response.getTextBody(s)
)[-1]
).then(
# 这个lambda也包含一个log和一个返回值,同样使用 (...)[-1]
lambda s, bodyText: (
_console.log("响应体:", bodyText),
s.resolve(None)
)[-1]
).onError(
# 这个lambda只有一个操作,直接执行即可
lambda error:
_console.error("JSON POST 请求失败:", error)
)
# encoding: utf-8
# 确保脚本在后台运行
$threads.getMain.setBackground(true)
scope = $coroutines.newScope(nil)
httpClient = $httpClients.newClient(nil)
url = "http://httpbin.org/post"
# 注意:json 参数需要是一个字符串
jsonString = '{"name": "M8Test", "version": "1.0", "features": ["http", "automation"]}'
httpClient.postJson(scope, url, jsonString, nil)
.then do |s, response|
$console.log("JSON POST 请求成功,状态码:", response.getStatusCode.getValue)
# 获取响应头
$console.log("响应 Content-Type:", response.getHeaders["Content-Type"])
response.getTextBody(s)
end
.then do |s, bodyText|
$console.log("响应体:", bodyText)
s.resolve(nil)
end
.onError do |error|
$console.error("JSON POST 请求失败:", error)
end
发起 Multipart 请求(文件上传)
import com.m8test.script.GlobalVariables.*
// 确保脚本在后台运行
_threads.getMain().setBackground(true)
val scope = _coroutines.newScope(null)
// 1. 准备一个要上传的文件
// buildFile 是 Receiver 模式
val fileToUpload = _files.buildFile {
setPath(_files.getFilesDir(), "my-upload.txt")
}
fileToUpload.writeText("这是 M8Test 脚本上传的文件内容。", "UTF-8")
_console.log("准备上传的文件:", fileToUpload.getAbsolutePath())
val httpClient = _httpClients.newClient(null)
val url = "http://httpbin.org/post"
// postMultipart 的配置块通常是 Receiver 模式 (MultipartBuilder)
httpClient.postMultipart(scope, url, {
// 添加文本字段
addText("description", "这是一个文件上传测试", null)
addText("author", "M8Test", null)
// 添加文件
// name: 表单字段名
// scriptFile: 要上传的文件对象
// fileName: 在请求中显示的文件名
// fileType: 文件的MIME类型
addFile("uploadFile", fileToUpload, "user-friendly-name.txt", "text/plain", null)
}, null)
.then { s, response ->
_console.log("Multipart POST 请求成功,状态码:", response.getStatusCode().getValue())
response.getTextBody(s)
}
.then { s, bodyText ->
_console.log("响应体:", bodyText)
s.resolve(null)
}
.onError { error ->
_console.error("Multipart POST 请求失败:", error)
}
.onComplete {
// 清理临时文件
fileToUpload.delete()
_console.log("临时文件已删除。")
}
// 确保脚本在后台运行
$threads.getMain().setBackground(true)
def scope = $coroutines.newScope(null)
// 1. 准备一个要上传的文件
def fileToUpload = $files.buildFile({
it.setPath($files.getFilesDir(), "my-upload.txt")
})
fileToUpload.writeText("这是 M8Test 脚本上传的文件内容。", "UTF-8")
$console.log("准备上传的文件:", fileToUpload.getAbsolutePath())
def httpClient = $httpClients.newClient(null)
def url = "http://httpbin.org/post"
httpClient.postMultipart(scope, url, { multipart ->
// 添加文本字段
multipart.addText("description", "这是一个文件上传测试", null)
multipart.addText("author", "M8Test", null)
// 添加文件
// name: 表单字段名
// scriptFile: 要上传的文件对象
// fileName: 在请求中显示的文件名
// fileType: 文件的MIME类型
multipart.addFile("uploadFile", fileToUpload, "user-friendly-name.txt", "text/plain", null)
}, null)
.then({ s, response ->
$console.log("Multipart POST 请求成功,状态码:", response.getStatusCode().getValue())
response.getTextBody(s)
})
.then({ s, bodyText ->
$console.log("响应体:", bodyText)
s.resolve(null)
})
.onError({ error ->
$console.error("Multipart POST 请求失败:", error)
})
.onComplete({
// 清理临时文件
fileToUpload.delete()
$console.log("临时文件已删除。")
})
// 确保脚本在后台运行
$threads.getMain().setBackground(true);
const scope = $coroutines.newScope(null);
// 1. 准备一个要上传的文件
const fileToUpload = $files.buildFile(it => {
it.setPath($files.getFilesDir(), "my-upload.txt");
});
fileToUpload.writeText("这是 M8Test 脚本上传的文件内容。", "UTF-8");
$console.log("准备上传的文件:", fileToUpload.getAbsolutePath());
const httpClient = $httpClients.newClient(null);
const url = "http://httpbin.org/post";
httpClient.postMultipart(scope, url, multipart => {
// 添加文本字段
multipart.addText("description", "这是一个文件上传测试", null);
multipart.addText("author", "M8Test", null);
// 添加文件
// name: 表单字段名
// scriptFile: 要上传的文件对象
// fileName: 在请求中显示的文件名
// fileType: 文件的MIME类型
multipart.addFile("uploadFile", fileToUpload, "user-friendly-name.txt", "text/plain", null);
}, null)
.then((s, response) => {
$console.log("Multipart POST 请求成功,状态码:", response.getStatusCode().getValue());
return response.getTextBody(s);
})
.then((s, bodyText) => {
$console.log("响应体:", bodyText);
return s.resolve(null);
})
.onError(error => {
$console.error("Multipart POST 请求失败:", error);
})
.onComplete(() => {
// 清理临时文件
fileToUpload.delete();
$console.log("临时文件已删除。");
});
-- 确保脚本在后台运行
_threads:getMain():setBackground(true)
local scope = _coroutines:newScope(nil)
-- 1. 准备一个要上传的文件
local fileToUpload = _files:buildFile(function(it)
it:setPath(_files:getFilesDir(), "my-upload.txt")
end)
fileToUpload:writeText("这是 M8Test 脚本上传的文件内容。", "UTF-8")
_console:log("准备上传的文件:", fileToUpload:getAbsolutePath())
local httpClient = _httpClients:newClient(nil)
local url = "http://httpbin.org/post"
httpClient:postMultipart(scope, url, function(multipart)
-- 添加文本字段
multipart:addText("description", "这是一个文件上传测试", nil)
multipart:addText("author", "M8Test", nil)
-- 添加文件
-- name: 表单字段名
-- scriptFile: 要上传的文件对象
-- fileName: 在请求中显示的文件名
-- fileType: 文件的MIME类型
multipart:addFile("uploadFile", fileToUpload, "user-friendly-name.txt", "text/plain", nil)
end, nil)
:_then(function(s, response)
_console:log("Multipart POST 请求成功,状态码:", response:getStatusCode():getValue())
return response:getTextBody(s)
end)
:_then(function(s, bodyText)
_console:log("响应体:", bodyText)
return s:resolve(nil)
end)
:onError(function(error)
if error ~= nil then
_console:error("Multipart POST 请求失败:", error:stackTraceToString())
else
_console:error("Multipart POST 请求失败,但未提供错误详情。")
end
end)
:onComplete(function()
-- 清理临时文件
fileToUpload:delete()
_console:log("临时文件已删除。")
end)
<?php
/** @var m8test_java\com\m8test\script\core\api\thread\Threads $threads */
global $threads;
/** @var m8test_java\com\m8test\script\core\api\coroutines\Coroutines $coroutines */
global $coroutines;
/** @var m8test_java\com\m8test\script\core\api\file\Files $files */
global $files;
/** @var m8test_java\com\m8test\script\core\api\net\http\HttpClients $httpClients */
global $httpClients;
/** @var m8test_java\com\m8test\script\core\api\console\Console $console */
global $console;
// 确保脚本在后台运行
$threads->getMain()->setBackground(true);
$scope = $coroutines->newScope(function ($context) {
$context->setDispatcher(function ($dispatchers) {
return $dispatchers->getScriptMain();
});
});
// 1. 准备一个要上传的文件
$fileToUpload = $files->buildFile(function ($it) use ($files) {
$it->setPath($files->getFilesDir(), javaString("my-upload.txt"));
});
$fileToUpload->writeText(javaString("这是 M8Test 脚本上传的文件内容。"), javaString("UTF-8"));
$console->log(javaString("准备上传的文件:"), $fileToUpload->getAbsolutePath());
$httpClient = $httpClients->newClient(null);
$url = javaString("http://httpbin.org/post");
$httpClient->postMultipart($scope, $url, function ($multipart) use ($fileToUpload) {
// 添加文本字段
$multipart->addText(javaString("description"), javaString("这是一个文件上传测试"), null);
$multipart->addText(javaString("author"), javaString("M8Test"), null);
// 添加文件
// name: 表单字段名
// scriptFile: 要上传的文件对象
// fileName: 在请求中显示的文件名
// fileType: 文件的MIME类型
$multipart->addFile(javaString("uploadFile"), $fileToUpload, javaString("user-friendly-name.txt"), javaString("text/plain"), null);
}, null)
->then(function ($s, $response) {
global $console;
$console->log(javaString("Multipart POST 请求成功,状态码:"), $response->getStatusCode()->getValue());
return $response->getTextBody($s);
})
->then(function ($s, $bodyText) {
global $console;
$console->log(javaString("响应体:"), $bodyText);
return $s->resolve(null);
})
->onError(function ($error) {
global $console;
$console->error(javaString("Multipart POST 请求失败:"), $error);
})
->onComplete(function () use ($fileToUpload) {
global $console;
// 清理临时文件
$fileToUpload->delete();
$console->log(javaString("临时文件已删除。"));
});
# 导入所需的全局变量
from m8test_java.com.m8test.script.GlobalVariables import _console
from m8test_java.com.m8test.script.GlobalVariables import _coroutines
from m8test_java.com.m8test.script.GlobalVariables import _files
from m8test_java.com.m8test.script.GlobalVariables import _httpClients
from m8test_java.com.m8test.script.GlobalVariables import _threads
# 确保脚本在后台运行
_threads.getMain().setBackground(True)
# 使用我们标准的作用域创建方式
scope = _coroutines.newScope(lambda it: it.setDispatcher(lambda d: d.getScriptMain()))
# 1. 准备一个要上传的文件
fileToUpload = _files.buildFile(lambda it:
it.setPath(_files.getFilesDir(), "my-upload.txt")
)
fileToUpload.writeText("这是 M8Test 脚本上传的文件内容。", "UTF-8")
_console.log("准备上传的文件:", fileToUpload.getAbsolutePath())
httpClient = _httpClients.newClient(None)
url = "http://httpbin.org/post"
(httpClient.postMultipart(scope, url, lambda multipart: (
# 添加文本字段
multipart.addText("description", "这是一个文件上传测试", None),
multipart.addText("author", "M8Test", None),
# 添加文件
# name: 表单字段名
# scriptFile: 要上传的文件对象
# fileName: 在请求中显示的文件名
# fileType: 文件的MIME类型
multipart.addFile("uploadFile", fileToUpload, "user-friendly-name.txt", "text/plain", None)
), None).then(lambda s, response: (
# 第一个 then,需要打印日志并返回 body Deferred
_console.log("Multipart POST 请求成功,状态码:", str(response.getStatusCode().getValue())),
response.getTextBody(s)
)[-1]).then(lambda s, bodyText: (
# 第二个 then,需要打印 body 并返回最终的 resolve
_console.log("响应体:", bodyText),
s.resolve(None)
)[-1]).onError(lambda error: (
# onError 回调
_console.error("Multipart POST 请求失败:", error)
))
.onComplete(lambda: (
# 清理临时文件
fileToUpload.delete(),
_console.log("临时文件已删除。")
)))
# encoding: utf-8
# 确保脚本在后台运行
$threads.getMain.setBackground(true)
scope = $coroutines.newScope(nil)
# 1. 准备一个要上传的文件
fileToUpload = $files.buildFile do |it|
it.setPath($files.getFilesDir, "my-upload.txt")
end
fileToUpload.writeText("这是 M8Test 脚本上传的文件内容。", "UTF-8")
$console.log("准备上传的文件:", fileToUpload.getAbsolutePath)
httpClient = $httpClients.newClient(nil)
url = "http://httpbin.org/post"
httpClient.postMultipart(scope, url, proc do |multipart|
# 添加文本字段
multipart.addText("description", "这是一个文件上传测试", nil)
multipart.addText("author", "M8Test", nil)
# 添加文件
# name: 表单字段名
# scriptFile: 要上传的文件对象
# fileName: 在请求中显示的文件名
# fileType: 文件的MIME类型
multipart.addFile("uploadFile", fileToUpload, "user-friendly-name.txt", "text/plain", nil)
end, nil)
.then do |s, response|
$console.log("Multipart POST 请求成功,状态码:", response.getStatusCode.getValue)
response.getTextBody(s)
end
.then do |s, bodyText|
$console.log("响应体:", bodyText)
s.resolve(nil)
end
.onError do |error|
$console.error("Multipart POST 请求失败:", error)
end
.onComplete do
# 清理临时文件
fileToUpload.delete
$console.log("临时文件已删除。")
end
使用通用的 request 方法
当需要使用 GET/POST 之外的 HTTP 方法(如 PUT, DELETE, PATCH)时,可以使用 request 方法。
import com.m8test.script.GlobalVariables.*
// 确保脚本在后台运行
_threads.getMain().setBackground(true)
val scope = _coroutines.newScope(null)
val httpClient = _httpClients.newClient(null)
// 示例:发起一个 PUT 请求
val putUrl = "http://httpbin.org/put"
val putData = """{"id": 1, "status": "updated"}"""
// request 配置块通常是 Receiver 模式
httpClient.request(scope, putUrl) {
// 必须设置请求方法
setMethod { it.getPut() }
// 设置请求体
setJsonBody(putData)
// 设置超时(可选)
setTimeout {
setRequestTimeout(5000) // 5秒总超时
setConnectTimeout(2000) // 2秒连接超时
}
}
.then { s, response ->
_console.log("PUT 请求成功,状态码:", response.getStatusCode().getValue())
response.getTextBody(s)
}
.then { s, bodyText ->
_console.log("响应体:", bodyText)
s.resolve(null)
}
.onError { error ->
_console.error("PUT 请求失败:", error)
}
// 确保脚本在后台运行
$threads.getMain().setBackground(true)
def scope = $coroutines.newScope(null)
def httpClient = $httpClients.newClient(null)
// 示例:发起一个 PUT 请求
def putUrl = "http://httpbin.org/put"
def putData = '{"id": 1, "status": "updated"}'
httpClient.request(scope, putUrl, { request ->
// 必须设置请求方法
request.setMethod({ methods -> methods.getPut() })
// 设置请求体
request.setJsonBody(putData)
// 设置超时(可选)
request.setTimeout({ timeout ->
timeout.setRequestTimeout(5000) // 5秒总超时
timeout.setConnectTimeout(2000) // 2秒连接超时
})
})
.then({ s, response ->
$console.log("PUT 请求成功,状态码:", response.getStatusCode().getValue())
response.getTextBody(s)
})
.then({ s, bodyText ->
$console.log("响应体:", bodyText)
s.resolve(null)
})
.onError({ error ->
$console.error("PUT 请求失败:", error)
})
// 确保脚本在后台运行
$threads.getMain().setBackground(true);
const scope = $coroutines.newScope(null);
const httpClient = $httpClients.newClient(null);
// 示例:发起一个 PUT 请求
const putUrl = "http://httpbin.org/put";
const putData = '{"id": 1, "status": "updated"}';
httpClient.request(scope, putUrl, request => {
// 必须设置请求方法
request.setMethod(methods => methods.getPut());
// 设置请求体
request.setJsonBody(putData);
// 设置超时(可选)
request.setTimeout(timeout => {
timeout.setRequestTimeout(5000); // 5秒总超时
timeout.setConnectTimeout(2000); // 2秒连接超时
});
})
.then((s, response) => {
$console.log("PUT 请求成功,状态码:", response.getStatusCode().getValue());
return response.getTextBody(s);
})
.then((s, bodyText) => {
$console.log("响应体:", bodyText);
return s.resolve(null);
})
.onError(error => {
$console.error("PUT 请求失败:", error);
});
-- 确保脚本在后台运行
_threads:getMain():setBackground(true)
local scope = _coroutines:newScope(nil)
local httpClient = _httpClients:newClient(nil)
-- 示例:发起一个 PUT 请求
local putUrl = "http://httpbin.org/put"
local putData = '{"id": 1, "status": "updated"}'
httpClient:request(scope, putUrl, function(request)
-- 必须设置请求方法
request:setMethod(function(methods) return methods:getPut() end)
-- 设置请求体
request:setJsonBody(putData)
-- 设置超时(可选)
request:setTimeout(function(timeout)
timeout:setRequestTimeout(5000) -- 5秒总超时
timeout:setConnectTimeout(2000) -- 2秒连接超时
end)
end)
:_then(function(s, response)
_console:log("PUT 请求成功,状态码:", response:getStatusCode():getValue())
return response:getTextBody(s)
end)
:_then(function(s, bodyText)
_console:log("响应体:", bodyText)
return s:resolve(nil)
end)
:onError(function(error)
if error ~= nil then
_console:error("PUT 请求失败:", error:stackTraceToString())
else
_console:error("PUT 请求失败,但未提供错误详情。")
end
end)
<?php
/** @var m8test_java\com\m8test\script\core\api\thread\Threads $threads */
global $threads;
/** @var m8test_java\com\m8test\script\core\api\coroutines\Coroutines $coroutines */
global $coroutines;
/** @var m8test_java\com\m8test\script\core\api\net\http\HttpClients $httpClients */
global $httpClients;
/** @var m8test_java\com\m8test\script\core\api\console\Console $console */
global $console;
// 确保脚本在后台运行
$threads->getMain()->setBackground(true);
$scope = $coroutines->newScope(function ($context) {
$context->setDispatcher(function ($dispatchers) {
return $dispatchers->getScriptMain();
});
});
$httpClient = $httpClients->newClient(null);
// 示例:发起一个 PUT 请求
$putUrl = javaString("http://httpbin.org/put");
$putData = javaString('{"id": 1, "status": "updated"}');
$httpClient->request($scope, $putUrl, function ($request) use ($putData) {
// 必须设置请求方法
$request->setMethod(function ($methods) {
return $methods->getPut();
});
// 设置请求体
$request->setJsonBody($putData);
// 设置超时(可选)
$request->setTimeout(function ($timeout) {
$timeout->setRequestTimeout(5000); // 5秒总超时
$timeout->setConnectTimeout(2000); // 2秒连接超时
});
})
->then(function ($s, $response) {
global $console;
$console->log(javaString("PUT 请求成功,状态码:"), $response->getStatusCode()->getValue());
return $response->getTextBody($s);
})
->then(function ($s, $bodyText) {
global $console;
$console->log(javaString("响应体:"), $bodyText);
return $s->resolve(null);
})
->onError(function ($error) {
global $console;
$console->error(javaString("PUT 请求失败:"), $error->stackTraceToString());
});
# 导入所需的全局变量
from m8test_java.com.m8test.script.GlobalVariables import _threads
from m8test_java.com.m8test.script.GlobalVariables import _coroutines
from m8test_java.com.m8test.script.GlobalVariables import _httpClients
from m8test_java.com.m8test.script.GlobalVariables import _console
# 确保脚本在后台运行
_threads.getMain().setBackground(True)
# 使用您指定的 scope 创建方式
scope = _coroutines.newScope(lambda it:
it.setDispatcher(lambda dispatchers: dispatchers.getScriptMain())
)
httpClient = _httpClients.newClient(None)
# 示例:发起一个 PUT 请求
putUrl = "http://httpbin.org/put"
putData = '{"id": 1, "status": "updated"}'
httpClient.request(scope, putUrl, lambda request: (
# 必须设置请求方法
request.setMethod(lambda methods: methods.getPut()),
# 设置请求体
request.setJsonBody(putData),
# 设置超时(可选)
request.setTimeout(lambda timeout: (
timeout.setRequestTimeout(5000), # 5秒总超时
timeout.setConnectTimeout(2000) # 2秒连接超时
))
)).then(lambda s, response: (
# 使用多个参数传递给 log 方法,避免因类型问题导致的拼接错误
_console.log("PUT 请求成功,状态码:", response.getStatusCode().getValue()),
# 返回下一个 Deferred
response.getTextBody(s)
# 此处有两个表达式(log 和 getTextBody),所以使用 (...)[-1] 是正确的
)[-1]).then(lambda s, bodyText: (
_console.log("响应体:", bodyText),
s.resolve(None)
# 此处有两个表达式(log 和 resolve),所以使用 (...)[-1] 是正确的
)[-1]).onError(lambda error: (
_console.error("PUT 请求失败:", error)
))
# encoding: utf-8
# 确保脚本在后台运行
$threads.getMain.setBackground(true)
scope = $coroutines.newScope(nil)
httpClient = $httpClients.newClient(nil)
# 示例:发起一个 PUT 请求
putUrl = "http://httpbin.org/put"
putData = '{"id": 1, "status": "updated"}'
httpClient.request(scope, putUrl) do |request|
# 必须设置请求方法
request.setMethod { |methods| methods.getPut }
# 设置请求体
request.setJsonBody(putData)
# 设置超时(可选)
request.setTimeout do |timeout|
timeout.setRequestTimeout(5000) # 5秒总超时
timeout.setConnectTimeout(2000) # 2秒连接超时
end
end.then do |s, response|
$console.log("PUT 请求成功,状态码:", response.getStatusCode.getValue)
response.getTextBody(s)
end.then do |s, bodyText|
$console.log("响应体:", bodyText)
s.resolve(nil)
end.onError do |error|
$console.error("PUT 请求失败:", error.stackTraceToString())
end
处理 WebSocket 连接
客户端可以与 WebSocket 服务器建立持久的双向连接。
import com.m8test.script.GlobalVariables.*
// 确保脚本在后台运行
_threads.getMain().setBackground(true)
val scope = _coroutines.newScope(null)
val httpClient = _httpClients.newClient(null)
val wsUrl = "wss://echo.websocket.org/" // 使用一个公共的 WebSocket Echo 服务器
_console.log("正在尝试连接 WebSocket...")
// webSocket 方法现在返回一个 Job,代表整个 WebSocket 会话的生命周期
// 注意:这里删除了 { session -> ... } 的参数,直接在 lambda 中使用 this (WebSocketSession)
val wsJob = httpClient.webSocket(scope, wsUrl, null) {
// 设置事件监听器
setOnOpen { s ->
_console.info("WebSocket 连接已建立!")
// 连接成功后发送一条消息, sendText 返回一个 Job
sendText(s, "你好,WebSocket 服务器!来自 M8Test。")
}
setOnText { s, text ->
_console.log("收到文本消息:", text)
// 收到消息 1 秒后关闭连接
// currentScope.delay(毫秒) 返回一个 Deferred<Unit>
scope.delay(1000).then { ss, v ->
// close 返回的是 Job,而 then 需要返回 Deferred
// 使用 toDeferred() 将 Job 转换为 Deferred<Unit>
close(ss, 1000, "测试完成").toDeferred()
}
}
setOnClose { s, reason ->
_console.warn("WebSocket 连接已关闭。原因:", reason?.getCode(), reason?.getMessage())
// 返回一个已完成的 Deferred (也是 Job)
s.resolve(null)
}
setOnError { s, error ->
_console.error("WebSocket 出现错误:", error)
s.resolve(null)
}
}
// 监听 Job 的完成事件
wsJob.invokeOnCompletion { cause ->
if (cause != null) {
_console.error("WebSocket 会话 Job 异常结束:", cause)
} else {
_console.log("WebSocket 会话 Job 正常完成。")
}
}
// 确保脚本在后台运行
$threads.getMain().setBackground(true)
def scope = $coroutines.newScope(null)
def httpClient = $httpClients.newClient(null)
def wsUrl = "wss://echo.websocket.org/" // 使用一个公共的 WebSocket Echo 服务器
$console.log("正在尝试连接 WebSocket...")
// webSocket 方法现在返回一个 Job,代表整个 WebSocket 会话的生命周期
def wsJob = httpClient.webSocket(scope, wsUrl, null, { session ->
// 设置事件监听器
session.setOnOpen({ s ->
$console.info("WebSocket 连接已建立!")
// 连接成功后发送一条消息, sendText 返回一个 Job
session.sendText(s, "你好,WebSocket 服务器!来自 M8Test。")
})
session.setOnText({ s, text ->
$console.log("收到文本消息:", text)
// 收到消息 1 秒后关闭连接
scope.delay(1000).then({ ss, v ->
session.close(ss, 1000, "测试完成")
})
})
session.setOnClose({ s, reason ->
$console.warn("WebSocket 连接已关闭。原因:", reason?.getCode(), reason?.getMessage())
// 返回一个已完成的 Job
s.resolve(null)
})
session.setOnError({ s, error ->
$console.error("WebSocket 出现错误:", error)
s.resolve(null)
})
})
// 监听 Job 的完成事件
wsJob.invokeOnCompletion({ cause ->
if (cause != null) {
$console.error("WebSocket 会话 Job 异常结束:", cause)
} else {
$console.log("WebSocket 会话 Job 正常完成。")
}
})
// 确保脚本在后台运行
$threads.getMain().setBackground(true);
const scope = $coroutines.newScope(null);
const httpClient = $httpClients.newClient(null);
const wsUrl = "wss://echo.websocket.org/"; // 使用一个公共的 WebSocket Echo 服务器
$console.log("正在尝试连接 WebSocket...");
// webSocket 方法现在返回一个 Job,代表整个 WebSocket 会话的生命周期
const wsJob = httpClient.webSocket(scope, wsUrl, null, session => {
// 设置事件监听器
session.setOnOpen(s => {
$console.info("WebSocket 连接已建立!");
// 连接成功后发送一条消息, sendText 返回一个 Job,必须返回
return session.sendText(s, "你好,WebSocket 服务器!来自 M8Test。");
});
session.setOnText((s, text) => {
$console.log("收到文本消息:", text);
// 收到消息 1 秒后关闭连接,整个链会返回一个 Job
return scope.delay(1000).then((ss, v) => {
return session.close(ss, 1000, "测试完成");
});
});
session.setOnClose((s, reason) => {
$console.warn("WebSocket 连接已关闭。原因:", reason?.getCode(), reason?.getMessage());
// 返回一个已完成的 Job
return s.resolve(null);
});
session.setOnError((s, error) => {
$console.error("WebSocket 出现错误:", error);
return s.resolve(null);
});
});
// 监听 Job 的完成事件
wsJob.invokeOnCompletion(cause => {
if (cause != null) {
$console.error("WebSocket 会话 Job 异常结束:", cause);
} else {
$console.log("WebSocket 会话 Job 正常完成。");
}
});
-- 确保脚本在后台运行
_threads:getMain():setBackground(true)
local scope = _coroutines:newScope(nil)
local httpClient = _httpClients:newClient(nil)
local wsUrl = "wss://echo.websocket.org/" -- 使用一个公共的 WebSocket Echo 服务器
_console:log("正在尝试连接 WebSocket...")
-- webSocket 方法现在返回一个 Job,代表整个 WebSocket 会话的生命周期
local wsJob = httpClient:webSocket(scope, wsUrl, nil, function(session)
-- 设置事件监听器
session:setOnOpen(function(s)
_console:info("WebSocket 连接已建立!")
-- 连接成功后发送一条消息, sendText 返回一个 Job
return session:sendText(s, "你好,WebSocket 服务器!来自 M8Test。")
end)
session:setOnText(function(s, text)
_console:log("收到文本消息:", text)
-- 收到消息 1 秒后关闭连接
return scope:delay(1000):_then(function(ss, v)
return session:close(ss, 1000, "测试完成")
end)
end)
session:setOnClose(function(s, reason)
_console:warn("WebSocket 连接已关闭。原因:", reason and reason:getCode(), reason and reason:getMessage())
-- 返回一个已完成的 Job
return s:resolve(nil)
end)
session:setOnError(function(s, error)
-- 修正:添加 nil 检查
if error ~= nil then
_console:error("WebSocket 出现错误:", error:stackTraceToString())
else
_console:error("WebSocket 出现未知错误。")
end
return s:resolve(nil)
end)
end)
-- 监听 Job 的完成事件
wsJob:invokeOnCompletion(function(cause)
if cause ~= nil then
_console:error("WebSocket 会话 Job 异常结束:", cause:stackTraceToString())
else
_console:log("WebSocket 会话 Job 正常完成。")
end
end)
<?php
/** @var m8test_java\com\m8test\script\core\api\thread\Threads $threads */
global $threads;
/** @var m8test_java\com\m8test\script\core\api\coroutines\Coroutines $coroutines */
global $coroutines;
/** @var m8test_java\com\m8test\script\core\api\net\http\HttpClients $httpClients */
global $httpClients;
/** @var m8test_java\com\m8test\script\core\api\console\Console $console */
global $console;
// 确保脚本在后台运行
$threads->getMain()->setBackground(true);
$scope = $coroutines->newScope(function ($context) {
$context->setDispatcher(function ($dispatchers) {
return $dispatchers->getScriptMain();
});
});
$httpClient = $httpClients->newClient(null);
$wsUrl = javaString("wss://echo.websocket.org/"); // 使用一个公共的 WebSocket Echo 服务器
$console->log(javaString("正在尝试连接 WebSocket..."));
// webSocket 方法现在返回一个 Job,代表整个 WebSocket 会话的生命周期
$wsJob = $httpClient->webSocket($scope, $wsUrl, null, function ($session) use ($scope) {
global $console;
// 设置事件监听器
$session->setOnOpen(function ($s) use ($session) {
global $console;
$console->info(javaString("WebSocket 连接已建立!"));
// 连接成功后发送一条消息, sendText 返回一个 Job
// [修正] 必须返回 Job 对象
return $session->sendText($s, javaString("你好,WebSocket 服务器!来自 M8Test。"));
});
$session->setOnText(function ($s, $text) use ($scope, $session) {
global $console;
$console->log(javaString("收到文本消息:"), $text);
// 收到消息 1 秒后关闭连接
// [修正] 必须返回 Job 对象
return $scope->delay(1000)->then(function ($ss, $v) use ($session) {
return $session->close($ss, 1000, javaString("测试完成"));
});
});
$session->setOnClose(function ($s, $reason) {
global $console;
$console->warn(javaString("WebSocket 连接已关闭。原因:"), $reason ? $reason->getCode() : null, $reason ? $reason->getMessage() : null);
// 返回一个已完成的 Job
return $s->resolve(null);
});
$session->setOnError(function ($s, $error) {
global $console;
$console->error(javaString("WebSocket 出现错误:"), $error);
return $s->resolve(null);
});
});
// 监听 Job 的完成事件
$wsJob->invokeOnCompletion(function ($cause) {
global $console;
if ($cause != null) {
$console->error(javaString("WebSocket 会话 Job 异常结束:"), $cause);
} else {
$console->log(javaString("WebSocket 会话 Job 正常完成。"));
}
});
# 导入所需的全局变量
from m8test_java.com.m8test.script.GlobalVariables import _console
from m8test_java.com.m8test.script.GlobalVariables import _coroutines
from m8test_java.com.m8test.script.GlobalVariables import _httpClients
from m8test_java.com.m8test.script.GlobalVariables import _threads
# 确保脚本在后台运行
_threads.getMain().setBackground(True)
# 使用本脚本指定的 scope 和 httpClient 创建方式
scope = _coroutines.newScope(lambda it:
it.setDispatcher(lambda dispatchers: dispatchers.getScriptMain())
)
httpClient = _httpClients.newClient(None)
wsUrl = "wss://echo.websocket.org/" # 使用一个公共的 WebSocket Echo 服务器
_console.log("正在尝试连接 WebSocket...")
# webSocket 方法现在返回一个 Job,代表整个 WebSocket 会话的生命周期
wsJob = httpClient.webSocket(scope, wsUrl, None, lambda session: (
# 设置事件监听器
session.setOnOpen(lambda s: (
_console.info("WebSocket 连接已建立!"),
# 连接成功后发送一条消息, sendText 返回一个 Job
session.sendText(s, "你好,WebSocket 服务器!来自 M8Test。")
)[-1]),
session.setOnText(lambda s, text: (
_console.log("收到文本消息:", text),
# 收到消息 1 秒后关闭连接
# 这里的 then 回调是单表达式,直接返回值
scope.delay(1000).then(lambda ss, v:
session.close(s, 1000, "测试完成")
)
)[-1]),
session.setOnClose(lambda s, reason: (
# Groovy 的 reason?.getCode() 转换为 Python 的条件表达式
_console.warn("WebSocket 连接已关闭。原因:",
reason.getCode() if reason is not None else None,
reason.getMessage() if reason is not None else None),
# 返回一个已完成的 Job
s.resolve(None)
)[-1]),
session.setOnError(lambda s, error: (
_console.error("WebSocket 出现错误:", error),
s.resolve(None)
)[-1])
))
# 监听 Job 的完成事件
wsJob.invokeOnCompletion(lambda cause: (
# 使用 Python 条件表达式处理 if/else
_console.error("WebSocket 会话 Job 异常结束:", cause) if cause is not None else
_console.log("WebSocket 会话 Job 正常完成。")
))
# encoding: utf-8
# 确保脚本在后台运行
$threads.getMain.setBackground(true)
scope = $coroutines.newScope(nil)
httpClient = $httpClients.newClient(nil)
wsUrl = "wss://echo.websocket.org/" # 使用一个公共的 WebSocket Echo 服务器
$console.log("正在尝试连接 WebSocket...")
# webSocket 方法现在返回一个 Job,代表整个 WebSocket 会话的生命周期
wsJob = httpClient.webSocket(scope, wsUrl, nil) do |session|
# 设置事件监听器
session.setOnOpen do |s|
$console.info("WebSocket 连接已建立!")
# 连接成功后发送一条消息, sendText 返回一个 Job
session.sendText(s, "你好,WebSocket 服务器!来自 M8Test。")
end
session.setOnText do |s, text|
$console.log("收到文本消息:", text)
# 收到消息 1 秒后关闭连接
scope.delay(1000).then do |ss, v|
session.close(ss, 1000, "测试完成")
end
end
session.setOnClose do |s, reason|
# 修复 Ruby 版本较低不支持 &. 操作符的问题,改为三元运算符检查 nil
code = reason ? reason.getCode : nil
message = reason ? reason.getMessage : nil
$console.warn("WebSocket 连接已关闭。原因:", code, message)
# 返回一个已完成的 Job
s.resolve(nil)
end
session.setOnError do |s, error|
$console.error("WebSocket 出现错误:", error)
s.resolve(nil)
end
end
# 监听 Job 的完成事件
wsJob.invokeOnCompletion do |cause|
if cause != nil
$console.error("WebSocket 会话 Job 异常结束:", cause)
else
$console.log("WebSocket 会话 Job 正常完成。")
end
end
大文件处理
大文件下载
若需下载大文件,请勿直接使用 get 方法——此举易致内存溢出。应改用 prepareGet 方法,结合通道流式下载,方可稳妥处理大文件。
import com.m8test.script.GlobalVariables.*
val scope = _coroutines.newScope {}
val client = _httpClients.newClient { }
// 创建文件,如果存在先删除
val file = _files.buildFile {
setPath("/sdcard/M8Test/tmp", "coolapk.apk")
}
file.mkdirs()
file.deleteRecursively()
// 下载一个apk, 使用通道保存数据
client.prepareGet(scope, "https://dl.coolapk.com/down?pn=com.coolapk.market&id=NDU5OQ&h=46bb9d98&from=from-web", null) { }
.then { s1, statement ->
statement.execute(s1) { s2, response ->
response.getByteReadChannelBody(s2).then { s3, channel ->
// 为文件创建一个写入通道
val writeChannel = _files.toWriteChannel(file, null)
// 将服务器返回的数据写入文件
channel.copyToByteWriteChannel(s3, writeChannel, null)
.then { s4, length ->
_console.log("文件大小为:", (length / 1024 / 1024), "M")
// 数据写入后就关闭文件通道,writeChannel由用户创建,所以需要自行取消,channel由系统创建,由系统管理不需要取消
writeChannel.flushAndClose(s4)
}
}
}
}
.then { s3, data ->
_console.log("下载完成")
s3.resolve(null)
}
.onError { e ->
_console.error("下载失败")
}
_threads.getMain().setBackground(true)
def scope = $coroutines.newScope {}
def client = $httpClients.newClient { hcc -> }
// 创建文件,如果存在先删除
def file = $files.buildFile { fb ->
fb.setPath("/sdcard/M8Test/tmp", "coolapk.apk")
}
file.mkdirs()
file.deleteRecursively()
// 下载一个apk, 使用通道保存数据
client.prepareGet(scope, "https://dl.coolapk.com/down?pn=com.coolapk.market&id=NDU5OQ&h=46bb9d98&from=from-web", null) { request -> }
.then { s1, statement ->
return statement.execute(s1) { s2, response ->
return response.getByteReadChannelBody(s2).then { s3, channel ->
// 为文件创建一个写入通道
def writeChannel = $files.toWriteChannel(file, null)
// 将服务器返回的数据写入文件
return channel.copyToByteWriteChannel(s3, writeChannel, null)
.then { s4, length ->
$console.log("文件大小为:", (length / 1024 / 1024), "M")
// 数据写入后就关闭文件通道,writeChannel由用户创建,所以需要自行取消,channel由系统创建,由系统管理不需要取消
return writeChannel.flushAndClose(s4)
}
}
}
}
.then { s3, data ->
$console.log("下载完成")
s3.resolve(null)
}
.onError { e ->
$console.error("下载失败")
}
$threads.getMain().setBackground(true)
const scope = $coroutines.newScope(() => {});
const client = $httpClients.newClient(hcc => {});
// 创建文件,如果存在先删除
const file = $files.buildFile(fb => {
fb.setPath("/sdcard/M8Test/tmp", "coolapk.apk");
});
file.mkdirs();
file.deleteRecursively();
// 下载一个apk, 使用通道保存数据
client.prepareGet(scope, "https://dl.coolapk.com/down?pn=com.coolapk.market&id=NDU5OQ&h=46bb9d98&from=from-web", null, request => {})
.then((s1, statement) => {
return statement.execute(s1, (s2, response) => {
return response.getByteReadChannelBody(s2).then((s3, channel) => {
// 为文件创建一个写入通道
const writeChannel = $files.toWriteChannel(file, null);
// 将服务器返回的数据写入文件
return channel.copyToByteWriteChannel(s3, writeChannel, null)
.then((s4, length) => {
$console.log("文件大小为:", (length / 1024 / 1024), "M");
// 数据写入后就关闭文件通道,writeChannel由用户创建,所以需要自行取消,channel由系统创建,由系统管理不需要取消
return writeChannel.flushAndClose(s4);
});
});
});
})
.then((s3, data) => {
$console.log("下载完成");
return s3.resolve(null);
})
.onError(e => {
$console.error("下载失败");
});
$threads.getMain().setBackground(true);
local scope = _coroutines:newScope(function() end)
local client = _httpClients:newClient(function(hcc) end)
-- 创建文件,如果存在先删除
local file = _files:buildFile(function(fb)
fb:setPath("/sdcard/M8Test/tmp", "coolapk.apk")
end)
file:mkdirs()
file:deleteRecursively()
-- 下载一个apk, 使用通道保存数据
client:prepareGet(scope, "https://dl.coolapk.com/down?pn=com.coolapk.market&id=NDU5OQ&h=46bb9d98&from=from-web", nil, function(request) end)
:_then(function(s1, statement)
return statement:execute(s1, function(s2, response)
return response:getByteReadChannelBody(s2):_then(function(s3, channel)
-- 为文件创建一个写入通道
local writeChannel = _files:toWriteChannel(file, nil)
-- 将服务器返回的数据写入文件
return channel:copyToByteWriteChannel(s3, writeChannel, nil)
:_then(function(s4, length)
_console:log("文件大小为:", (length / 1024 / 1024), "M")
-- 数据写入后就关闭文件通道,writeChannel由用户创建,所以需要自行取消,channel由系统创建,由系统管理不需要取消
return writeChannel:flushAndClose(s4)
end)
end)
end)
end)
:_then(function(s3, data)
_console:log("下载完成")
return s3:resolve(nil)
end)
:onError(function(e)
if e ~= nil then
_console:error("下载失败:", e:stackTraceToString())
else
_console:error("下载失败,但未提供错误详情。")
end
end)
_threads:getMain():setBackground(true)
<?php
/** @var m8test_java\com\m8test\script\core\api\coroutines\Coroutines $coroutines */
global $coroutines;
/** @var m8test_java\com\m8test\script\core\api\net\http\HttpClients $httpClients */
global $httpClients;
/** @var m8test_java\com\m8test\script\core\api\file\Files $files */
global $files;
/** @var m8test_java\com\m8test\script\core\api\console\Console $console */
global $console;
/** @var m8test_java\com\m8test\script\core\api\thread\Threads $threads */
global $threads;
$scope = $coroutines->newScope(function ($context) {
$context->setDispatcher(function ($dispatchers) {
return $dispatchers->getScriptMain();
});
});
$client = $httpClients->newClient(function ($hcc) {});
// 创建文件,如果存在先删除
$file = $files->buildFile(function ($fb) {
$fb->setPath(javaString("/sdcard/M8Test/tmp"), javaString("coolapk.apk"));
});
$file->mkdirs();
$file->deleteRecursively();
// 下载一个apk, 使用通道保存数据
$client->prepareGet($scope, javaString("https://dl.coolapk.com/down?pn=com.coolapk.market&id=NDU5OQ&h=46bb9d98&from=from-web"), null, function ($request) {})
->then(function ($s1, $statement) use ($file) {
return $statement->execute($s1, function ($s2, $response) use ($file) {
return $response->getByteReadChannelBody($s2)->then(function ($s3, $channel) use ($file) {
global $files;
// 为文件创建一个写入通道
$writeChannel = $files->toWriteChannel($file, null);
// 将服务器返回的数据写入文件
return $channel->copyToByteWriteChannel($s3, $writeChannel, null)
->then(function ($s4, $length) use ($writeChannel) {
global $console;
$console->log(javaString("文件大小为:"), ($length / 1024 / 1024), javaString("M"));
// 数据写入后就关闭文件通道,writeChannel由用户创建,所以需要自行取消,channel由系统创建,由系统管理不需要取消
return $writeChannel->flushAndClose($s4);
});
});
});
})
->then(function ($s3, $data) {
global $console;
$console->log(javaString("下载完成"));
return $s3->resolve(null);
})
->onError(function ($e) {
global $console;
$console->error(javaString("下载失败"));
});
$threads->getMain()->setBackground(true);
# 导入所需的全局变量
from m8test_java.com.m8test.script.GlobalVariables import _console
from m8test_java.com.m8test.script.GlobalVariables import _coroutines
from m8test_java.com.m8test.script.GlobalVariables import _files
from m8test_java.com.m8test.script.GlobalVariables import _httpClients
from m8test_java.com.m8test.script.GlobalVariables import _threads
# 使用空 lambda 对应 Groovy 的空闭包 {}
scope = _coroutines.newScope(lambda it: it.setDispatcher(lambda d: d.getScriptMain()))
client = _httpClients.newClient(lambda hcc: None)
# 创建文件,如果存在先删除
file = _files.buildFile(lambda fb:
fb.setPath("/sdcard/M8Test/tmp", "coolapk.apk")
)
file.mkdirs()
file.deleteRecursively()
# 下载一个apk, 使用通道保存数据
client.prepareGet(scope, "https://dl.coolapk.com/down?pn=com.coolapk.market&id=NDU5OQ&h=46bb9d98&from=from-web", None,
lambda request: None) \
.then(lambda s1, statement:
# 这个 lambda 的唯一任务是执行 statement.execute 并返回其结果 (一个 Deferred)
# 这是一个单行返回,所以直接返回值,不使用 (...)[-1]
statement.execute(s1, lambda s2, response:
# 同样,这个 lambda 的唯一任务是调用 getByteReadChannelBody 并返回其结果 (一个 Deferred)
response.getByteReadChannelBody(s2).then(lambda s3, channel: (
# 为文件创建一个写入通道
(writeChannel := _files.toWriteChannel(file, None)),
# 将服务器返回的数据写入文件,并返回 copyTo 的结果 (一个 Deferred)
channel.copyToByteWriteChannel(s3, writeChannel, None)
.then(lambda s4, length: (
# log 和 flushAndClose 是两个操作,使用 (...)[-1]
_console.log("文件大小为:", (length / 1024 / 1024), "M"),
# 数据写入后就关闭文件通道,writeChannel由用户创建,所以需要自行取消,channel由系统创建,由系统管理不需要取消
writeChannel.flushAndClose(s4)
)[-1])
)[-1])
)
) \
.then(lambda s3, data: (
# log 和 resolve 是两个操作,使用 (...)[-1]
_console.log("下载完成"),
s3.resolve(None)
)[-1]) \
.onError(lambda e:
_console.error("下载失败")
)
_threads.getMain().setBackground(True)
# encoding: utf-8
scope = $coroutines.newScope { }
client = $httpClients.newClient { |hcc| }
# 创建文件,如果存在先删除
file = $files.buildFile do |fb|
fb.setPath("/sdcard/M8Test/tmp", "coolapk.apk")
end
file.mkdirs
file.deleteRecursively
# 下载一个apk, 使用通道保存数据
client.prepareGet(scope, "https://dl.coolapk.com/down?pn=com.coolapk.market&id=NDU5OQ&h=46bb9d98&from=from-web", nil) { |request| }
.then do |s1, statement|
statement.execute(s1) do |s2, response|
response.getByteReadChannelBody(s2).then do |s3, channel|
# 为文件创建一个写入通道
writeChannel = $files.toWriteChannel(file, nil)
# 将服务器返回的数据写入文件
channel.copyToByteWriteChannel(s3, writeChannel, nil)
.then do |s4, length|
$console.log("文件大小为:", (length / 1024 / 1024), "M")
# 数据写入后就关闭文件通道,writeChannel由用户创建,所以需要自行取消,channel由系统创建,由系统管理不需要取消
writeChannel.flushAndClose(s4)
end
end
end
end
.then do |s3, data|
$console.log("下载完成")
s3.resolve(nil)
end
.onError do |e|
$console.error("下载失败")
end
$threads.getMain.setBackground(true)
如需实时查看下载进度,可参考下方示例:
import com.m8test.script.GlobalVariables.*
val scope = _coroutines.newScope {}
val client = _httpClients.newClient { }
// 创建文件,如果存在先删除
val file = _files.buildFile {
setPath("/sdcard/M8Test/tmp", "coolapk.apk")
}
file.mkdirs()
file.deleteRecursively()
// 下载一个apk, 使用通道保存数据
client.prepareGet(scope, "https://dl.coolapk.com/down?pn=com.coolapk.market&id=NDU5OQ&h=46bb9d98&from=from-web", null) { }
.then { s1, statement ->
statement.execute(s1) { s2, response ->
response.getByteReadChannelBody(s2).then { s3, channel ->
var current = 0L // Long type
val buffer = _arrays.newByteArray(8192)
// 为文件创建一个写入通道
val writeChannel = _files.toWriteChannel(file, null)
s3.loop { s4 ->
// 如果通道已经关闭直接退出循环
if (channel.isClosedForRead()) {
return@loop s4.resolve(false)
}
channel.readAvailable(s4, buffer, 0, buffer.size).then { s5, length ->
if (length <= 0) {
return@then s5.resolve(false)
}
current += length
// 下面注释掉,因为可能会非常频繁输出日志
// _console.log("下载进度:${current / 1024 / 1024}M")
writeChannel.writeFully(s5, buffer, 0, length).then { s6, data -> s6.resolve(true) }
}
}.then { s, data ->
// loop 循环结束,关闭写入通道, 这一步必须有,否则可能会造成文件损坏
writeChannel.flushAndClose(s)
}
}
}
}
.then { s3, data ->
_console.log("下载完成")
s3.resolve(null)
}
.onError { e ->
_console.error("下载失败")
}
_threads.getMain().setBackground(true)
def scope = $coroutines.newScope {}
def client = $httpClients.newClient { hcc -> }
// 创建文件,如果存在先删除
def file = $files.buildFile { fb ->
fb.setPath("/sdcard/M8Test/tmp", "coolapk.apk")
}
file.mkdirs()
file.deleteRecursively()
// 下载一个apk, 使用通道保存数据
client.prepareGet(scope, "https://dl.coolapk.com/down?pn=com.coolapk.market&id=NDU5OQ&h=46bb9d98&from=from-web", null) { request -> }
.then { s1, statement ->
return statement.execute(s1) { s2, response ->
return response.getByteReadChannelBody(s2).then { s3, channel ->
def current = 0
def buffer = $arrays.newByteArray(8192)
// 为文件创建一个写入通道
def writeChannel = $files.toWriteChannel(file, null)
return s3.loop { s4 ->
// 如果通道已经关闭直接退出循环
if (channel.isClosedForRead()) {
return s4.resolve(false)
}
return channel.readAvailable(s4, buffer, 0, buffer.size()).then { s5, length ->
if (length <= 0) {
return s5.resolve(false)
}
current += length
// 下面注释掉,因为可能会非常频繁输出日志
// $console.log("下载进度:${current / 1024 / 1024}M")
return writeChannel.writeFully(s5, buffer, 0, length).then { s6, data -> return s6.resolve(true) }
}
}.then { s, data ->
// loop 循环结束,关闭写入通道, 这一步必须有,否则可能会造成文件损坏
return writeChannel.flushAndClose(s)
}
}
}
}
.then { s3, data ->
$console.log("下载完成")
s3.resolve(null)
}
.onError { e ->
$console.error("下载失败")
}
$threads.getMain().setBackground(true)
const scope = $coroutines.newScope(() => {});
const client = $httpClients.newClient(hcc => {});
// 创建文件,如果存在先删除
const file = $files.buildFile(fb => {
fb.setPath("/sdcard/M8Test/tmp", "coolapk.apk");
});
file.mkdirs();
file.deleteRecursively();
// 下载一个apk, 使用通道保存数据
client.prepareGet(scope, "https://dl.coolapk.com/down?pn=com.coolapk.market&id=NDU5OQ&h=46bb9d98&from=from-web", null, request => {})
.then((s1, statement) => {
return statement.execute(s1, (s2, response) => {
return response.getByteReadChannelBody(s2).then((s3, channel) => {
let current = 0;
const buffer = $arrays.newByteArray(8192);
// 为文件创建一个写入通道
const writeChannel = $files.toWriteChannel(file, null);
return s3.loop(s4 => {
// 如果通道已经关闭直接退出循环
if (channel.isClosedForRead() == true) {
return s4.resolve(false);
}
return channel.readAvailable(s4, buffer, 0, buffer.length).then((s5, length) => {
if (length <= 0) {
return s5.resolve(false);
}
current += length;
// 下面注释掉,因为可能会非常频繁输出日志
// $console.log(`下载进度:${current / 1024 / 1024}M`);
return writeChannel.writeFully(s5, buffer, 0, length).then((s6, data) => s6.resolve(true));
});
})
.then((s, data) => {
// loop 循环结束,关闭写入通道, 这一步必须有,否则可能会造成文件损坏
return writeChannel.flushAndClose(s);
});
});
});
})
.then((s3, data) => {
$console.log("下载完成");
return s3.resolve(null);
})
.onError(e => {
$console.error("下载失败");
});
$threads.getMain().setBackground(true);
local scope = _coroutines:newScope(function() end)
local client = _httpClients:newClient(function(hcc) end)
-- 创建文件,如果存在先删除
local file = _files:buildFile(function(fb)
fb:setPath("/sdcard/M8Test/tmp", "coolapk.apk")
end)
file:mkdirs()
file:deleteRecursively()
-- 下载一个apk, 使用通道保存数据
client:prepareGet(scope, "https://dl.coolapk.com/down?pn=com.coolapk.market&id=NDU5OQ&h=46bb9d98&from=from-web", nil, function(request) end)
:_then(function(s1, statement)
return statement:execute(s1, function(s2, response)
return response:getByteReadChannelBody(s2):_then(function(s3, channel)
local current = 0
local buffer = _arrays:newByteArray(8192)
-- 为文件创建一个写入通道
local writeChannel = _files:toWriteChannel(file, nil)
return s3:loop(function(s4)
-- 如果通道已经关闭直接退出循环
if channel:isClosedForRead() then
return s4:resolve(false)
end
-- Lua的'#'操作符对Java数组无效, 使用包装器获取长度
return channel:readAvailable(s4, buffer, 0, _objectWrappers:wrapByteArray(buffer):count()):_then(function(s5, length)
if length <= 0 then
return s5:resolve(false)
end
current = current + length
-- 下面注释掉,因为可能会非常频繁输出日志
-- _console:log("下载进度:" .. tostring(current / 1024 / 1024) .. "M")
return writeChannel:writeFully(s5, buffer, 0, length):_then(function(s6, data) return s6:resolve(true) end)
end)
end):_then(function(s, data)
-- loop 循环结束,关闭写入通道, 这一步必须有,否则可能会造成文件损坏
return writeChannel:flushAndClose(s)
end)
end)
end)
end)
:_then(function(s3, data)
_console:log("下载完成")
return s3:resolve(nil)
end)
:onError(function(e)
if e ~= nil then
_console:error("下载失败:", e:stackTraceToString())
else
_console:error("下载失败,但未提供错误详情。")
end
end)
_threads:getMain():setBackground(true)
<?php
/** @var m8test_java\com\m8test\script\core\api\coroutines\Coroutines $coroutines */
global $coroutines;
/** @var m8test_java\com\m8test\script\core\api\http\HttpClients $httpClients */
global $httpClients;
/** @var m8test_java\com\m8test\script\core\api\file\Files $files */
global $files;
/** @var m8test_java\com\m8test\script\core\api\collections\Arrays $arrays */
global $arrays;
/** @var m8test_java\com\m8test\script\core\api\console\Console $console */
global $console;
/** @var m8test_java\com\m8test\script\core\api\thread\Threads $threads */
global $threads;
$scope = $coroutines->newScope(function ($context) {
$context->setDispatcher(function ($dispatchers) {
return $dispatchers->getScriptMain();
});
});
$client = $httpClients->newClient(function ($hcc) {
});
// 创建文件,如果存在先删除
$file = $files->buildFile(function ($fb) {
$fb->setPath("/sdcard/M8Test/tmp", "coolapk.apk");
});
$file->mkdirs();
$file->deleteRecursively();
// 下载一个apk, 使用通道保存数据
$client->prepareGet($scope, "https://dl.coolapk.com/down?pn=com.coolapk.market&id=NDU5OQ&h=46bb9d98&from=from-web", null, function ($request) {
})->then(function ($s1, $statement) use ($scope, $file) {
return $statement->execute($s1, function ($s2, $response) use ($scope, $file) {
return $response->getByteReadChannelBody($s2)->then(function ($s3, $channel) use ($scope, $file) {
global $arrays, $files;
$current = 0;
$buffer = $arrays->newByteArray(8192);
// 为文件创建一个写入通道
$writeChannel = $files->toWriteChannel($file, null);
return $s3->loop(function ($s4) use ($scope, $channel, $buffer, $writeChannel, &$current) {
// 如果通道已经关闭直接退出循环
if ($channel->isClosedForRead()) {
return $s4->resolve(false);
}
return $channel->readAvailable($s4, $buffer, 0, 8192)->then(function ($s5, $length) use ($scope, $writeChannel, $buffer, &$current) {
if ($length <= 0) {
return $s5->resolve(false);
}
$current += $length;
// 下面注释掉,因为可能会非常频繁输出日志
// $console->log("下载进度:${current / 1024 / 1024}M")
return $writeChannel->writeFully($s5, $buffer, 0, $length)->then(function ($s6, $data) use ($scope) {
return $s6->resolve(true);
});
});
})->then(function ($s, $data) use ($scope, $writeChannel) {
// loop 循环结束,关闭写入通道, 这一步必须有,否则可能会造成文件损坏
return $writeChannel->flushAndClose($s);
});
});
});
})
->then(function ($s3, $data) {
global $console;
$console->log(javaString("下载完成"));
return $s3->resolve(null);
})
->onError(function ($e) {
global $console;
$console->error(javaString("下载失败"));
});
$threads->getMain()->setBackground(true);
# python 暂时不支持文件下载显示进度,如果需要的话可以使用python网络请求库实现(例如requests)
# encoding: utf-8
scope = $coroutines.newScope { }
client = $httpClients.newClient { |hcc| }
# 创建文件,如果存在先删除
file = $files.buildFile do |fb|
fb.setPath("/sdcard/M8Test/tmp", "coolapk.apk")
end
file.mkdirs
file.deleteRecursively
# 下载一个apk, 使用通道保存数据
client.prepareGet(scope, "https://dl.coolapk.com/down?pn=com.coolapk.market&id=NDU5OQ&h=46bb9d98&from=from-web", nil) { |request| }
.then do |s1, statement|
statement.execute(s1) do |s2, response|
response.getByteReadChannelBody(s2).then do |s3, channel|
current = 0
buffer = $arrays.newByteArray(8192)
# 为文件创建一个写入通道
writeChannel = $files.toWriteChannel(file, nil)
s3.loop do |s4|
# 如果通道已经关闭直接退出循环
if channel.isClosedForRead
# 【修复】使用 next 代替 return 来从块中提前返回
next s4.resolve(false)
end
channel.readAvailable(s4, buffer, 0, buffer.size).then do |s5, length|
if length <= 0
# 【修复】使用 next 代替 return
next s5.resolve(false)
end
current += length
# 下面注释掉,因为可能会非常频繁输出日志
# $console.log("下载进度:#{current / 1024 / 1024}M")
# Ruby块最后一行自动作为返回值,不需要 return
writeChannel.writeFully(s5, buffer, 0, length).then { |s6, data| s6.resolve(true) }
end
end.then do |s, data|
# loop 循环结束,关闭写入通道, 这一步必须有,否则可能会造成文件损坏
writeChannel.flushAndClose(s)
end
end
end
end
.then do |s3, data|
$console.log("下载完成")
s3.resolve(nil)
end
.onError do |e|
$console.error("下载失败")
end
$threads.getMain.setBackground(true)
上传大文件
同样,若需上传大文件,请勿直接使用 post 方法——此举易致内存溢出。应改用 preparePost 方法,结合通道流式上传,方可稳妥处理大文件。
import com.m8test.script.GlobalVariables.*
val scope = _coroutines.newScope {}
val client = _httpClients.newClient { }
// 创建文件,如果存在先删除
val origin = _files.buildFile {
setPath("/sdcard/M8Test/tmp", "origin.bin")
}
origin.mkdirs()
origin.deleteRecursively()
// 填充数据到文件中
val b = _arrays.newByteArray(8)
for (i in 0 until 1024) {
origin.appendBytes(b)
}
val dest = _files.buildFile {
setPath("/sdcard/M8Test/tmp", "dest.bin")
}
dest.mkdirs()
dest.deleteRecursively()
// 模拟上传一个大文件
client.preparePost(scope, "https://httpbin.org/post", null) {
// 通过通道设置请求体,这样可以避免内存溢出
setWriteChannelContentBody {
val length = origin.getFile().length()
setContentLength(length)
writeTo { s, channel ->
// 创建文件读取通道
val readChannel = _files.toReadChannel(origin, 0, length - 1, null)
// 复制到字节写入通道并关闭
readChannel.copyToByteWriteChannelAndClose(s, channel)
}
}
}.then { s1, statement ->
// 这里可以处理服务器响应信息
statement.execute(s1) { s2, response ->
response.getTextBody(s2).then { s3, text ->
_console.log(text)
s3.resolve(null)
}
}
}.then { s3, data ->
_console.log("下载完成")
s3.resolve(null)
}.onError { e ->
// Kotlin 中 Exception 通常使用 e.stackTraceToString() (标准库扩展) 或 e.toString()
// 假设 ThrowableWrapper 暴露了类似方法
_console.error("下载失败", e)
}
_threads.getMain().setBackground(true)
def scope = $coroutines.newScope {}
def client = $httpClients.newClient { hcc -> }
// 创建文件,如果存在先删除
def origin = $files.buildFile { fb ->
fb.setPath("/sdcard/M8Test/tmp", "origin.bin")
}
origin.mkdirs()
origin.deleteRecursively()
// 填充数据到文件中
def b = $arrays.newByteArray(8)
for (i in 0..<1024) {
origin.appendBytes(b)
}
def dest = $files.buildFile { fb ->
fb.setPath("/sdcard/M8Test/tmp", "dest.bin")
}
dest.mkdirs()
dest.deleteRecursively()
// 模拟上传一个大文件
client.preparePost(scope, "https://httpbin.org/post", null) { request ->
// 通过通道设置请求体,这样可以避免内存溢出
request.setWriteChannelContentBody { wcc ->
def length = origin.getFile().length()
wcc.setContentLength(length)
wcc.writeTo { s, channel ->
// 创建文件读取通道
def readChannel = $files.toReadChannel(origin, 0, length - 1, null)
// 复制到字节写入通道并关闭
return readChannel.copyToByteWriteChannelAndClose(s, channel)
}
}
}.then { s1, statement ->
// 这里可以处理服务器响应信息
return statement.execute(s1) { s2, response ->
return response.getTextBody(s2).then { s3, text ->
$console.log(text)
return s3.resolve(null)
}
}
}.then { s3, data ->
$console.log("下载完成")
s3.resolve(null)
}.onError { e ->
$console.error("下载失败", e.stackTraceToString())
}
$threads.getMain().setBackground(true)
const scope = $coroutines.newScope(() => {});
const client = $httpClients.newClient(hcc => {});
// 创建文件,如果存在先删除
const origin = $files.buildFile(fb => {
fb.setPath("/sdcard/M8Test/tmp", "origin.bin");
});
origin.mkdirs();
origin.deleteRecursively();
// 填充数据到文件中
const b = $arrays.newByteArray(8);
for (let i = 0; i < 1024; i++) {
origin.appendBytes(b);
}
const dest = $files.buildFile(fb => {
fb.setPath("/sdcard/M8Test/tmp", "dest.bin");
});
dest.mkdirs();
dest.deleteRecursively();
// 模拟上传一个大文件
client.preparePost(scope, "https://httpbin.org/post", null, request => {
// 通过通道设置请求体,这样可以避免内存溢出
request.setWriteChannelContentBody(wcc => {
const length = origin.getFile().length();
wcc.setContentLength(length);
wcc.writeTo((s, channel) => {
// 创建文件读取通道
const readChannel = $files.toReadChannel(origin, 0, length - 1, null);
// 复制到字节写入通道并关闭
return readChannel.copyToByteWriteChannelAndClose(s, channel);
});
});
}).then((s1, statement) => {
// 这里可以处理服务器响应信息
return statement.execute(s1, (s2, response) => {
return response.getTextBody(s2).then((s3, text) => {
$console.log(text);
return s3.resolve(null);
});
});
}).then((s3, data) => {
$console.log("下载完成");
return s3.resolve(null);
}).onError(e => {
$console.error("下载失败", e.stackTraceToString());
});
$threads.getMain().setBackground(true);
local scope = _coroutines:newScope(function() end)
local client = _httpClients:newClient(function(hcc) end)
-- 创建文件,如果存在先删除
local origin = _files:buildFile(function(fb)
fb:setPath("/sdcard/M8Test/tmp", "origin.bin")
end)
origin:mkdirs()
origin:deleteRecursively()
-- 填充数据到文件中
local b = _arrays:newByteArray(8)
for i = 0, 1023 do
origin:appendBytes(b)
end
local dest = _files:buildFile(function(fb)
fb:setPath("/sdcard/M8Test/tmp", "dest.bin")
end)
dest:mkdirs()
dest:deleteRecursively()
-- 模拟上传一个大文件
client:preparePost(scope, "https://httpbin.org/post", nil, function(request)
-- 通过通道设置请求体,这样可以避免内存溢出
request:setWriteChannelContentBody(function(wcc)
local length = origin:getFile():length()
wcc:setContentLength(length)
wcc:writeTo(function(s, channel)
-- 创建文件读取通道
local readChannel = _files:toReadChannel(origin, 0, length - 1, nil)
-- 复制到字节写入通道并关闭
return readChannel:copyToByteWriteChannelAndClose(s, channel)
end)
end)
end):_then(function(s1, statement)
-- 这里可以处理服务器响应信息
return statement:execute(s1, function(s2, response)
return response:getTextBody(s2):_then(function(s3, text)
_console:log(text)
return s3:resolve(nil)
end)
end)
end):_then(function(s3, data)
_console:log("下载完成")
return s3:resolve(nil)
end):onError(function(e)
if e ~= nil then
_console:error("下载失败", e:stackTraceToString())
else
_console:error("下载失败,但未提供错误详情。")
end
end)
_threads:getMain():setBackground(true)
<?php
/** @var m8test_java\com\m8test\script\core\api\coroutines\Coroutines $coroutines */
global $coroutines;
/** @var m8test_java\com\m8test\script\core\api\net\http\HttpClients $httpClients */
global $httpClients;
/** @var m8test_java\com\m8test\script\core\api\file\Files $files */
global $files;
/** @var m8test_java\com\m8test\script\core\api\collections\Arrays $arrays */
global $arrays;
/** @var m8test_java\com\m8test\script\core\api\console\Console $console */
global $console;
/** @var m8test_java\com\m8test\script\core\api\thread\Threads $threads */
global $threads;
$scope = $coroutines->newScope(function ($context) {
$context->setDispatcher(function ($dispatchers) {
return $dispatchers->getScriptMain();
});
});
$client = $httpClients->newClient(function ($hcc) {});
// 创建文件,如果存在先删除
$origin = $files->buildFile(function ($fb) {
$fb->setPath(javaString("/sdcard/M8Test/tmp"), javaString("origin.bin"));
});
$origin->mkdirs();
$origin->deleteRecursively();
// 填充数据到文件中
$b = $arrays->newByteArray(8);
for ($i = 0; $i < 1024; $i++) {
$origin->appendBytes($b);
}
$dest = $files->buildFile(function ($fb) {
$fb->setPath(javaString("/sdcard/M8Test/tmp"), javaString("dest.bin"));
});
$dest->mkdirs();
$dest->deleteRecursively();
// 模拟上传一个大文件
$client->preparePost($scope, javaString("https://httpbin.org/post"), null, function ($request) use ($origin) {
// 通过通道设置请求体,这样可以避免内存溢出
$request->setWriteChannelContentBody(function ($wcc) use ($origin) {
$length = $origin->getFile()->length();
$wcc->setContentLength($length);
$wcc->writeTo(function ($s, $channel) use ($origin, $length) {
global $files;
// 创建文件读取通道
$readChannel = $files->toReadChannel($origin, 0, $length - 1, null);
// 复制到字节写入通道并关闭
return $readChannel->copyToByteWriteChannelAndClose($s, $channel);
});
});
})->then(function ($s1, $statement) {
// 这里可以处理服务器响应信息
return $statement->execute($s1, function ($s2, $response) {
return $response->getTextBody($s2)->then(function ($s3, $text) {
global $console;
$console->log($text);
return $s3->resolve(null);
});
});
})->then(function ($s3, $data) {
global $console;
$console->log(javaString("下载完成"));
return $s3->resolve(null);
})->onError(function ($e) {
global $console;
$console->error(javaString("下载失败"), $e->getStackTraceAsString());
});
$threads->getMain()->setBackground(true);
# 导入所需的全局变量
from m8test_java.com.m8test.script.GlobalVariables import _arrays
from m8test_java.com.m8test.script.GlobalVariables import _console
from m8test_java.com.m8test.script.GlobalVariables import _coroutines
from m8test_java.com.m8test.script.GlobalVariables import _files
from m8test_java.com.m8test.script.GlobalVariables import _httpClients
from m8test_java.com.m8test.script.GlobalVariables import _threads
# 使用您指定的 scope 创建方式
scope = _coroutines.newScope(lambda it: it.setDispatcher(lambda d: d.getScriptMain()))
# 对于空的配置闭包,使用 lambda _: None
client = _httpClients.newClient(lambda hcc: None)
# 创建文件,如果存在先删除
origin = _files.buildFile(lambda fb:
fb.setPath("/sdcard/M8Test/tmp", "origin.bin")
)
origin.mkdirs()
origin.deleteRecursively()
# 填充数据到文件中
# Groovy 的 for (i in 0..<1024) 转换为 Python 的 for i in range(1024)
b = _arrays.newByteArray(8)
for i in range(1024):
origin.appendBytes(b)
dest = _files.buildFile(lambda fb:
fb.setPath("/sdcard/M8Test/tmp", "dest.bin")
)
dest.mkdirs()
dest.deleteRecursively()
# 模拟上传一个大文件
(client.preparePost(scope, "https://httpbin.org/post", None, lambda request:
# 通过通道设置请求体,这样可以避免内存溢出
request.setWriteChannelContentBody(lambda wcc: (
(length := origin.getFile().length()),
wcc.setContentLength(length),
wcc.writeTo(lambda s, channel: (
# 创建文件读取通道
(readChannel := _files.toReadChannel(origin, 0, length - 1, None)),
# 复制到字节写入通道并关闭
# 这个 lambda 只有一个返回表达式,所以直接返回
readChannel.copyToByteWriteChannelAndClose(scope, channel)
)[-1]))))
.then(lambda s1, statement:
# 这里可以处理服务器响应信息
# 这个 lambda 只有一个返回表达式,直接返回
statement.execute(s1, lambda s2, response:
# 这个 lambda 只有一个返回表达式,直接返回
response.getTextBody(s2).then(lambda s3, text: (
_console.log(text),
s3.resolve(None)
)[-1])))
.then(lambda s3, data: (_console.log("下载完成"), s3.resolve(None))[-1])
.onError(lambda e:
# 这个 lambda 只有一个表达式,直接调用
_console.error("下载失败", e.stackTraceToString())
))
_threads.getMain().setBackground(True)
# encoding: utf-8
scope = $coroutines.newScope { }
client = $httpClients.newClient { |hcc| }
# 创建文件,如果存在先删除
origin = $files.buildFile do |fb|
fb.setPath("/sdcard/M8Test/tmp", "origin.bin")
end
origin.mkdirs
origin.deleteRecursively
# 填充数据到文件中
b = $arrays.newByteArray(8)
(0...1024).each do |i|
origin.appendBytes(b)
end
dest = $files.buildFile do |fb|
fb.setPath("/sdcard/M8Test/tmp", "dest.bin")
end
dest.mkdirs
dest.deleteRecursively
# 模拟上传一个大文件
client.preparePost(scope, "https://httpbin.org/post", nil) do |request|
# 通过通道设置请求体,这样可以避免内存溢出
request.setWriteChannelContentBody do |wcc|
length = origin.getFile.length
wcc.setContentLength(length)
wcc.writeTo do |s, channel|
# 创建文件读取通道
readChannel = $files.toReadChannel(origin, 0, length - 1, nil)
# 复制到字节写入通道并关闭
readChannel.copyToByteWriteChannelAndClose(s, channel)
end
end
end.then do |s1, statement|
# 这里可以处理服务器响应信息
statement.execute(s1) do |s2, response|
response.getTextBody(s2).then do |s3, text|
$console.log(text)
s3.resolve(nil)
end
end
end.then do |s3, data|
$console.log("下载完成")
s3.resolve(nil)
end.onError do |e|
$console.error("下载失败", e.stackTraceToString)
end
$threads.getMain.setBackground(true)
09 December 2025