外观
Fetch(网络请求)
fetch 插件为页面提供一个 fetch,它从 Go 侧发出 HTTP 请求。它接受 标准 fetch 的选项并返回标准的 Response,但请求是从应用的 Go 侧 发出的,因此:
- 它不受 CORS 限制:任何服务器都会应答,并且每个响应头都可读取,除了
Set-Cookie,而Response本来也无法携带它; - 它可以设置任何请求头,包括 webview 禁止的那些,例如
Origin、Cookie、Referer和User-Agent; - 它的响应体在 Go 读取的同时流式传给页面,中止请求会取消 Go 中的请求。
设置
在 Go 中使用该插件:
go
import (
"github.com/egoist/mygo"
"github.com/egoist/mygo/plugins/fetch"
)
func main() {
mygo.Use(fetch.Plugin)
// ...
}并在前端添加它的包:
sh
bun add @mygo-plugins/fetch在一个未使用其 Go 侧组件的应用中调用它的 fetch,Promise 会以一个 错误拒绝(reject),错误信息提示要调用 mygo.Use。
发起请求
从包中导入 fetch,像调用 webview 自带的 fetch 那样调用它:
ts
import { fetch } from "@mygo-plugins/fetch";
const res = await fetch("https://api.example.com/items", {
method: "POST",
headers: {
authorization: `Bearer ${token}`,
"content-type": "application/json",
},
body: JSON.stringify({ name: "Ada" }),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const item = await res.json();Request 可以代替 URL 使用。请求体与标准 fetch 所接受的一样,例如 字符串、FormData、URLSearchParams、Blob 和 ArrayBuffer,并带上 它本会给出的 Content-Type;它们在请求开始前被整体读取,流也一样。
自带 fetch 的库——许多 API 客户端都是如此——可以使用这个 fetch。 不要把它赋值给 globalThis.fetch:插件会把不由它发出的 URL——协议不是 http 或 https 的那些——交给 webview 的 fetch。
流式响应
响应体是一个 ReadableStream,Go 在服务器发送的同时填充它,这很适合 服务器发送事件(server-sent events)和模型 API 的流式回答:
ts
const res = await fetch("https://api.example.com/events");
const reader = res.body!.pipeThrough(new TextDecoderStream()).getReader();
for (;;) {
const { value, done } = await reader.read();
if (done) break;
output.append(value);
}Go 的读取不会快过页面:当页面还有超过 1 MiB 的响应体尚未读取时,Go 会 等待,因此大型下载不会在页面的内存中堆积。
取消
中止请求的 signal 会在 Go 中取消请求,无论响应是否已经到达:
ts
const ctrl = new AbortController();
const timer = setTimeout(() => ctrl.abort(), 10_000);
try {
const res = await fetch(url, { signal: ctrl.signal });
return await res.text();
} finally {
clearTimeout(timer);
}Promise 或响应体的读取会以 signal 的 reason 拒绝。取消响应体 (res.body.cancel())、页面导航离开和关闭窗口同样会取消请求。
错误
与 webview 的 fetch 一样,响应不是错误,无论其状态码如何:请检查 res.ok。当请求失败时——例如无法到达服务器、TLS 失败或重定向过多—— Promise 会以 TypeError 拒绝,其消息会说明原因:
ts
try {
await fetch("https://unreachable.example");
} catch (err) {
console.log(err.message); // fetch failed: Get "https://unreachable.example": dial tcp: lookup unreachable.example: no such host
}与 webview 的 fetch 的区别
mode、credentials、cache、referrerPolicy、integrity、keepalive和priority不适用,会被忽略。- Cookie 是 Go 侧
http.Client的,绝不是 webview 的:默认没有,除非 客户端带有保存它们的Jar(参见选项)。 redirect: "manual"返回重定向响应本身,带有它的状态码和Location,而不是不透明响应。"error"在重定向时拒绝,"follow"(默认值)最多跟随 20 次重定向,并设置res.redirected和res.url。- 协议不是 http 或 https 的 URL,例如构建版本中应用自己的页面 (
mygo://localhost)、data:和blob:URL,会交给 webview 的fetch。相对 URL 相对于页面的 URL 解析:在mygo dev期间,它们是 开发服务器的 URL,由 Go 去抓取。
选项
fetch.Plugin 通过系统的代理设置发出请求,与 webview 一样(参见 代理),并允许页面请求任何 URL。fetch.New 则接受选项:
go
jar, _ := cookiejar.New(nil)
mygo.Use(fetch.New(fetch.Options{
Client: &http.Client{Jar: jar, Timeout: time.Minute},
Allow: func(r *http.Request) bool {
return r.URL.Hostname() == "api.example.com"
},
}))Client发出请求,因此它的Transport(代理、TLS 设置)、Jar(跨请求保存的 Cookie)和Timeout都会生效,它的CheckRedirect限制跟随的重定向。没有提供Client时,客户端的传输是netproxy.Transport():当环境(HTTPS_PROXY、NO_PROXY)指定了 代理时使用环境代理,否则使用系统代理。你自己的客户端保留它自己的Transport;要使用相同的代理,请给它netproxy.Transport(),或把 它的Proxy设为netproxy.Proxy。Allow在 Go 发出每个请求之前看到它,带着它的 URL、方法和请求头, 并决定页面是否可以发出该请求:被它拒绝的请求会以错误拒绝。它只看到 第一个请求,看不到随后跟随的重定向,后者可以由客户端的CheckRedirect拒绝。
只有应用自己的页面可以调用该插件,与每个绑定方法一样(参见谁可以 调用);Allow 可以防止一个运行了不该 运行的代码的页面(例如通过跨站脚本漏洞)访问其他服务器。