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では、次の標準ヘッダーが許可されています。
host
accept
accept-encoding
accept-language
access-control-request-method
access-control-request-headers
cache-control
cookie
connection
forwarded
priority
sec-ch-ua
sec-ch-ua-mobile
sec-ch-ua-platform
sec-fetch-dest
sec-fetch-site
sec-fetch-user
upgrade-insecure-requests
via
x-forwarded-for
x-forwarded-proto
authorization
user-agent
origin
referer
content-length
content-type
x-hub-signature-256
x-line-signature
x-slack-signature
x-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タイプのファンクションは次のメソッドに対応しています。
GET
POST
PUT
DELETE
OPTIONS
ここでは、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!');}
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
によるレスポンスを行わずにファンクションを終了した場合、ファンクションはステータスコード500を返却します。- タイムアウトした場合、ファンクションはステータスコード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!');}