JSONPlaceholder 网络请求实验台
这个示例不再只是展示一段策略配置,而是让用户在沙盒内选择真实的网络任务:读取 posts、查看详情、拉取评论、按用户过滤,或者向 JSONPlaceholder 发起一个演示用 POST。请求不会从 iframe 直接出网,而是通过 api.fetch() 交给宿主,由宿主按 allowlist、方法、超时、请求体大小和响应类型决定是否代理。
slex: "0.1",
namespace: "example_jsonplaceholder_network_lab",
g: {
scenario: "posts",
postId: 1,
userId: 1,
timeout: 3000,
title: "SlexKit network demo",
status: "未请求",
statusCode: "-",
elapsed: "-",
response: "选择一个网络任务,然后点击“发起请求”。",
lastUrl: "https://jsonplaceholder.typicode.com/posts",
method: function () { return this.scenario === "create" ? "POST" : "GET"; },
url: function () {
if (this.scenario === "detail") return "https://jsonplaceholder.typicode.com/posts/" + this.postId;
if (this.scenario === "comments") return "https://jsonplaceholder.typicode.com/posts/" + this.postId + "/comments";
if (this.scenario === "user-posts") return "https://jsonplaceholder.typicode.com/users/" + this.userId + "/posts";
if (this.scenario === "create") return "https://jsonplaceholder.typicode.com/posts";
return "https://jsonplaceholder.typicode.com/posts?_limit=5";
},
requestBody: function () {
if (this.scenario !== "create") return undefined;
return {
title: this.title,
body: "这是一条从 SlexKit secure runtime 发起、由宿主代理的演示请求。",
userId: this.userId
};
},
description: function () {
if (this.scenario === "detail") return "读取单篇 post,适合详情页或引用卡片。";
if (this.scenario === "comments") return "读取某篇 post 的评论,适合评论摘要、审阅流和证据面板。";
if (this.scenario === "user-posts") return "按用户读取 posts,适合个人空间、作者档案或关联资源列表。";
if (this.scenario === "create") return "提交一条演示 post。JSONPlaceholder 会返回假写入结果,不会持久化。";
return "读取最新 posts 列表,适合 feed、任务列表和知识库索引。";
},
riskText: function () {
if (this.method() === "POST") return "POST 已被限制为 JSONPlaceholder origin,且请求体大小受 policy 约束。";
return "GET 请求仍然要经过 origin、超时、响应大小和 content-type 校验。";
},
requestSnippet: function () {
var body = this.requestBody();
var lines = [
"await api.fetch('" + this.url() + "', {",
" method: '" + this.method() + "',",
" timeoutMs: " + this.timeout + ","
];
if (body) lines.push(" body: " + JSON.stringify(body, null, 2).replace(/\n/g, "\n ") + ",");
lines.push(" credentials: 'omit'");
lines.push("})");
return lines.join("\n");
},
policyRows: function () {
return [
{ item: "Origin", value: "https://jsonplaceholder.typicode.com", reason: "只允许演示 API" },
{ item: "Method", value: "GET, POST", reason: "读列表/详情和创建演示数据" },
{ item: "Credentials", value: "omit", reason: "不携带 cookie 或站点身份" },
{ item: "Content-Type", value: "application/json", reason: "拒绝非 JSON 响应进入沙盒" },
{ item: "Body", value: "<= 4096 bytes", reason: "避免大请求体被模型输出滥用" }
];
},
async run(api) {
var targetUrl = String(this.url());
this.status = "请求中";
this.statusCode = "-";
this.elapsed = "-";
this.lastUrl = targetUrl;
this.response = "等待宿主代理 " + this.method() + " " + targetUrl;
try {
var result = await api.fetch(targetUrl, {
method: this.method(),
timeoutMs: this.timeout,
body: this.requestBody(),
credentials: "omit"
});
this.status = result.ok ? "成功" : "HTTP 错误";
this.statusCode = String(result.status) + " " + result.statusText;
this.elapsed = Math.round(result.elapsedMs) + " ms";
this.response = JSON.stringify(result.data === undefined ? result.text : result.data, null, 2).slice(0, 2400);
} catch (error) {
this.status = api.isPolicyError(error) ? "Policy 拦截" : api.isTimeoutError(error) ? "超时" : "网络失败";
this.statusCode = api.isPolicyError(error) ? "policy" : api.isTimeoutError(error) ? "timeout" : "network";
var elapsedMs = error && typeof error === "object" && typeof error.elapsedMs === "number" ? error.elapsedMs : undefined;
this.elapsed = elapsedMs === undefined ? "-" : Math.round(elapsedMs) + " ms";
this.response = targetUrl + "\n" + api.errorMessage(error);
}
},
async runBlocked(api) {
var targetUrl = "https://example.com/posts/1";
this.status = "请求中";
this.statusCode = "-";
this.elapsed = "-";
this.lastUrl = targetUrl;
this.response = "这次请求故意访问 allowlist 外的 origin,应该被 host policy 拦截。";
try {
await api.get(targetUrl, { timeoutMs: this.timeout, credentials: "omit" });
this.status = "异常通过";
this.statusCode = "unexpected";
this.response = "如果看到这行,说明 policy 没有按预期拦截。";
} catch (error) {
this.status = api.isPolicyError(error) ? "Policy 拦截" : "失败";
this.statusCode = api.isPolicyError(error) ? "origin_blocked" : "network";
this.response = targetUrl + "\n" + api.errorMessage(error);
}
}
},
layout: {
"section:network": {
eyebrow: "平台能力",
title: "JSONPlaceholder 网络请求实验台",
subtitle: "在沙盒内选择网络任务,请求通过 host policy 代理。",
"card:network": {
title: "JSONPlaceholder 请求实验台",
"callout:intent": { tone: "info", "$text": "g.description()" },
"grid:controls": {
columns: 1,
mdColumns: 2,
"select:scenario": {
label: "网络任务",
"$value": "g.scenario",
options: [
{ label: "Posts 列表 GET /posts", value: "posts" },
{ label: "Post 详情 GET /posts/:id", value: "detail" },
{ label: "评论列表 GET /posts/:id/comments", value: "comments" },
{ label: "用户 posts GET /users/:id/posts", value: "user-posts" },
{ label: "创建 post POST /posts", value: "create" }
],
onchange: "g.scenario = String($event)"
},
"slider:postId": { label: "Post ID", "$value": "g.postId", min: 1, max: 10, step: 1, onchange: "g.postId = Number($event)" },
"slider:userId": { label: "User ID", "$value": "g.userId", min: 1, max: 10, step: 1, onchange: "g.userId = Number($event)" },
"slider:timeout": { label: "超时上限", "$value": "g.timeout", min: 500, max: 8000, step: 500, unit: "ms", onchange: "g.timeout = Number($event)" }
},
"row:actions": {
"button:run": { label: "发起请求", icon: "paper-plane-tilt", onclick: "g.run(api)" },
"button:block": { label: "测试拦截", variant: "secondary", icon: "shield-warning", onclick: "g.runBlocked(api)" }
},
"grid:status": {
columns: 1,
mdColumns: 3,
"stat:method": { label: "方法", "$value": "g.method()" },
"stat:status": { label: "状态", "$value": "g.status" },
"stat:elapsed": { label: "耗时", "$value": "g.elapsed" }
},
"code-block:request": { title: "沙盒内请求代码", language: "ts", "$code": "g.requestSnippet()" },
"code-block:response": { title: "宿主代理响应", language: "json", "$code": "g.response" },
"table:policy_matrix": {
columns: [
{ key: "item", label: "Policy 项" },
{ key: "value", label: "允许值" },
{ key: "reason", label: "原因" }
],
"$rows": "g.policyRows()"
},
"callout:policy_note": { "$tone": "g.status === 'Policy 拦截' ? 'warning' : 'success'", "$text": "g.riskText()" }
}
}
}
}