[Deno] fetchする時にUser-Agentを設定する方法

Denoでは`fetch`でHTTPリクエストを送る際、リクエストヘッダーの`User-Agent`に`Deno/<バージョン名>`という値が入っています。この部分を変える方法です。

## デフォルトのUser-Agent

デフォルトのUser-Agentヘッダーは以下のコードで確認できます。

```ts
const res = await fetch("https://dump-headers.herokuapp.com/");
console.log(await res.json());
```

上のコードを実行すると、User-Agentヘッダーが「`Deno/1.24.1`」であることが分かります。もちろんバージョン番号部分はお使いのDenoのバージョンによって異なります。

また、単に`navigator.userAgent`とすればプログラムから取得することもできます。

```ts
console.log(navigator.userAgent); // "Deno/1.24.1"
```

## User-Agentヘッダーの書き換え

`Request`オブジェクトを使用して、以下のように書きます。

```ts
const req = new Request("https://dump-headers.herokuapp.com/", {
  headers: {
    "user-agent": "hello!!!", // User-Agentヘッダーを"hello!!!"に設定
  },
});
const res = await fetch(req);
console.log(await res.json());
```

上記のコードを実行すると、User-Agentが目的の値に書き変わっているのが分かると思います。

## ブラウザと非互換なふるまい

Denoに実装されている`fetch`はブラウザと同様のAPIです。
しかし上記のコード、ブラウザで実行しても`User-Agent`ヘッダーを書き換えることはできません。
このように、**ブラウザとDenoの間で微妙に（意図的に）挙動が異なる**部分があります。

挙動が異なる部分が文書化されているので、ちょっと読んでみましょう。

> - The Deno user agent does not have a cookie jar. As such, the `set-cookie`
  header on a response is not processed, or filtered from the visible response
  headers.
> - Deno does not follow the same-origin policy, because the Deno user agent
  currently does not have the concept of origins, and it does not have a cookie
  jar. This means Deno does not need to protect against leaking authenticated
  data cross origin. Because of this Deno does not implement the following
  sections of the WHATWG `fetch` specification:
>   - Section `3.1. 'Origin' header`.
>   - Section `3.2. CORS protocol`.
>   - Section `3.5. CORB`.
>   - Section `3.6. 'Cross-Origin-Resource-Policy' header`.
>   - `Atomic HTTP redirect handling`.
>   - The `opaqueredirect` response type.
> - A `fetch` with a `redirect` mode of `manual` will return a `basic` response
  rather than an `opaqueredirect` response.
> - The specification is vague on how
  [`file:` URLs are to be handled](https://fetch.spec.whatwg.org/#scheme-fetch).
  Firefox is the only mainstream browser that implements fetching `file:` URLs,
  and even then it doesn't work by default. As of Deno 1.16, Deno supports
  fetching local files. See the next section for details.
> - The `request` and `response` header guards are implemented, but unlike
  browsers do not have any constraints on which header names are allowed.
> - The `referrer`, `referrerPolicy`, `mode`, `credentials`, `cache`, `integrity`,
  `keepalive`, and `window` properties and their relevant behaviours in
  `RequestInit` are not implemented. The relevant fields are not present on the
  `Request` object.
> - Request body upload streaming is supported (on HTTP/1.1 and HTTP/2). Unlike
  the current fetch proposal, the implementation supports duplex streaming.
> - The `set-cookie` header is not concatenated when iterated over in the
  `headers` iterator. This behaviour is in the
  [process of being specified](https://github.com/whatwg/fetch/pull/1346).
>
> https://deno.land/manual/runtime/web_platform_apis#spec-deviations

要約すると、主に以下の点がブラウザの挙動と異なると言っています。

- Cookieの処理を行わない
- ブラウザにあるCORS制限はない
- file URLに対するfetch（＝ローカルファイルfetch）を許可する
- Requestヘッダーに指定できるヘッダー名に制約はない（→ブラウザの[禁止ヘッダー名](https://developer.mozilla.org/ja/docs/Glossary/Forbidden_header_name)を参照）
- 双方向ストリーミングが可能
- set-cookie ヘッダーの処理方法

:::note warn
実はUser-Agentヘッダー自体は禁止ヘッダー名の中に[含まれていません](https://developer.mozilla.org/ja/docs/Glossary/Forbidden_header_name)。

User-AgentヘッダーがChromeで書き換えられないのは、[単に古い挙動が放置されている](https://bugs.chromium.org/p/chromium/issues/detail?id=571722)からだと思われます。
（実際、FirefoxではUser-Agentヘッダーを書き換えることができます。）
:::

このように、Denoに実装されているfetchとブラウザに実装されているfetchは微妙に挙動が異なり、サーバーサイドでも使いやすいように工夫されています。

## まとめ

- DenoのデフォルトのUser-Agentは`Deno/<バージョン名>`という値
  - これは`navigator.userAgent`で取得できる
- `Request`オブジェクトを使うと`User-Agent`ヘッダーを書き換えることができる
- Denoのfetchはブラウザと挙動が異なる部分がある

ちなみに、Denoのfetchの挙動については、cloudflareやNode.jsとともに[Winter CG](https://wintercg.org/)で標準化されている最中です。

https://github.com/wintercg/fetch