HTTPタイプのファンクションを作成する
このページでは、HTTPタイプのファンクションの記法およびファンクション作成時のルールについて説明します。
HTTPタイプのファンクションの記法
ファンクションの実行ランタイムやエントリポイント、利用できるモジュールは ファンクションの書き方 で説明しました。ここでは主に、ファンクションの入力データについて説明します。
入力 (data)
ファンクションのエントリポイントでは、引数 data で入力値を受け取ります。HTTPタイプのファンクションの場合、data として req オブジェクトと res オブジェクトを値に持つオブジェクトを受け取ります。
export default async function (data, {}) {
// data は req, res オブジェクトを持つ const { req, res } = data;
// ... 後続処理を書く}これらの値はそれぞれ、Express.js の リクエスト(req) オブジェクトと レスポンス(res) オブジェクトです。HTTPタイプのファンクションではこれらの値を使ってHTTPリクエストを処理します。
req でHTTPリクエストから情報を取得する
req オブジェクトにはHTTPリクエストの情報が含まれています。例えば次のプロパティにアクセスできます。
req.method: HTTPリクエストのメソッドreq.query: クエリパラメータreq.body: リクエストボディreq.get(): リクエストヘッダーの値を取得する
なお、Craft Functionsでは body-parser を使ってContent-Typeヘッダーに基づいてリクエスト本文を解析しています。そのため、 req.body および req.rawBody オブジェクトに直接アクセスできます。
許可されているリクエストヘッダー
Craft Functionsでは、次のリクエストヘッダーが許可されています。
hostacceptaccept-encodingaccept-languageaccess-control-request-methodaccess-control-request-headerscache-controlcookieconnectionforwardedprioritysec-ch-uasec-ch-ua-mobilesec-ch-ua-platformsec-fetch-destsec-fetch-sitesec-fetch-userupgrade-insecure-requestsviax-forwarded-forx-forwarded-protoauthorizationuser-agentoriginreferercontent-lengthcontent-typex-hub-signature-256x-line-signaturex-slack-signaturex-slack-request-timestamp
これ以外のリクエストヘッダーが必要な場合は craft-functions-custom-headers を利用できます。このリクエストヘッダーを使用することで、ファンクション内で追加の情報を取り扱うことが可能になります。
さらに他のリクエストヘッダーを追加する必要がある場合は、KARTE Craft | リクエストヘッダー 追加申請フォーム から申請してください。
res でHTTPレスポンスを行う
HTTPタイプのファンクションでは res.send(), res.json() をはじめとする res オブジェクトのレスポンス用のメソッドを使ってHTTPレスポンスを行います。
以下は res.send() メソッドでプレーンテキストを返却する例です。
export default async function (data, {}) { const { req, res } = data;
// `plain/text` を返却する res.send('Hello!');}JSON形式 ( Content-Type: application/json ) で値を返す場合は res.json() メソッドを使います。
export default async function (data, {}) { const { req, res } = data;
res.json({ name: 'Karte Taro' });}200以外のステータスコードを返却する場合は res.status() メソッドを使用します。
export default async function (data, {}) { const { req, res } = data;
res.status(404).send('Not Found')}ファンクションの実行方法
HTTPタイプのファンクションは、ファンクションのエンドポイントURLにHTTPリクエストを行うことで実行できます。エンドポイントURLはファンクション詳細画面の「設定」タブで確認できます。
- ファンクションのURLは
https://DOMAIN_PREFIX.cev2.karte.io/functions/ENDPOINT_SUFFIXの形式です。DOMAIN_PREFIXはKARTEプロジェクトごとに付与されます。ENDPOINT_SUFFIXはファンクションごとに付与されます。
HTTPタイプのファンクションは次のメソッドに対応しています。
GETPOSTPUTDELETEOPTIONS
ここでは、curlコマンドで GET および POST メソッドでファンクションを実行する方法を紹介します。
GET リクエストの送信
以下はcurlコマンドでファンクションのエンドポイントURLに name, age クエリパラメータと共に GET リクエストを送信する例です。
curl 'https://DOMAIN_PREFIX.cev2.karte.io/functions/ENDPOINT_SUFFIX?name=Taro&age=20'DOMAIN_PREFIXおよびENDPOINT_SUFFIXはそれぞれ、実際のエンドポイントURLのものに置き換えます。- クエリパラメータ (
?name=Taro&age=20) は、ファンクション内でreq.queryとして参照できます。
POST リクエストの送信
以下はcurlコマンドでファンクションのエンドポイントURLに POST リクエストを送信する例です。
curl 'https://DOMAIN_PREFIX.cev2.karte.io/functions/ENDPOINT_SUFFIX' \-H 'Content-Type: application/json' \-d '{ "name": "Taro", "age": 20}'DOMAIN_PREFIXおよびENDPOINT_SUFFIXはそれぞれ、実際のエンドポイントURLのものに置き換えます。- リクエストボディ (
-dオプションの値) は、ファンクション内でreq.bodyとして参照できます。
HTTPタイプのファンクションのルール
HTTPタイプのファンクション特有のルールを説明します。
必ず res で明示的にレスポンスする
HTTPタイプのファンクションでは、res オブジェクトのメソッドを使ってHTTPレスポンスを送信してください。
次の例では res オブジェクトでレスポンスを行わずに return で値を返却しようとしています。HTTPタイプのファンクションは、return で返り値を渡してもHTTPレスポンスを行いません。
export default async function (data, {}) { const { req, res } = data;
// NG: res でレスポンスせずに return する return 'Hello!';}このような場合は、例えば次のように、res.send() メソッドでHTTPレスポンスを送ります。
export default async function (data, {}) { const { req, res } = data;
// NG: res でレスポンスせずに return する return 'Hello!'; // OK: res オブジェクトのメソッドを使ってHTTPレスポンスを行う res.send('Hello!');}ファンクション内でHTTPレスポンスを行わない場合、Craft Functionsは自動的に 599 ステータスのエラーレスポンスを返却します。
res によるレスポンスは1回の実行につき1回のみ
res.send(), res.json() 等のHTTPレスポンスを行うメソッドは、ファンクションの1実行につき1回のみ使えます。2回目以降はエラーになるので注意しましょう。
例えば、次のコードはリクエストのContent-Typeによって異なるレスポンスを行います。このコードは、Content-Type: application/json のリクエストを受け取った際にレスポンス用のメソッドを2回呼び出します。
export default async function (data, {}) { const { req, res } = data;
const contentType = req.get('content-type');
if (contentType === 'application/json') { ({ name } = req.body); res.json({ message: `Hello ${name}` }); }
// application/json タイプのリクエストを受け取ると以下のエラーになる // Error: Can't set headers after they are sent. ... res.send({ message: `Hello World!` });}改善方法として、if文内でHTTPレスポンスを行う際に return して、後続処理をスキップする方法があります。
if (contentType === 'application/json') { ({ name } = req.body); res.json({ message: `Hello ${name}` }); return res.json({ message: `Hello ${name}` });}その他仕様
ファンクションの記法に関わらない、HTTPタイプのファンクション特有の仕様を説明します。
特定のエラーレスポンス
ファンクションの終了ステータスは res.status() で柔軟に設定できます。ただし、次の状況ではファンクションは特定のステータスコードを返却します。
resによるレスポンスを行わずにファンクションを終了した場合、ファンクションはステータスコード599を返却します。- タイムアウトした場合、ファンクションはステータスコード504を返却します。
サンプルコード
HTTPタイプのファンクションのサンプルコードを紹介します。
リクエストの内容に基づきレスポンスを変える
以下はHTTPタイプのファンクションのサンプルコードです。このコードでは次の処理を行っています。
- HTTPリクエストのメソッドを検証
POSTリクエストのみを許容する
- リクエストの
Content-Typeヘッダーの値によってHTTPレスポンスの内容を変えるapplication/jsonの場合はリクエストボディのnameプロパティの値を使ってJSON文字列でレスポンスを行うtext/plainの場合はリクエストボディの値をそのまま使ってテキストメッセージをレスポンスを行う- それ以外の場合は固定のメッセージでレスポンスを行う
const LOG_LEVEL = 'DEBUG';
export default async function (data, { MODULES }) { const { req, res } = data; const { initLogger } = MODULES; const logger = initLogger({ logLevel: LOG_LEVEL });
// HTTPメソッドがPOSTでなければ処理を終了 if (req.method !== 'POST') { res.setHeader('Allow', 'POST'); res.status(405).send('Method Not Allowed'); return; }
let name;
// リクエストのContent-Typeに応じて処理を分岐 switch (req.get('content-type')) { case 'application/json': // Content-Typeがapplication/jsonの場合の処理 logger.log(`content-type is application/json`); ({ name } = req.body); // 可読性のために明示的に 200 を返してもよい res.status(200).json({ message: `Hello ${name}` }); break;
case 'text/plain': // Content-Typeがtext/plainの場合の処理 logger.log(`content-type is text/plain`); name = req.body; res.status(200).send(`Hello ${name}`); break;
default: // どのContent-Typeにも該当しない場合の処理 name = 'World!'; res.status(200).json({ message: `Hello ${name}` }); }}req, res オブジェクトのより詳しい利用方法については、Express.jsのリファレンスを参照してください。
CORSへの対応
ブラウザからURLエンドポイントにリクエストする際には CORS (オリジン管理ソース共有、Cross-Origin Resource Sharing) への対応が必要です。具体的には、クライアントからのプリフライトリクエストに応答してください。
以下は特定のオリジン ( https://hoge.example.com ) に対してクロスオリジンリクエストを許可する場合のレスポンス例です。
const ALLOWED_ORIGIN = 'https://hoge.example.com'
export default async function (data, {}) { const { req, res } = data;
// GETリクエストについても Access-Control-Allow-Origin ヘッダーを付与 res.set('Access-Control-Allow-Origin', ALLOWED_ORIGIN); if (req.method === 'OPTIONS') { res.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.set('Access-Control-Allow-Headers', 'Content-Type'); res.set('Access-Control-Allow-Credentials', true); return res.status(204).send(''); }
res.send('hello!');}複数のオリジンに許可をする場合は、次のようにファンクション側で許可対象のオリジンリストを保持して、リクエスト内容と比較した上で Access-Control-Allow-Origin の値を指定します。
const ALLOWED_ORIGINS = [ 'https://hoge.example.com', 'https://fuga.example.com',];
export default async function (data, {}) { const { req, res } = data;
// Origin ヘッダーの値を検証して Access-Control-Allow-Origin ヘッダーに値を付与する const srcOrigin = req.get('origin'); if (ALLOWED_ORIGINS.includes(srcOrigin)) { res.set('Access-Control-Allow-Origin', srcOrigin); }
if (req.method === 'OPTIONS') { res.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.set('Access-Control-Allow-Headers', 'Content-Type'); res.set('Access-Control-Allow-Credentials', true); return res.status(204).send(''); }
res.send('hello!');}イベント駆動タイプのファンクションの エンドポイント トリガーと同等のレスポンスをする場合は次のようにOriginヘッダーの値をそのまま Access-Control-Allow-Origin ヘッダーの値に利用します。
export default async function (data, {}) { const { req, res } = data;
// Origin ヘッダーの値を検証して Access-Control-Allow-Origin ヘッダーに値を付与する const srcOrigin = req.get('origin'); if (srcOrigin) res.set('Access-Control-Allow-Origin', srcOrigin);
if (req.method === 'OPTIONS') { res.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.set('Access-Control-Allow-Headers', 'Content-Type'); res.set('Access-Control-Allow-Credentials', true); return res.status(204).send(''); }
res.send('hello!');}