[Deno] Web標準Responseオブジェクトで様々なデータをやり取りする

Web標準にはHTTP通信のリクエスト/レスポンスを表すRequest / Responseオブジェクトが存在します。
これらはそれぞれ`fetch()`関数の引数と返り値であり、フロントエンドで主に利用されています。加えて最近はDenoやNode.jsなどのサーバーサイドでもこれらのオブジェクトが活用されており、クライアント・サーバー間で様々な形式のデータを送受信することが可能になっています。

https://developer.mozilla.org/ja/docs/Web/API/Request

https://developer.mozilla.org/ja/docs/Web/API/Response

当記事ではResponseオブジェクトを使用したデータの受け渡しを、Denoを用いて解説していきます。

# Responseオブジェクトを使用してデータを受け渡しする

当記事では解説のためにDeno標準ライブラリのHTTPサーバーを利用します。

このサーバーは、HTTPサーバーを「Requestオブジェクトを受け取ってResponseオブジェクトを返す関数」として書くことができます。

```ts:サーバー側
import { serve } from "https://deno.land/std@0.187.0/http/mod.ts";

serve(() => new Response());
//     ^ Requestオブジェクトを受け取ってResponseオブジェクトを返す関数
```

クライアント側 (ブラウザを想定) では、`fetch()`関数を使用してHTTPリクエストを送信します。`fetch`関数の返り値も、Responseオブジェクトです。

```ts:クライアント側
// 変数resにはResponseオブジェクトが入る
const res = await fetch("http://example.com");
```

ここからは、Deno標準ライブラリのHTTPサーバーとfetch関数を使用して、サーバーからクライアント側に様々な形式のデータを送信する方法について見ていきます。

## 文字列の受け渡し

文字列データを受け渡しするには、**サーバー側で`Response`コンストラクタの引数に文字列を入れ**、**クライアント側で`await res.text()`で値を取り出せばよい**です。

```ts:サーバー側
import { serve } from "https://deno.land/std@0.187.0/http/mod.ts";

serve(() => new Response("text data"));
```


```ts:クライアント側
// res は Response オブジェクト
const res = await fetch("http://example.com/");
const text = await res.text(); // res.text()で、サーバー側で入れた値を取り出し
console.log(text); // => "text data"
```

## JSONデータの受け渡し

Responseの作成には、JSONデータからResponseオブジェクトを作成するための`Response.json()`メソッドが用意されています。これを利用してJSONデータのレスポンスを作成できます。

クライアント側では、`await res.json()`でJSONデータを取り出すことができます。

```ts:サーバー側
import { serve } from "https://deno.land/std@0.187.0/http/mod.ts";

// Response.json()を使用すると自動でContent-Typeがapplicatin/jsonに設定される
serve(() => Response.json({ key: "value" }));
```


```ts:クライアント側
// res は Response オブジェクト
const res = await fetch("http://example.com/");
const json = await res.json(); // res.json()で、サーバー側で入れた値を取り出し
console.log(json); // => { key: "value" }
```

`.json()`メソッドが2つ出てくるのでややこしいですが、

- `Response.json()` (staticメソッド) はResponse作成用
- `Response.prototype.json()` (インスタンスメソッド) はResponseからデータを取り出す用

なので混同しないようにしましょう。

## Uint8Array / ArrayBufferの受け渡し

バイナリデータを送受信する際に役立つのが`Uint8Array`と`ArrayBuffer`です。
Denoにおいては、例えば`Deno.readFile()`を使ってファイルの内容をUint8Arrayで取得することができます。

これらのオブジェクトは文字列と同様、Responseコンストラクタの第1引数に与えることができます。
また、クライアント側では`res.arrayBuffer()`メソッドを使用して値を取り出すことができます。


```ts:サーバー側
import { serve } from "https://deno.land/std@0.187.0/http/mod.ts";

const u8 = new Uint8Array([0, 1, 2])
serve(() => new Response(u8, {
  headers: { "Content-Type": "image/png" }, // Content-Typeはデータに合わせて適切なものを設定
}));
```


```ts:クライアント側
// res は Response オブジェクト
const res = await fetch("http://example.com/");
const arrayBuffer = await res.arrayBuffer(); // res.arrayBuffer()で、サーバー側で入れた値を取り出し
console.log(arrayBuffer); // => ArrayBufferオブジェクト

// ArrayBufferはUint8Arrayに変換できる
const u8 = new Uint8Array(arrayBuffer);
console.log(u8); // => Uint8Array([0, 1, 2])
```

なおResponseにContent-Typeを設定する際は、コンストラクタの第2引数に`headers`オブジェクトを設定します。

## ReadableStreamの受け渡し

Responseオブジェクトでは、Uint8Arrayが流れてくるReadableStreamを送受信できます。（TypeScriptで言うと`ReadableStream<Uint8Array>`型）

これらは主に、ファイルの内容をストリーミングする際に使われます。
大きなファイルの場合、ReadableStreamを使用することでメモリ効率が良くなるため、データの送受信を高速化できるというメリットがあります。

Denoにおいては、`(await Deno.open("file.txt")).readable`のような形で、ファイルの内容を読み出すReadableStreamを取り出すことができます。

```ts:サーバー側
import { serve } from "https://deno.land/std@0.187.0/http/mod.ts";

serve(async () => {
  const readableStream = (await Deno.open("path/to/file.txt")).readable
  return new Response(readableStream);
});
```


```ts:クライアント側
// res は Response オブジェクト
const res = await fetch("http://example.com/");

// res.bodyの中にReadableStreamが入っている
if (res.body) {
  // ReadableStreamを読み出してconsole.logで出力
  const reader = res.body.getReader();
  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    console.log(value);
  }
}
```

## その他のデータ形式

Responseオブジェクトは上記で紹介した以外にも、以下のデータ形式に対応しています。

- `FormData`オブジェクト
- `Blob`オブジェクト
- `DataView`オブジェクト
- `URLSearchParams`オブジェクト

各形式のオブジェクトについて、Response作成＆データ読み出し方法をまとめたものが以下の表になります。

|形式|Responseオブジェクトの作成|データの読み出し
|--|--|--|
文字列|`new Response("文字列")`|`await response.text()`
JSONデータ|`Response.json(jsonデータ)`|`await response.json()`
Uint8Array|`new Response(new Uint8Array())`|`new Uint8Array(await response.arrayBuffer())`
ArrayBuffer|`new Response(new ArrayBuffer())`|`await response.arrayBuffer()`
ReadableStream|`new Response(new ReadableStream())`|`response.body`
FormData|`new Response(new FormData())`|`await response.formData()`
Blob|`new Response(new Blob())`|`await response.blob()`
DataView|`new Response(new DataView())`|`new DataView(await response.arrayBuffer())`
URLSearchParams|`new Response(new URLSearchParams())`|`new URLSearchParams(await response.text())`

## データ形式は相互に変換できる

HTTP通信では最終的にバイナリデータにエンコードされて送受信が行われます。Responseオブジェクトは、そのエンコードとデコードを担当しています。

つまり、

- ReadableStreamから作成したResponseを文字列として取り出し
- 文字列から作成したResponseをJSONデータとして取り出し
- Uint8Arrayから作成したResponseをBlobとして取り出し

のように、データ形式を相互に変換することが可能です。

# Requestオブジェクトでも同様のことが可能

上ではResponseオブジェクトを使用してサーバー側からクライアント側へデータを送信する方法について解説しました。

Web標準APIには、Responseオブジェクトだけでなく、Requestオブジェクトも存在しています。

このRequestオブジェクトを使用すると、**POSTメソッドによるクライアント側からサーバー側へのデータ送信**についても、ほぼ同様にデータをやり取りすることができます。

```ts:クライアント側
fetch("http://example.com/", {
  method: "POST",
  body: "文字列", // 第2引数のbodyに送信するデータを指定する。
  // body: new Uint8Array(),
  // body: new FormData(),
  // ...etc
});
```

```ts:サーバー側
import { serve } from "https://deno.land/std@0.187.0/http/mod.ts";

serve(async (req) => {
  const data = await req.text(); // 文字列としてで読み取り
  const data = await req.json(); // JSONデータとして読み取り
  const data = await req.arrayBuffer(); // バイナリデータとして読み取り
  const data = await req.formdata(); // Formdata形式で読み取り
  // ...etc

  return new Response("OK");
});
```

# まとめ

- Responseオブジェクトを使用して、文字列、JSONデータ、バイナリデータなどを送受信することができる。
- Responseオブジェクトは、JavaScriptのオブジェクトをHTTP通信に流すバイナリデータにエンコード/デコードする役割を担当している。
- Requestオブジェクト：クライアント側→サーバー側のデータ送信に使用
- Responseオブジェクト：サーバー側→クライアント側のデータ送信に使用

なお、Responseオブジェクトがデータのデコード/エンコードを行うことを生かして、データ形式の変換に利用するというTipsが存在します。

```ts
// ReadableStreamをすべて読み取ったうえで文字列にデコードする関数
async function readableStreamToString(readableStream: ReadableStream<Uint8Array>) {
  // 一旦Responseオブジェクトを経由することでデータ形式を変換できる
  return await new Response(readableStream).text();
}
```

このように、ResponseオブジェクトはHTTP通信が関係ない場面でも活用されることがあります。