Tauri アプリから Web API を呼ぶ方法は、ブラウザ(WebView)の fetch、HTTP プラグインの fetch、Rust のコマンドで使う HTTP クライアント reqwest の 3 つです。HTTP プラグインの fetch は JS から呼びますが通信は Rust 側で行われるので、ブラウザの CORS(別オリジンへのアクセス制限)を受けません。このレシピでは GET で JSON を取得する基本と 3 つの使い分け、HTTP プラグインの権限とスコープ(送り先 URL の許可リスト)の書き方をまとめます。
前提条件
どの方法で送るか
ブラウザの fetch | HTTP プラグインの fetch | Rust の reqwest | |
|---|---|---|---|
| 通信するのは | WebView | Rust(JS から呼ぶ) | Rust(自作のコマンド) |
| CORS の制限 | 受ける | 受けない | 受けない |
| 送り先の制限 | サーバーの CORS 設定 | スコープで許可した URL | 自分のコードで決める |
| Cookie | WebView のもの | プラグイン専用の保存先 | 既定では保持しない |
| 向く場面 | CORS を許可した自前の API | JS だけで外部 API を使う | API キーを JS に渡さない |
ページのオリジンは、Windows・Android では http://tauri.localhost、macOS・Linux では tauri://localhost、tauri dev 中は http://localhost:1420 などの開発サーバーです。サーバーが開発時のオリジンだけを許可していると「ビルドしたら CORS エラー」になります。サーバーを直せないなら HTTP プラグインか reqwest を使います。
HTTP プラグインを追加する
npm run tauri add http
パッケージの追加、lib.rs への登録、capability への http:default の追加が行われます。ただし http:default が許可するのは fetch 系のコマンドだけで、送り先の URL は 1 つも許可されていません。文字列の "http:default" を、allow に URL を並べたオブジェクトに書き換えます(URL は文字列だけでも書けます)。
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": ["main"],
"permissions": [
"core:default",
{
"identifier": "http:default",
"allow": [
{ "url": "https://jsonplaceholder.typicode.com" },
{ "url": "https://api.example.com/v1/*" },
"http://localhost:*"
],
"deny": [{ "url": "https://api.example.com/v1/admin/*" }]
}
]
}
allow に書く URL の規則
URL は URL Pattern という Web 標準の書式で照合され、* がワイルドカードになります。ポートとサブドメインとパスの末尾でつまずきがちです。
| 書き方 | 一致する例 | 一致しない例 |
|---|---|---|
https://api.example.com | そのホストの全パス | http:// の URL、:8443 などの別ポート |
https://api.example.com/v1/* | /v1/users?page=2 | /v1(末尾の / なし) |
https://api.example.com/users | /users?page=2 | /users/1 |
https://*.example.com | a.b.example.com | example.com 自体 |
http://localhost:* | 全ポートの localhost | http://127.0.0.1:3000 |
- クエリ(
?以降)はパターンに書かなければ何が付いても一致します(net-004)。 denyはallowより優先されます。メソッドは区別されません。https://*(443 番ポートの全 HTTPS)も書けますが、不正なスクリプトが入り込むと任意の送り先へデータを持ち出されます。使うホストだけを並べます。
1. フロントエンドから実装する (TypeScript)
@tauri-apps/plugin-http の fetch は標準の fetch と同じ形で呼べ、Response を返します。import を忘れてもエラーにならず、ブラウザの fetch が使われる点に注意します。
import { fetch } from '@tauri-apps/plugin-http';
export type Todo = { userId: number; id: number; title: string; completed: boolean };
export async function getTodo(id: number): Promise<Todo> {
let res: Response;
try {
res = await fetch(`https://jsonplaceholder.typicode.com/todos/${id}`, {
method: 'GET', // 省略しても GET
headers: { Accept: 'application/json' },
});
} catch (e) {
// 通信できなかった(スコープ外・名前解決や接続の失敗など)。e は文字列で届く
throw new Error(`通信に失敗しました: ${String(e)}`);
}
if (!res.ok) {
// 404 や 500 は例外にならず、ok が false の Response になる
throw new Error(`HTTP ${res.status} ${res.statusText}`);
}
return (await res.json()) as Todo;
}
拒否されたときの値は Error ではなく文字列です。404 や 500 では拒否されず res.ok が false になるので、両方を確かめます。第 2 引数にはプラグイン独自の maxRedirections(0 でリダイレクトをたどらない)、connectTimeout(net-007)、proxy も渡せます。
2. バックエンドから実装する (Rust)
API キーを WebView に置きたくないときや、結果を Rust で加工・保存するときは reqwest を使います。通信系のレシピは 0.12 系で書いています(0.13 系との違いは net-004)。
cd src-tauri
cargo add reqwest@0.12 --features json
reqwest::Client は接続を使い回すので、setup で 1 つ作って manage() で共有します。reqwest は既定で User-Agent を送らず、それを拒否する API(GitHub の REST API など)もあるので、ここで付けておきます。async fn のコマンドなら待つ間も画面は固まりません(rust-005)。
use serde::{Deserialize, Serialize};
use tauri::Manager;
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
struct Todo {
user_id: u32,
id: u32,
title: String,
completed: bool,
}
/// reqwest のエラーは to_string() だけでは原因が出ないので、source() をたどってつなげる
fn describe(err: &reqwest::Error) -> String {
let mut msg = err.to_string();
let mut source = std::error::Error::source(err);
while let Some(cause) = source {
msg.push_str(&format!(": {cause}"));
source = cause.source();
}
msg
}
/// 送り先のホストは Rust 側で固定し、JS からは ID だけを受け取る
#[tauri::command]
async fn get_todo(client: tauri::State<'_, reqwest::Client>, id: u32) -> Result<Todo, String> {
let url = format!("https://jsonplaceholder.typicode.com/todos/{id}");
client
.get(&url)
.send()
.await
.and_then(|res| res.error_for_status()) // 4xx / 5xx を Err にする
.map_err(|e| describe(&e))?
.json::<Todo>()
.await
.map_err(|e| describe(&e))
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_http::init()) // 1 章の fetch 用(tauri add http で追加済み)
.setup(|app| {
let client = reqwest::Client::builder()
.user_agent(concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")))
.build()?;
app.manage(client);
Ok(())
})
.invoke_handler(tauri::generate_handler![get_todo])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
to_string() だけでは「error sending request for url (…)」としか出ず、名前解決の失敗か接続の拒否か分からないため、describe() で原因をつなげています。また、HTTP プラグインのスコープは reqwest には効きません。JS から受け取った URL をそのまま取得するコマンドは任意の送り先への抜け道になるので、ホストは Rust 側で固定します。
import { invoke } from '@tauri-apps/api/core';
type Todo = { userId: number; id: number; title: string; completed: boolean };
try {
const todo = await invoke<Todo>('get_todo', { id: 1 });
console.log(todo.title);
} catch (e) {
console.error(e); // Rust の Err(String) が文字列で届く
}
動作確認
npm run tauri dev で起動し、ボタンのクリック処理などで次を実行します。
import { fetch } from '@tauri-apps/plugin-http';
const res = await fetch('https://jsonplaceholder.typicode.com/todos/1');
console.log(res.status, res.headers.get('content-type'));
console.log(await res.json());
200 application/json; charset=utf-8
{userId: 1, id: 1, title: 'delectus aut autem', completed: false}
allow に無い https://example.com/ では「url not allowed on the configured scope: https://example.com/」で拒否されることも確かめておきます。通信は Rust が行うので、DevTools の Network タブには出ません。
よくあるエラーと対処法
- 「http.fetch not allowed. Permissions associated with this command: http:allow-fetch, http:default」: capability に
http:defaultがありません。リリースビルドでは「Command plugin:http|fetch not allowed by ACL」になります。 - 「url not allowed on the configured scope: …」: URL が
allowに一致しません。スキーム、ポート、サブドメイン、パス末尾の/*を上の表で確かめます。allowの無い"http:default"のままでもこうなります。 - コンソールに CORS のエラーが出る:
fetchを import し忘れ、ブラウザのfetchが使われています。 - 「error sending request for url (…)」: 名前解決・接続・TLS のどこかで失敗しています。証明書は net-008 を参照します。
- 「plugin http not found」:
lib.rsに.plugin(tauri_plugin_http::init())がありません。
OS ごとの違いと注意点
- Origin: 上記の WebView のオリジンは、HTTP プラグインが自動で付ける
Originヘッダーの値にもなります(net-003)。 - Windows・Android の
useHttpsScheme:trueにするとオリジンがhttps://tauri.localhostになり、ブラウザのfetchでhttp://の API を呼ぶと混在コンテンツとして止められます。HTTP プラグインは影響を受けません。 - モバイル: HTTP プラグインは Android・iOS でも使えます。Android のエミュレーターでは
localhostがエミュレーター自身なので、PC 上の API は10.0.2.2で呼びます。 - 大きなデータ: ダウンロード と アップロード は別のレシピで扱います。
