Skip to content

Commit b24ec11

Browse files
author
obsidian
committed
vault backup: 2025-06-03 15:39:05 A source/_posts/Tauri 如何避免触发 CORS.md
Affected files: source/_posts/Tauri 如何避免触发 CORS.md
1 parent 7138576 commit b24ec11

File tree

1 file changed

+172
-0
lines changed

1 file changed

+172
-0
lines changed
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
---
2+
title: Tauri 如何避免触发 CORS
3+
date: 2025-06-01 11:39
4+
updated: 2025-06-01T11:39:53+08:00
5+
permalink:
6+
top: 0
7+
comments:
8+
copyright: true
9+
tags:
10+
categories:
11+
keywords:
12+
description:
13+
---
14+
Tauri 也是通过平台侧提供的 WebView 引擎来解析渲染,所以当然也会遇到 CORS 限制.本文讲述如何处理这个问题.
15+
16+
1、最常见的处理方式就是服务端支持 `Access-Control-Allow-Origin` 但是此处调用的不是自己的服务器,不可能全部都处理这种请求.
17+
18+
2、tauri V2 支持跨平台(移动端)能力. 提供了 tauri-plugin-http 插件, 为其他平台提供 `fetch/XMLHttpRequest` 函数支持, 所以猜测可以直接使用 tauri-plugin-http 提供的 `fetch/XMLHttpRequest` 替代浏览器的函数
19+
20+
```ts
21+
import { fetch as tauriFetch } from "@tauri-apps/plugin-http";
22+
// once binding, invoke fetch or any rust command will cause page freeze!
23+
window.fetch = tauriFetch;
24+
```
25+
26+
> https://github.com/ShuttleSpace/fetcher
27+
> https://github.com/tauri-apps/plugins-workspace/issues/2728
28+
29+
实际测试最新版 v2.4.4 直接在 main.ts 中替换会导致页面卡死
30+
31+
3、第三种方案就是拦截 `http/https/ws/wss` 请求,在 rust 侧处理,然后将响应返回到UI侧
32+
33+
因为问题出在 `invoke('command')` 上,不能直接拦截然后就调用 rust command.所以此处通过自定义 scheme `listenTwo` 来触发 rust 拦截
34+
35+
> src-tauri/src/lib.rs
36+
```rust
37+
tauri::Builder::default()
38+
.register_asynchronous_uri_scheme_protocol("listentwo", |_ctx, request, responder| {
39+
let uri = request.uri().to_string();
40+
let origin_method = request.headers().get("origin-method")
41+
.and_then(|v| v.to_str().ok())
42+
.unwrap_or("https");
43+
let target_url = format!("{}:{}", origin_method, uri.replace("listentwo://", ""));
44+
trace!("[listentwo] target_url: {}", target_url);
45+
static CLIENT: once_cell::sync::OnceCell<reqwest::Client> = once_cell::sync::OnceCell::new();
46+
let client = CLIENT.get_or_init(|| {
47+
reqwest::Client::builder()
48+
.timeout(std::time::Duration::from_secs(10))
49+
.build()
50+
.unwrap()
51+
});
52+
let future = async move {
53+
let method = match request.method() {
54+
&Method::GET => reqwest::Method::GET,
55+
&Method::POST => reqwest::Method::POST,
56+
&Method::PUT => reqwest::Method::PUT,
57+
&Method::DELETE => reqwest::Method::DELETE,
58+
&Method::HEAD => reqwest::Method::HEAD,
59+
&Method::OPTIONS => reqwest::Method::OPTIONS,
60+
&Method::PATCH => reqwest::Method::PATCH,
61+
_ => reqwest::Method::GET,
62+
};
63+
let is_body_method = matches!(method, reqwest::Method::POST | reqwest::Method::PUT | reqwest::Method::PATCH);
64+
let mut request_builder = client.request(method, &target_url);
65+
if is_body_method {
66+
request_builder = request_builder.body(request.body().to_vec());
67+
}
68+
let mut header_map = reqwest::header::HeaderMap::new();
69+
for (k, v) in request.headers().iter() {
70+
if let (Ok(header_name), Ok(header_value)) = (
71+
reqwest::header::HeaderName::from_bytes(k.as_str().as_bytes()),
72+
reqwest::header::HeaderValue::from_str(v.to_str().unwrap_or_default())
73+
) {
74+
header_map.insert(header_name, header_value);
75+
}
76+
}
77+
request_builder = request_builder.headers(header_map);
78+
match request_builder.send().await {
79+
Ok(response) => {
80+
trace!("[listentwo] [get] response status: {}", response.status());
81+
let status = response.status();
82+
let headers = response.headers().clone();
83+
let bytes = response.bytes().await.unwrap();
84+
85+
let mut builder = http::Response::builder()
86+
.status(status)
87+
.header("Access-Control-Allow-Origin", "*")
88+
.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
89+
.header("Access-Control-Allow-Headers", "Content-Type, origin-method");
90+
91+
for (key, value) in headers.iter() {
92+
if key != "access-control-allow-origin" {
93+
builder = builder.header(key, value);
94+
}
95+
}
96+
97+
responder.respond(builder.body(bytes.to_vec()).unwrap())
98+
},
99+
Err(e) => {
100+
responder.respond(
101+
http::Response::builder()
102+
.status(http::StatusCode::BAD_GATEWAY)
103+
.body(format!("代理请求错误: {}", e).into_bytes())
104+
.unwrap()
105+
)
106+
}
107+
}
108+
};
109+
tauri::async_runtime::spawn(future);
110+
})
111+
.plugin(tauri_plugin_http::init())
112+
.plugin(tauri_plugin_opener::init())
113+
.invoke_handler(tauri::generate_handler![greet])
114+
.run(tauri::generate_context!())
115+
.expect("error while running tauri application");
116+
```
117+
118+
> src/main.ts
119+
```ts
120+
let originFetch = window.fetch
121+
window.fetch = async function (input, init): Promise<Response> {
122+
if (typeof input === 'string' && (input.startsWith('http') || input.startsWith('https'))) {
123+
const url = new URL(input);
124+
if (url.hostname === 'localhost' || url.hostname === '127.0.0.1') {
125+
return originFetch(input, init);
126+
}
127+
const index = input.indexOf('://')
128+
const originMethod = input.substring(0, index)
129+
const newInit = {
130+
...init,
131+
headers: {
132+
...init?.headers,
133+
'origin-method': originMethod
134+
}
135+
}
136+
try {
137+
const cacheKey = `listentwo:${input}`;
138+
if (newInit.method === 'GET') {
139+
const cached = sessionStorage.getItem(cacheKey);
140+
if (cached) {
141+
return new Response(cached, {
142+
headers: new Headers({'Content-Type': 'application/json'})
143+
});
144+
}
145+
}
146+
const response = await originFetch("listentwo" + input.substring(index), newInit)
147+
if (newInit.method === 'GET' && response.ok) {
148+
const data = await response.clone().text();
149+
sessionStorage.setItem(cacheKey, data);
150+
}
151+
if (newInit.method === 'OPTIONS') {
152+
return new Response(null, {
153+
status: 204,
154+
headers: {
155+
'Access-Control-Allow-Origin': '*',
156+
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
157+
'Access-Control-Allow-Headers': 'Content-Type, origin-method'
158+
}
159+
});
160+
}
161+
return response;
162+
} catch (error) {
163+
console.error("Fetch error:", error);
164+
throw error;
165+
}
166+
}
167+
return originFetch(input, init)
168+
}
169+
```
170+
171+
> 按照 [MDN](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/CORS)说明除了 `fetch/XMLHttpRequest` 外,还有 Web Fonts, WebGL textures, Canvas drawImage, CSS Shapes Image 等.
172+
> 这些请求也可以通过相同方式进行拦截处理.

0 commit comments

Comments
 (0)