Tauri 如何避免触发 CORS HumphreyDan 2025-06-01 2025-06-01 Tauri 也是通过平台侧提供的 WebView 引擎来解析渲染,所以当然也会遇到 CORS 限制.本文讲述如何处理这个问题.
1、最常见的处理方式就是服务端支持 Access-Control-Allow-Origin
但是此处调用的不是自己的服务器,不可能全部都处理这种请求.
2、tauri V2 支持跨平台(移动端)能力. 提供了 tauri-plugin-http 插件, 为其他平台提供 fetch/XMLHttpRequest
函数支持, 所以猜测可以直接使用 tauri-plugin-http 提供的 fetch/XMLHttpRequest
替代浏览器的函数
1 2 3 import { fetch as tauriFetch } from "@tauri-apps/plugin-http" ;window .fetch = tauriFetch;
https://github.com/ShuttleSpace/fetcher https://github.com/tauri-apps/plugins-workspace/issues/2728
实际测试最新版 v2.4.4 直接在 main.ts 中替换会导致页面卡死
3、第三种方案就是拦截 http/https/ws/wss
请求,在 rust 侧处理,然后将响应返回到UI侧
因为问题出在 invoke('command')
上,不能直接拦截然后就调用 rust command.所以此处通过自定义 scheme listenTwo
来触发 rust 拦截
src-tauri/src/lib.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 tauri::Builder::default () .register_asynchronous_uri_scheme_protocol ("listentwo" , |_ctx, request, responder| { let uri = request.uri ().to_string (); let origin_method = request.headers ().get ("origin-method" ) .and_then (|v| v.to_str ().ok ()) .unwrap_or ("https" ); let target_url = format! ("{}:{}" , origin_method, uri.replace ("listentwo://" , "" )); trace!("[listentwo] target_url: {}" , target_url); static CLIENT: once_cell::sync::OnceCell<reqwest::Client> = once_cell::sync::OnceCell::new (); let client = CLIENT.get_or_init (|| { reqwest::Client::builder () .timeout (std::time::Duration::from_secs (10 )) .build () .unwrap () }); let future = async move { let method = match request.method () { &Method::GET => reqwest::Method::GET, &Method::POST => reqwest::Method::POST, &Method::PUT => reqwest::Method::PUT, &Method::DELETE => reqwest::Method::DELETE, &Method::HEAD => reqwest::Method::HEAD, &Method::OPTIONS => reqwest::Method::OPTIONS, &Method::PATCH => reqwest::Method::PATCH, _ => reqwest::Method::GET, }; let is_body_method = matches!(method, reqwest::Method::POST | reqwest::Method::PUT | reqwest::Method::PATCH); let mut request_builder = client.request (method, &target_url); if is_body_method { request_builder = request_builder.body (request.body ().to_vec ()); } let mut header_map = reqwest::header::HeaderMap::new (); for (k, v) in request.headers ().iter () { if let (Ok (header_name), Ok (header_value)) = ( reqwest::header::HeaderName::from_bytes (k.as_str ().as_bytes ()), reqwest::header::HeaderValue::from_str (v.to_str ().unwrap_or_default ()) ) { header_map.insert (header_name, header_value); } } request_builder = request_builder.headers (header_map); match request_builder.send ().await { Ok (response) => { trace!("[listentwo] [get] response status: {}" , response.status ()); let status = response.status (); let headers = response.headers ().clone (); let bytes = response.bytes ().await .unwrap (); let mut builder = http::Response::builder () .status (status) .header ("Access-Control-Allow-Origin" , "*" ) .header ("Access-Control-Allow-Methods" , "GET, POST, PUT, DELETE, OPTIONS" ) .header ("Access-Control-Allow-Headers" , "Content-Type, origin-method" ); for (key, value) in headers.iter () { if key != "access-control-allow-origin" { builder = builder.header (key, value); } } responder.respond (builder.body (bytes.to_vec ()).unwrap ()) }, Err (e) => { responder.respond ( http::Response::builder () .status (http::StatusCode::BAD_GATEWAY) .body (format! ("代理请求错误: {}" , e).into_bytes ()) .unwrap () ) } } }; tauri::async_runtime::spawn (future); }) .plugin (tauri_plugin_http::init ()) .plugin (tauri_plugin_opener::init ()) .invoke_handler (tauri::generate_handler![greet]) .run (tauri::generate_context!()) .expect ("error while running tauri application" );
src/main.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 let originFetch = window .fetch window .fetch = async function (input, init ): Promise <Response > { if (typeof input === 'string' && (input.startsWith ('http' ) || input.startsWith ('https' ))) { const url = new URL (input); if (url.hostname === 'localhost' || url.hostname === '127.0.0.1' ) { return originFetch (input, init); } const index = input.indexOf ('://' ) const originMethod = input.substring (0 , index) const newInit = { ...init, headers : { ...init?.headers , 'origin-method' : originMethod } } try { const cacheKey = `listentwo:${input} ` ; if (newInit.method === 'GET' ) { const cached = sessionStorage .getItem (cacheKey); if (cached) { return new Response (cached, { headers : new Headers ({'Content-Type' : 'application/json' }) }); } } const response = await originFetch ("listentwo" + input.substring (index), newInit) if (newInit.method === 'GET' && response.ok ) { const data = await response.clone ().text (); sessionStorage .setItem (cacheKey, data); } if (newInit.method === 'OPTIONS' ) { return new Response (null , { status : 204 , headers : { 'Access-Control-Allow-Origin' : '*' , 'Access-Control-Allow-Methods' : 'GET, POST, PUT, DELETE, OPTIONS' , 'Access-Control-Allow-Headers' : 'Content-Type, origin-method' } }); } return response; } catch (error) { console .error ("Fetch error:" , error); throw error; } } return originFetch (input, init) }
按照 MDN 说明除了 fetch/XMLHttpRequest
外,还有 Web Fonts, WebGL textures, Canvas drawImage, CSS Shapes Image 等. 这些请求也可以通过相同方式进行拦截处理.