Overview
私はFunctionsからBigQueryに問い合わせて、結果をただ返したかっただけだった。
しかし、"message:"Bad Request", status:"INVALID_ARGUMENT"
の闇に遭難することになる。
今回はその中で、FunctionsのログにRequest has invalid method. GET
と出るケースを扱う。
Bad Requestは様々な理由が考えられるため、Functionsのログをみて該当するケースか確認してほしい。
(巨大なオブジェクトが出力されているため冒頭のエラーを見るのはなかなか大変だが)
Target reader
Request has invalid method. GET
が解決できないユーザー
Body
いきなり結論
結論的には似たようなケースを扱ったことがあるのでそこから抜粋する。
https://qiita.com/qrusadorz/items/40234ac0b5c5c2315cad
FirebaseのCloud Functionsには2つの関数があります。
- functions.https.onCall
- functions.https.onRequest
1はSDKが用意されているAndroid,iOS,Javascriptから呼び出されるタイプ。
https://firebase.google.com/docs/functions/callable?hl=ja
2はSDKがない場合、汎用のWebAPIとして作成するタイプ。
https://firebase.google.com/docs/functions/http-events?hl=ja
functions.https.onCall
で作っている場合、汎用のWebAPIとは異なりhttps.onCall のプロトコルの仕様
に従わなくてはならない。
どういう縛りがあるのか、公式ドキュメントから仕様を抜粋する。
https://firebase.google.com/docs/functions/callable-reference?hl=ja
リクエストの形式: ヘッダー
呼び出し可能なトリガー エンドポイントへの HTTP リクエストは、次のヘッダーが含まれる POST にする必要があります。必須: Content-Type: application/json
オプション: Authorization: Bearer
オプション: Firebase-Instance-ID-Token:その他のヘッダーが含まれる場合は、下記のレスポンスに関するドキュメントで説明されているように、リクエストは拒否されます。
まずヘッダーにapplication/jsonが必須。
日本語がおかしいが、リクエストはGETではなくPOSTでなくてはならない。
リクエスト本文
HTTP リクエストの本文は、次のいずれかのフィールドが含まれる JSON オブジェクトにする必要があります。必須: data - 関数に渡される引数です。これには任意の有効な JSON 値を使用できます。これは、以下で説明するシリアル化形式に従って、>JavaScript のネイティブ型に自動的にデコードされます。
リクエストに他のフィールドが存在する場合、バックエンドはそのリクエストの形式を不正とみなし、リクエストは拒否されます。
そして本体にはdataフィールドが必須。
私がどうやってAPIにアクセスしていたか、確認しよう。
serve
コマンドでローカルサーバーを立てる。
準備ができると、以下のようなログがVSCodeのターミナルに出力される。(Function名はsearchItems)
functions[searchItems]: http function initialized (http://localhost:5000/<project-Id>/asia-northeast1/searchItems).
ここでツールチップの通り、Ctrl+クリックで()n内のURLにアクセスしていた。
つまりhttps.onCall のプロトコルの仕様
を完全に無視したわけだ。
それは1時間たっても解決するはずないですわ
POSTでリクエストを送るため、POSTMAN…、ここはVSCodeでお手軽なやつを探して、@toshi0607さんの紹介されているREST Clientを採用。
https://qiita.com/toshi0607/items/c4440d3fbfa72eac840c
request.httpというファイルを作って、下記のリクエストを作成。
<projectId>
は自身のものを利用すること。
Content-TypeとBodyの間に1行の空行は必須。
POST https://asia-northeast1-<projectId>.cloudfunctions.net/searchItems
Content-Type: application/json
{
"data": {}
}
Send Requestボタンをクリックすると正常に応答が来る。
めでたくBigQueryよりデータが取れている。
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
(中略)
{
"result": [
{
"id": 1,
"text": "item1",
"timestamp": 1
},
{
"id": 2,
"text": "item2",
"timestamp": 2
}
]
}
Conclusion
VSCodeの甘い誘いにより、泥沼に引き込まれた話でした。
functions.https.onCall
はクライアントからコールする際は便利ですが、WebAPIとして利用する場合には隠蔽されている部分に準拠する必要があるので注意しましょう。
今回は認証を使っていないため軽傷でしたが、認証が必須のFunctionではAuthorization
にログイン後にuserオブジェクトが持つトークンが必要になります。
トークンは1時間で無効になるおまけ付きなので、REST Clientからコールするよりアプリからコールすることを検討したほうがいいでしょう。
(アプリでは自動的にトークンの期限が延長されるため、期限切れの心配が不要です)