LoginSignup
1
1

Cloudflare Workers で、リクエストを処理するサンプル

Posted at

はじめに

みなさん、こんにちは。
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 パス:なし、クエリーパラメータ:なし
スクリーンショット 2023-07-13 7.11.34.png

GET パス:/hello、クエリーパラメータ:name=hoge&age=20&gender=male
スクリーンショット 2023-07-13 7.12.26.png

POST パス:なし、Body: JSON
スクリーンショット 2023-07-13 7.11.03.png

コード

worker.ts
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は、abもしくはcのいずれかがString型として指定されている必要があります。

それで、このスキーマを実際の Body パラメータでチェックするのが、以下のコードになります。

// Bodyパラメータのチェック
const data = vs.applySchemaObject(RequestBodySchema, await request.json(), errorHandler, () => {
	// エラーが発生するたびに errorHandler が呼ばれ、すべてのエラーチェックが終わったらerrorを投げる
	throw new Error(`👺 Parameter error. ${errors.join(', ')}`);
});

value-schemaapplySchemaObjectメソッドを使うと、スキーマとの整合性を確認することができます。引数の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(仮)という形で無償にて対応させていただくこととなります。詳しくは上記サイトをご覧ください。

自己紹介  
高橋克己(Katsumi Takahashi) 自称「□い芸人
グローバル・インターネット・ジャパン株式会社 代表取締役
株式会社KDDIウェブコミュニケーションズ コミュニケーションDX事業部エバンジェリスト

2001年より大手通信事業者の法人サービスの教育に携わり、企業における電話のしくみや重要性を研究。2016年よりKWCにジョインし、Twilioを使ったスマートコミュニケーションの普及活動を精力的に行っている。
2015 Hall of Doers
2019 Twilio Champions
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1