Web標準にはHTTP通信のリクエスト/レスポンスを表すRequest / Responseオブジェクトが存在します。
これらはそれぞれfetch()
関数の引数と返り値であり、フロントエンドで主に利用されています。加えて最近はDenoやNode.jsなどのサーバーサイドでもこれらのオブジェクトが活用されており、クライアント・サーバー間で様々な形式のデータを送受信することが可能になっています。
当記事ではResponseオブジェクトを使用したデータの受け渡しを、Denoを用いて解説していきます。
Responseオブジェクトを使用してデータを受け渡しする
当記事では解説のためにDeno標準ライブラリのHTTPサーバーを利用します。
このサーバーは、HTTPサーバーを「Requestオブジェクトを受け取ってResponseオブジェクトを返す関数」として書くことができます。
import { serve } from "https://deno.land/std@0.187.0/http/mod.ts";
serve(() => new Response());
// ^ Requestオブジェクトを受け取ってResponseオブジェクトを返す関数
クライアント側 (ブラウザを想定) では、fetch()
関数を使用してHTTPリクエストを送信します。fetch
関数の返り値も、Responseオブジェクトです。
// 変数resにはResponseオブジェクトが入る
const res = await fetch("http://example.com");
ここからは、Deno標準ライブラリのHTTPサーバーとfetch関数を使用して、サーバーからクライアント側に様々な形式のデータを送信する方法について見ていきます。
文字列の受け渡し
文字列データを受け渡しするには、サーバー側でResponse
コンストラクタの引数に文字列を入れ、クライアント側でawait res.text()
で値を取り出せばよいです。
import { serve } from "https://deno.land/std@0.187.0/http/mod.ts";
serve(() => new Response("text data"));
// 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データを取り出すことができます。
import { serve } from "https://deno.land/std@0.187.0/http/mod.ts";
// Response.json()を使用すると自動でContent-Typeがapplicatin/jsonに設定される
serve(() => Response.json({ key: "value" }));
// 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()
メソッドを使用して値を取り出すことができます。
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はデータに合わせて適切なものを設定
}));
// 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を取り出すことができます。
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);
});
// 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メソッドによるクライアント側からサーバー側へのデータ送信についても、ほぼ同様にデータをやり取りすることができます。
fetch("http://example.com/", {
method: "POST",
body: "文字列", // 第2引数のbodyに送信するデータを指定する。
// body: new Uint8Array(),
// body: new FormData(),
// ...etc
});
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が存在します。
// ReadableStreamをすべて読み取ったうえで文字列にデコードする関数
async function readableStreamToString(readableStream: ReadableStream<Uint8Array>) {
// 一旦Responseオブジェクトを経由することでデータ形式を変換できる
return await new Response(readableStream).text();
}
このように、ResponseオブジェクトはHTTP通信が関係ない場面でも活用されることがあります。