API キーやアクセストークン(Authorization)、表示言語(Accept-Language)、アプリのバージョンなど、API に渡す情報の多くはヘッダーで送ります。HTTP プラグインの fetch でも標準と同じく headers に書けますが、黙って捨てられるヘッダーと自動で付くヘッダーがあり、書いたとおりには届きません。このレシピでは実際に届く内容と、Rust の reqwest でヘッダーを付ける方法を説明します。
前提条件
HTTP プラグインの追加と、capability の http:default に送り先を allow で書く方法は HTTP GET リクエストを送る(Rust経由) のとおりです。API との通信は DevTools の Network タブに出ないので、ここでは受け取ったヘッダーを JSON で返してくれる httpbin.org を許可して、届いた内容を確かめます。
{
"$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://httpbin.org" }]
}
]
}
1. フロントエンドから実装する (TypeScript)
共通のヘッダーとトークンを付ける
毎回付けるヘッダーは 1 か所にまとめ、呼び出しごとの指定で上書きできるようにします。オブジェクトのスプレッドで合成すると、content-type と Content-Type のように綴りの違う同じヘッダーが両方残り、値が「a, b」と連結されて送られます。Headers は名前の大文字・小文字を区別しないので、set() で確実に上書きできます。
import { fetch } from '@tauri-apps/plugin-http';
const COMMON_HEADERS: Record<string, string> = {
Accept: 'application/json',
'Accept-Language': 'ja',
'User-Agent': 'MyApp/1.2.0', // 省略すると tauri-plugin-http/<バージョン> になる
};
export async function apiFetch(url: string, token: string | null, init: RequestInit = {}): Promise<Response> {
const headers = new Headers(COMMON_HEADERS);
new Headers(init.headers).forEach((value, name) => headers.set(name, value)); // 呼び出し側の指定を優先
if (token) headers.set('Authorization', `Bearer ${token}`);
return fetch(url, { ...init, headers });
}
トークンを保存する場所は 暗号化ストレージ (Stronghold) の導入と利用 を参照します。
送れないヘッダーと自動で付くヘッダー
次のヘッダーは Fetch の仕様で禁止されているため、Rust 側でエラーにならずに捨てられます。tauri dev のターミナルには「Skipping cookie header as it is a forbidden header per fetch spec」のような警告が出ますが、リリースビルドでは何も出ません。
Cookie、Origin、Referer、Host、Content-Length、Connection、Accept-Encoding、Accept-Charset、Date、DNT、Expect、TE、Trailer、Transfer-Encoding、Upgrade、Via、Set-Cookie、Access-Control-Request-Headers、Access-Control-Request-Method- 名前が
Proxy-かSec-で始まるもの
User-Agent と Authorization は禁止されていないので送れます。逆に、指定しなくても次のヘッダーは付きます。
| ヘッダー | 付く値 |
|---|---|
User-Agent | tauri-plugin-http/<バージョン>(指定すれば置き換わる) |
Origin | WebView のオリジン(tauri dev 中は開発サーバー) |
Cookie | プラグインが保存している Cookie |
Cookie は、応答の Set-Cookie をプラグインが覚えて、以後の同じサイトへのリクエストに自動で付けます。有効期限付きのものはアプリの終了時にアプリのキャッシュフォルダの .cookies に保存され、次回の起動時に読み込まれます。WebView の Cookie とは別なので、ログインは HTTP プラグイン経由で行います。
禁止ヘッダーをどうしても送るなら、src-tauri/Cargo.toml でプラグインの unsafe-headers 機能を有効にします。Origin の上書きもでき、空文字列を渡せば Origin を付けずに送れます。ただし WebView 上のどのスクリプトからも送れるようになるので、必要な通信だけを 2 章の Rust 側で行う方が安全です。
[dependencies]
tauri-plugin-http = { version = "2", features = ["unsafe-headers"] }
応答ヘッダーを読む
レート制限の残り回数や ETag などは res.headers から読みます。名前の大文字・小文字は問いません。ブラウザの fetch と違い、set-cookie も読めます。
import { fetch } from '@tauri-apps/plugin-http';
const res = await fetch('https://httpbin.org/response-headers?X-RateLimit-Remaining=42');
console.log(res.headers.get('x-ratelimit-remaining')); // "42"
for (const [name, value] of res.headers) console.log(`${name}: ${value}`);
2. バックエンドから実装する (Rust)
reqwest には禁止ヘッダーの制限が無く、Origin も Cookie も自動では付きません(Cookie を保存するには reqwest の cookies 機能が要ります)。すべてのリクエストに付けるヘッダーは default_headers()、個別のものは header()、トークンは bearer_auth() で付けます。reqwest の追加と Client の共有は net-001 のとおりです。
use reqwest::header::{HeaderMap, HeaderValue, ACCEPT, ACCEPT_LANGUAGE};
use tauri::Manager;
/// JS には渡さず、Rust 側だけで持つトークン
struct ApiToken(Option<String>);
fn build_client() -> reqwest::Result<reqwest::Client> {
let mut headers = HeaderMap::new();
headers.insert(ACCEPT, HeaderValue::from_static("application/json"));
headers.insert(ACCEPT_LANGUAGE, HeaderValue::from_static("ja"));
reqwest::Client::builder()
.user_agent(concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")))
.default_headers(headers) // この Client のすべてのリクエストに付く
.build()
}
#[tauri::command]
async fn echo_headers(
client: tauri::State<'_, reqwest::Client>,
token: tauri::State<'_, ApiToken>,
) -> Result<serde_json::Value, String> {
let mut req = client
.get("https://httpbin.org/headers")
.header("X-Client-Version", env!("CARGO_PKG_VERSION"));
if let Some(t) = &token.0 {
req = req.bearer_auth(t); // Authorization: Bearer <t>
}
let res = req.send().await.map_err(|e| e.to_string())?;
res.json::<serde_json::Value>().await.map_err(|e| e.to_string())
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_http::init()) // 1 章の fetch 用
.setup(|app| {
// 例: 設定フォルダの token.txt から読む。末尾の改行は trim() で落とす
let path = app.path().app_config_dir()?.join("token.txt");
let token = std::fs::read_to_string(path).ok().map(|s| s.trim().to_string());
app.manage(ApiToken(token));
app.manage(build_client()?);
Ok(())
})
.invoke_handler(tauri::generate_handler![echo_headers])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
ファイルや環境変数から読んだトークンは、末尾に改行が付いていることがよくあります。改行の入った値はヘッダーにできず、送信時にエラーになるので trim() しておきます。
import { invoke } from '@tauri-apps/api/core';
const echoed = await invoke<{ headers: Record<string, string> }>('echo_headers');
console.log(echoed.headers['User-Agent'], echoed.headers['Authorization']);
動作確認
npm run tauri dev で起動し、httpbin.org にヘッダーを送って、届いた内容を表示します。Cookie はわざと付けています。
import { fetch } from '@tauri-apps/plugin-http';
const res = await fetch('https://httpbin.org/headers', {
headers: {
Accept: 'application/json',
Authorization: 'Bearer test-token',
'User-Agent': 'MyApp/1.2.0',
'X-Client-Version': '1.2.0',
Cookie: 'theme=dark', // 禁止ヘッダーなので届かない
},
});
console.log(JSON.stringify(await res.json(), null, 2));
次のように表示されます(一部省略)。Cookie は届かず、指定していない Origin が付いています。ターミナルには前述の警告が出ます。
{
"headers": {
"Accept": "application/json",
"Authorization": "Bearer test-token",
"Host": "httpbin.org",
"Origin": "http://localhost:1420",
"User-Agent": "MyApp/1.2.0",
"X-Client-Version": "1.2.0"
}
}
よくあるエラーと対処法
- 付けたはずのヘッダーがサーバーに届かない: 上の禁止ヘッダーです。
unsafe-headersを有効にするか、Rust 側で送ります。 - 401 Unauthorized:
Bearerとトークンの間の半角スペース、トークンの期限切れ、API が求める形式(X-Api-Keyなど)を確かめます。 Headersを作る時点で TypeError になる: 値に日本語などの文字か、途中に改行が入っています。サーバーと取り決めてencodeURIComponent()や Base64 で ASCII にします。- Rust で「builder error」になる: ヘッダーの値に改行などが入っています。
source()をたどると「failed to parse header value」と出ます。 - ブラウザの
fetchでだけ CORS エラーになる:Authorizationなどを付けると事前確認(プリフライト)が行われ、サーバーがそのヘッダーを許可していないと失敗します。HTTP プラグインはこの影響を受けません。
OS ごとの違いと注意点
- Origin の値: Windows・Android は
http://tauri.localhost、macOS・Linux はtauri://localhostです。Origin を見て拒否するサーバーでは OS によって結果が変わります。プラグイン 2.5.6 より前は macOS・Linux でnullが送られていました。 - ヘッダー名の大文字・小文字: HTTP/2 では小文字で送られ、
res.headersの名前も小文字になります。サーバー側で区別しない前提で扱います。 - 秘密の置き場所:
Authorizationの値は WebView の JS から読めます。漏れて困る API キーは Rust 側に置き、2 章の方法で付けます。クエリに入れるとログに残るので避けます(URL クエリパラメータを付与して送る)。 - Content-Type: 本文との組み合わせは JSON データを POST 送信する を参照します。
