M8Test Help

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