Compute 向けの Node.js 形式 HTTP インターフェイス
Fastly の Compute JavaScript プラットフォームが提供するリクエストオブジェクトとレスポンスオブジェクトは、Node.js プログラムで従来使用されている req オブジェクトおよび res オブジェクトではなく、fetch 標準に基づいています。Node.js 向けに設計したプログラムを Compute に移行することを検討している場合や、使用したいライブラリが Node 向けに設計されている場合、Fastly の新しいオープンソースライブラリ、http-compute-js が役立ちます。
Node.js は長年にわたり、Web サーバーに送信したリクエストを表す IncomingMessage オブジェクトとサーバーからのレスポンスを表す ServerResponse オブジェクトを提供してきました。これらのオブジェクトは、最先端の fetch 標準によって定義され、Compute で JavaScript によって実装された Request オブジェクトや Response オブジェクトとは一致しません。fetch は Node.js の最近のバージョンではネイティブサポートされているものの、現在の Node.js コードの多くは fetch 向けには書かれていません。
そこで Fastly は http-compute-js
によって、なじみ深い Node.js と互換性のあるインターフェイスを持つオブジェクトを開発者が利用できるようにしました。これにより、ユーザーやユーザーが使用するライブラリはこれらのオブジェクトをサポートすることが可能になります。
Node.js の http オブジェクトが Compute で利用可能に
以下をご覧ください。
import http from '@fastly/http-compute-js';
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
data: 'Hello World!'
}));
});
server.listen();
上部に import
ステートメントがなければ、通常の Node.js プログラムのように見えます。createServer 関数は Node.js プログラムと同じように、使用できる引数を含むコールバックを渡すイベントハンドラーを受け入れます。
req
は IncomingMessage オブジェクトで、その readable
ストリーミングインターフェイスは Compute リクエストのボディストリームに接続されています。そのため、on('data')
や on('end')
などの標準ストリームメカニズムの使用、別のストリームとの接続、parse-body などのライブラリの使用によって読み取ることができます。また、Node.js と同様、ヘッダーなどの情報を読み取ることも可能です。
res
は ServerResponse オブジェクトで、書き込み可能なストリームインターフェイスがインメモリバッファに接続されています。通常 res.write()
や res.end()
を使用して書き込んだり、res.pipe()
でパイプしたりします。また、Node.js と同じ方法でヘッダーとステータスコードも設定できます。
以下はそうした機能を含む、先ほどより少々複雑な例です。
import http from '@fastly/http-compute-js';
const server = http.createServer(async (req, res) => {
// Get URL, method, headers, and body from req
const url = req.url;
const method = req.method;
const headers = {};
for (let [key, value] of Object.entries(req.headers)) {
if(!Array.isArray(value)) {
value = [String(value)];
}
headers[key] = value.join(', ');
}
let body = null;
if (method !== 'GET' && method !== 'HEAD') {
// Reading data out of a stream.Readable
body = await new Promise(resolve => {
const data = [];
req.on('data', (chunk) => {
data.push(chunk);
});
req.on('end', () => {
resolve(data.join(''));
});
});
}
// Write output to res
res.setHeader('Content-Type', 'application/json');
res.statusCode = 200;
res.write(JSON.stringify({
url,
method,
headers,
body,
}));
res.end();
});
server.listen();
createServer
に渡されるコールバックは非同期である点に注目してください。レスポンスは待機の後に作成されますが、ハンドラーは res.end()
を待ってから Response
を作成して基盤となる Compute のfetch
イベントハンドラーを通じて返すことができます。
ポリフィルを利用する
http-compute-js
が想定したとおりの動作をするよう、十分に実証された既存のポリフィルを、根底となる部分、例えばストリーミングやバッファリングなどに使用することにしました。Compute JavaScript プログラムでは webpack を使用して Web ワーカーにバンドルし、そこにこれらのポリフィルを追加することができます。そのため、http-compute-js
を使用するには、JavaScript Compute プロジェクトに含まれる webpack.config.js
ファイルにいくつか変更を加える必要があります。
以下の webpack.ProvidePlugin()
をプラグイン配列に追加し、以下のアイテムを alias および fallback セクションに追加して、必要に応じて resolve、alias、fallback プロパティを作成します。
module.exports = {
/* ...other config... */
plugins: [
new webpack.ProvidePlugin({
Buffer: [ 'buffer', 'Buffer' ],
process: 'process',
setTimeout: [ 'timeout-polyfill', 'setTimeout' ],
clearTimeout: [ 'timeout-polyfill', 'clearTimeout' ],
}),
],
resolve: {
alias: {
'timeout-polyfill': require.resolve('@fastly/http-compute-js/dist/polyfill'),
},
fallback: {
'buffer': require.resolve('buffer/'),
'process': require.resolve('process/browser'),
'stream': require.resolve('stream-browserify'),
}
},
};
これらを設定すると、上述した例が、Node.js で動作するのと同じように動作するようになります。Compute ではまもなく setTimeout
のサポートも追加する予定ですので、いずれポリフィルの数を削減できるようになるかもしれません。
req と res の手動によるインスタンス化
場合によっては、Node.js 形式のリクエストオブジェクトとレスポンスオブジェクトを、プログラムの一部でのみ使用する必要があるかもしれません。また、それらと連携する関数に一度限りのコールを実行するケースもあり得ます。そうした場合のために、Compute で使用されている Request
オブジェクトや Response
オブジェクトと、それらに相当する Node.js 互換のオブジェクトを変換するためのユーティリティ関数を用意しています。
/// <reference types='@fastly/js-compute' />
import { toReqRes, toComputeResponse } from '@fastly/http-compute-js';
addEventListener('fetch', (event) => event.respondWith(handleRequest(event)));
async function handleRequest(event) {
// Create Node.js-compatible req and res from event.request
const { req, res } = toReqRes(event.request);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
data: 'Hello World!',
url: req.url,
}));
// Create a Compute@Edge Response object based on res, and return it
const response = await toComputeResponse(res);
return response;
}
この例から、fetch ハンドラーの一環として Node 互換の req
や res
がいかに簡単に作成できるか、また、Compute のResponse
オブジェクトに変換して返されるプロセスがお分かりいただけます。
エッジでより多くのコードを実行
プログラミング可能なプラットフォームとしてのインターネットは日々進化し続けています。Fastly はこの業界のリーダーであることを誇りに思うとともに、Compute が開発者にとって魅力的なプラットフォームであり続けられるよう努めています。
絶え間なく進化しているということは、常にすべてが変わり続けているということです。Fastly では、開発者がより多くのコードをエッジで実行し、素早く開発できるようサポートするツールを構築すると同時に、さらに幅広いツールを使用できる環境を提供したいと考えています。開発者の皆様がこのツールを活用してどのようなものを構築するか、楽しみにしております。ぜひ Twitter でお聞かせください。