はじめに
みなさん、こんにちは。
KDDIウェブコミュニケーションズ、CPaaSエバンジェリストの高橋です。
今回、初めて Cloudflare の FaaS でもある Workers を使ってみたのですが、リクエストのパラメータ受け取り方法などが調べてもあまり出てこなかったので、こんな感じで使えば良いのではというサンプルを作成してみました。
なにせ、Cloudflare 自体が初心者なもので、これが正しいかはわかりませんが、何かのお役に立てばと思います。
今回は Cloudflare のセットアップや、Wrangler の使い方などには触れていません。
シナリオ
このサンプルは、以下の3つのリクエストを想定した Workers になっています。これらを1つの Wokers で処理します。使用した Wrangler のバージョンは、3.1.1
です。
メソッド | パス | クエリーパラメータ | ボディパラメータ | レスポンス |
---|---|---|---|---|
GET | なし | なし | - | OK |
GET | /hello | name, age, gender | Hello [name] ! Age: [age] Gender: [male|female] | |
POST | なし | なし | {"key1":"value","key2":2, "key3": "a"} | {"url":"https://[URL]/","path": "","key1": "value","key2": 2,"key3": "a"} |
実行結果
GET パス:/hello、クエリーパラメータ:name=hoge&age=20&gender=male
コード
import vs from 'value-schema';
export interface Env {
}
// クエリーパラメータのスキーマ定義
// Learn more at https://github.com/shimataro/value-schema
interface QueryParams {
[key: string]: string;
}
const QuerySchema = {
name: vs.string({
ifUndefined: 'world',
}),
age: vs.number(),
gender: vs.string({
only: ['male', 'female'],
}),
}
// Bodyパラメータのスキーマ定義
// Learn more at https://github.com/shimataro/value-schema
const RequestBodySchema = {
key1: vs.string(),
key2: vs.number({
ifUndefined: 0,
}),
key3: vs.string({
only: ['a', 'b', 'c'],
}),
}
// パラメータのエラーハンドラ
let errors:string[] = [];
const errorHandler = (err: any) => {
const key = err.keyStack.shift();
errors.push(key);
}
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
console.info(`🐞 Function start...`);
const url = new URL(request.url);
const pathArray = url.pathname.split('/');
const searchParams = url.searchParams;
const queryParams: QueryParams = {}
for (const [key, value] of searchParams) queryParams[key] = value;
// メソッドとパスごとに処理を分岐
switch (`${request.method}/${pathArray[1]}`) {
case 'GET/':
try {
// レスポンスの返却
return new Response(`OK`);
} catch (error) {
console.error(`👺 ${error}`);
return new Response(`${error}`);
}
case 'GET/hello':
try {
// クエリーパラメータのチェック
errors = [];
const data = vs.applySchemaObject(QuerySchema, queryParams, errorHandler, () => {
// エラーが発生するたびに errorHandler が呼ばれ、すべてのエラーチェックが終わったらerrorを投げる
throw new Error(`👺 Parameter error. ${errors.join(', ')}`);
});
// レスポンスの返却
return new Response(`Hello ${data.name} ! Age: ${data.age} Gender: ${data.gender}`);
} catch (error) {
console.error(`👺 ${error}`);
return new Response(`${error}`);
}
case 'POST/':
try {
// Bodyパラメータのチェック
errors = [];
const data = vs.applySchemaObject(RequestBodySchema, await request.json(), errorHandler, () => {
// エラーが発生するたびに errorHandler が呼ばれ、すべてのエラーチェックが終わったらerrorを投げる
throw new Error(`👺 Parameter error. ${errors.join(', ')}`);
});
// レスポンスの返却
const result = JSON.stringify({
url: request.url,
path: pathArray[1],
key1: data.key1,
key2: data.key2,
key3: data.key3,
});
return new Response(result, {
headers: {
'content-type': 'application/json;charset=UTF-8',
},
})
} catch (error) {
console.error(`👺 ${error}`);
return new Response(`${error}`);
}
default:
throw new Error(`${request.method} is not supported.`);
}
},
};
解説
ではここからはコードの解説をしていきます。
今回の趣旨は、リクエスト時の各種パラメータの取得です。これらの情報は、一番最初に受け取るRequest
オブジェクトから取得することができます。
Request
オブジェクトは、以下のような形で、リクエストを受け付けたときに取得できます。
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
...
},
}
Request
オブジェクト自体は、Cloudflare の仕様ということではなく、W3C の WebAPI 仕様にある、Fetch API で規定されているものです。
では、このRequest
オブジェクトを使って、各種パラメータを取得する方法について見ていきましょう。
メソッドの判定
メソッドは、request.method
で取得可能です。
const method = request.method; // GETやPOSTが返ります
クエリーパラメータ
クエリーパラメータというのは、たとえばhttps://example.com/?key1=value1&key2=value2
のようなURLの中の、key1
とかkey2
に当たる部分です。
これらを取得するには、まずはResponse
オブジェクトからURL
オブジェクトを取り出し、URL
オブジェクトのsearchParams
パラメータで取得できます。具体的には以下のようになります。
const url = new URL(request.url);
const searchParams = url.searchParams;
取り出した結果は、for
でループさせることで、keyとvalueに分解することができます。
for (const [key, value] of searchParams) console.info(key, value);
// key1 value1
// key2 value2
パスの取得
パスというのは、たとえばhttps://example.com/path1?key1=value1&key2=value2
のようなURLの中の、path1
に当たる部分です。
パスを取得するには、URL
オブジェクトのpathname
パラメータを取得すればよいのですが、パスは / を使って何段にも分けることが可能なので、以下のようにして最初のパスを取得します。
const pathArray = url.pathname.split('/');
const path = pathArray[1];
pathArray配列の1番目(pathArray[0])にはドメイン名が入るので、パスは2番目以降に入る点に注意してください。
Body パラメータの取得
POST
メソッドでは、Body 部にデータを入れてリクエストすることがあります。
たとえば、JSON 形式で Body 部に指定されたパラメータを取得するには以下のように記述します。
const data = await request.json();
Request
オブジェクトのjson()
メソッドは非同期でPromise
を返すようになっていますので、await
を使って処理を行っています。
パラメータをバリデーションする
リクエストを受け付けたあとに、パラメータの整合性チェックを行うケースがほとんどだと思います。
これらをよりシンプルにするために、サンプルコード内では、value-schema
ライブラリを使っています。
value-schema
では、たとえば以下のようにパラメータのスキーマを定義することができます。
const RequestBodySchema = {
key1: vs.string(),
key2: vs.number({
ifUndefined: 0,
}),
key3: vs.string({
only: ['a', 'b', 'c'],
}),
}
これは以下の制約を意味しています。
- key1という名前の
String
型パラメータが必須です。 - key2については
Number
型の任意のパラメータになりますが、値が設定されていない場合は0として処理します。 - key3は、
a
、b
もしくはc
のいずれかがString
型として指定されている必要があります。
それで、このスキーマを実際の Body パラメータでチェックするのが、以下のコードになります。
// Bodyパラメータのチェック
const data = vs.applySchemaObject(RequestBodySchema, await request.json(), errorHandler, () => {
// エラーが発生するたびに errorHandler が呼ばれ、すべてのエラーチェックが終わったらerrorを投げる
throw new Error(`👺 Parameter error. ${errors.join(', ')}`);
});
value-schema
のapplySchemaObject
メソッドを使うと、スキーマとの整合性を確認することができます。引数の1番目がスキーマ定義、2番目は比較対象となるJSONオブジェクト、エラーがある場合はパラメータごとに第3引数(このケースでは、errorHandler
)が呼ばれ、すべてのエラーがチェックし終わったときに第4引数がコールバックされます。
第3、第4引数はエラーがない場合はコールされません。
クエリーパラメータも同様の方法でバリデーションが可能ですが、url.searchParams
では JSON オブジェクトが返らないので、このサンプルコード内ではバリデーション用に JSON を生成してからチェックしています。
まとめ
まだまだ Workers 初心者ですが、このプロダクトは大変おもしろいので、これからも色々と触っていきたいと思います。
KDDIウェブコミュニケーションズ(KWC)について
https://cloudapi.kddi-web.com
残念ながら2023年5月1日をもって、KWCは10年間に渡るTwilioのリセール事業を終了いたしましたが、引き続きCPaaS市場において、過去の実績や知見をもとによりよいサービスを提供するべく活動を続けております。
また引き続き、Twilioのサポートについても、CPaaS Care(仮)という形で無償にて対応させていただくこととなります。詳しくは上記サイトをご覧ください。