Function Compute + API GatewayでPOSTのエンドポイントを作る
アリババクラウド のFunction Compute + API GatewayでPOSTのエンドポイントを作ってリクエストボディを拾うのが大変でした。
今回のソースコード
// index.js const atob = require('atob'); const createMessage = (parameters) => { // データを保存するなりなんなりと } module.exports.handler = (event, context, callback) => { const query = JSON.parse(event.toString('utf8')) createMessage(JSON.parse(atob(query["body"]))); callback(null, { statusCode: 201, body: 'success!' }); };
ここでポイントが2つあります。
fc-helper
のhook
は使うことができない- リクエストボディを拾うのに手間がかかる
それぞれに関して補足します。
fc-helper
のhook
が使えない
hookを使うときはhook(async (ctx) => {})
のようなインターフェイスで使うのですが、API GatewayからFunction Computeにリクエストボディが渡らないためにctx.req
が空になってしまうので使い物になりません。
ですので(event, context, callback) => {})
のスタイルを使いましょう。
fc-helper
使えないならテストがめんどくさいですね・・・
改善を期待します。
リクエストボディを拾うのに手間がかかる
やたらと遠くにいて拾いにくいです。
> const request = JSON.parse(event.toString('utf8')) {"body":"CxxxxxxxxxxxxogInxxxxxxxxxxlIGJvZHkgfSB9In0K","headers":{"X-Ca-Api-Gateway":"101CFC98-4F9C-46EF-BBDF-36D280F45D33","X-Real-IP”:”xxx.xxx.xxx.xxx”,”X-Forwarded-Proto":"http","X-Forwarded-For":"xxx.xxx.xxx.xxx","User-Agent":"curl/7.54.0","Content-Type":"application/json","Accept":"*/*","CA-Host":"10ba8f4b3ea74xxxxxx0e5c4-cn-shanghai.alicloudapi.com"},"httpMethod":"POST","isBase64Encoded":true,"path":"/","pathParameters":{},"queryParameters":{}} > atob(request["body"]) CxxxxxxxxxxxxogInxxxxxxxxxxlIGJvZHkgfSB9In0K
さらに注意
本題とはあまり関係ありませんが、callback
の引数に気をつけましょう。第二引数のオブジェクトのstatusCode
とbody
は必須です。
API GatewayのmethodをPOSTにするのを忘れないようにしましょう。template.yml
を使いまわしてGETのままで少し詰まりました。
Function Computeの自動テスト
Function Computeに対するテストはfc-helper
のtest
を使って行います。
ここではアクセスがあったらhello world!\n
というbodyと200のステータスを返すエンドポイントを想定します。
// index.js const { hook } = require('fc-helper'); exports.handler = hook(async (ctx) => { ctx.body = 'hello world!\n'; });
このコードに対するテストコードは以下のようになります。
// test/index.test.js const assert = require('assert'); const { test } = require('fc-helper'); const index = require('../index.js') describe('hello world', () => { it('should return correct response', async () => { const res = await test(index.handler).run('{}', '{}'); assert.equal(res.statusCode, 200, 'status code') assert.equal(res.body, 'hello world!\n', 'http body') }); });
めちゃくちゃ便利ですね。
serverless frameworkを使ってFunction ComputeとAPI Gatewayにデプロイする
serverlessを使う
serverlessはlambdaなどの各サービスの上へ簡単にサーバレスなアプリケーションを作成/デプロイできるCLIツールです。ロゴがカッコいいですね。
serverlessのプラグインとしてアリババクラウドが出しているserverless-aliyun-function-computeがあるのでこれを使います。
まずyarn global add serverless
でserverlessをインストールしておきましょう。
aliyun-nodejsのインストール
$ serverless install --url https://github.com/aliyun/serverless-function-compute-examples/tree/master/aliyun-nodejs Serverless: Downloading and installing "aliyun-nodejs"... Serverless: Successfully installed "aliyun-nodejs" $ ls -lah aliyun-nodejs total 32 drwxr-xr-x 6 ryouta staff 192B Aug 7 2018 . drwxr-xr-x 3 ryouta staff 96B Aug 7 2018 .. -rw-r--r-- 1 ryouta staff 86B Aug 7 2018 .gitignore -rw-r--r-- 1 ryouta staff 191B Aug 7 2018 index.js -rw-r--r-- 1 ryouta staff 322B Aug 7 2018 package.json -rw-r--r-- 1 ryouta staff 712B Aug 7 2018 serverless.yml $ serverless plugin install --name serverless-aliyun-function-compute Serverless: Installing plugin "serverless-aliyun-function-compute@latest" (this might take a few seconds...) Serverless: Successfully installed "serverless-aliyun-function-compute@latest"
credentialの設定をする
アリババクラウド、各サービスがそれぞれで秘密情報の場所とフォーマットを設定しているから同じ内容のファイルがバラけて気持ち悪い。
デフォルトのファイルの場所は~/.aliyuncli/credentials
です。変更も可能です。
$ mkdir ~/.aliyuncli $ vim ~/.aliyuncli/credentials $ cat ~/.aliyuncli/credentials [default] aliyun_access_key_id = xxxxxxxxxxxxxxx aliyun_access_key_secret = xxxxxxxxxxxxxxxxxxx aliyun_account_id = xxxxxxxxxxxxxxx
deployを実行する。
$ serverless deploy Serverless: Packaging service... Serverless: Excluding development dependencies... Serverless: Compiling function "hello"... Serverless: Finished Packaging. Serverless: Log project sls-xxxxxxxx-logs already exists. Serverless: Log store sls-xxxxxxxxxx-logs/aliyun-nodejs-dev already exists. Serverless: Log store sls-xxxxxxxxxx-logs/aliyun-nodejs-dev already has an index. Serverless: RAM role sls-aliyun-nodejs-dev-exec-role exists. Serverless: RAM policy fc-aliyun-nodejs-dev-access exists. Serverless: RAM policy fc-aliyun-nodejs-dev-access has been attached to sls-aliyun-nodejs-dev-exec-role. Serverless: Service aliyun-nodejs-dev already exists. Serverless: Bucket sls-xxxxxxxxxxx already exists. Serverless: Uploading serverless/aliyun-nodejs/dev/xxxxxxxxxxx-2018-08-07T04:01:01.158Z/aliyun-nodejs.zip to OSS bucket sls-xxxxxxxxxxxxx... Serverless: Uploaded serverless/aliyun-nodejs/dev/xxxxxxxxxxxx-2018-08-07T04:01:01.158Z/aliyun-nodejs.zip to OSS bucket sls-xxxxxxxxxxxxx Serverless: Updating function aliyun-nodejs-dev-hello... Serverless: Updated function aliyun-nodejs-dev-hello Serverless: RAM role sls-aliyun-nodejs-dev-invoke-role exists. Serverless: Attaching RAM policy AliyunFCInvocationAccess to sls-aliyun-nodejs-dev-invoke-role... Serverless: Attached RAM policy AliyunFCInvocationAccess to sls-aliyun-nodejs-dev-invoke-role Serverless: Creating API group aliyun_nodejs_dev_api... Serverless: Created API group aliyun_nodejs_dev_api Serverless: Creating API sls_http_aliyun_nodejs_dev_hello... Serverless: Created API sls_http_aliyun_nodejs_dev_hello Serverless: Deploying API sls_http_aliyun_nodejs_dev_hello... Serverless: Deployed API sls_http_aliyun_nodejs_dev_hello Serverless: GET http://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-cn-shanghai.alicloudapi.com/foo -> aliyun-nodejs-dev.aliyun-nodejs-dev-hello $ $ serverless invoke -f hello Serverless: Invoking aliyun-nodejs-dev-hello of aliyun-nodejs-dev Serverless: {"statusCode":200,"body":"{\"message\":\"Hello!\"}"}
serverless invoke
に渡す必要がある-f
オプションは、jsファイル中のhandler関数の名前です。(ここではhello)
出たエラー
serverless deploy
を実行した時に以下の2つのエラーが発生しましたが、ログを見ると特にリソース自体は作成されていたので何もせずdeployを再実行しました。私は3回目で正常に成功しましたw
きっとプラグイン側のバグでしょう。
POST /services failed with 400. requestid: 5a98e7ee-8159-cc22-0d5a-18bec02a9cc8, message: project 'sls-5326127221743842-logs' does not exist. The role not exists:sls-aliyun-nodejs-dev-invoke-role
確認
コンソールで確認して見ると、Function ComputeとAPI Gatewayにリソースが作られていました。
fun
かserverless
どちらを使うべきか
上記のように謎に失敗する時があることや鍵の指定方法を考えると、fun
がいいと思います。公式なので変更にも強そうです。
fun
だとタイムトリガーなど他のトリガーにも対応できますし。
タイムトリガーでFunction ComputeからSlackに投稿する
やること
この記事ではFunction ComputeからSlackに投稿する最小のコードを紹介します。
関数、トリガーの作成
まずFunction Computeでタイムトリガーの関数を作成します。
以下のコードがFunction ComputeからSlackに現在時刻を投稿する最小の(多分)コードになります。webhook urlが分からない方ははググってください。私はいつもググってます。
const { IncomingWebhook } = require('@slack/client'); module.exports.handler = function(request, context, callback) { const url = 'https://hooks.slack.com/services/**********/*******************'; // YOUR_WEBHOOK_URL const webhook = new IncomingWebhook(url); const date = new Date(); webhook.send(date.toString(), (err, res) => { const message = err || res; callback(null, message); }); };
handlerの関数の引数がHTTPトリガーのものと違っていることに注意してください。私はここで結構ハマりました。
なお、ローカルでnpm install
してnode_modules
ごとアップロードしなければ@slack/client
は動きません。
タイムトリガーを1分ごとに設定して動くことが確認できました。
まとめ
タイムトリガーの「動く最小のコード」が見当たらなかったので書きました。
Function ComputeとAPI GatewayにTravis CIからデプロイする
GitHubにpushしたFunction Compute用のコードをTravis CIからfunを使ってデプロイします。
やること
Function Compute用のjsファイルをTravis CIからFunction ComputeとAPI Gatewayに自動でデプロイします。
準備
以下を参考にしてまずローカルからfunを使ってFunction Computeにデプロイできるようになっていてください。
Function ComputeとAPI Gatewayのデプロイツールであるfunを使う - asmsuechan’s blog
.travis.ymlの追加
fun deploy
コマンドによって、node_modules/を含めてzipに固めてFunction Computeにデプロイしてくれます。
アクセスキー等は環境変数に入れておけばコマンド実行時に拾ってくれます。travis encrypt
で.travis.ymlに追加しておきましょう。
$ travis encrypt -r asmsuechan/fc_deploy_travis ACCESS_KEY_ID=xxxxxxxxxxxxxx --add $ travis encrypt -r asmsuechan/fc_deploy_travis ACCESS_KEY_SECRET=xxxxxxxxxxxxxxxxxxxxx --add $ travis encrypt -r asmsuechan/fc_deploy_travis ACCOUNT_ID=xxxxxxxxxxxx --add # エンドポイントの数字部分
funはnode.jsのバージョン8以上じゃないと動きません。
language: node_js node_js: - 8 install: - yarn script: - yarn run lint before_deploy: - yarn add global @alicloud/fun deploy: provider: script skip_cleanup: true script: - fun deploy on: repo: asmsuechan/fc_deploy_travis env: global: - secure: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - secure: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - secure: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx - DEFAULT_REGION=cn-shanghai
REGION
じゃなくてDEFAULT_REGION
にしないとエラーが出ます。
Waiting for service fc to be deployed... TypeError: "config.region" must be passed in at new Client (/home/travis/build/asmsuechan/fc_deploy_travis/node_modules/@alicloud/fc2/lib/client.js:54:13) at getFcClient (/home/travis/build/asmsuechan/fc_deploy_travis/node_modules/@alicloud/fun/lib/deploy/deploy-support.js:39:10) at <anonymous> Script failed with status 255
OSSにTravisCIから自動アップロードする
Alibaba CloudのOSSにTravisCIから自動でアップロードします。
やること
vue-cliで作成されたVue.jsのプロジェクトを、GitHubにプッシュされたらtravisでビルド後ossutil cp dist/ oss://test-cli --recursive -i xxxxxxxxxxx -k xxxxxxxxxxxxxxx -e oss-cn-shanghai.aliyuncs.com
を実行してOSSにアップロードする。
準備
デプロイ用のRAMユーザー、OSSのバケット、travisのアカウント、travisコマンド(gem install travis
)、GitHubのリポジトリが必要です。適当に準備しておきましょう。
参考
一度ossutilをローカルで試してみたほうが良さそうです。
.travis.ymlの追加
S3みたいにtravisコマンドが自動でいい感じにしてくれるわけでもtravisがproviderとして準備してくれているわけでもないのでprovider: scriptとしてゴリゴリ書く必要があります。
追加した.travis.ymlは以下のようになりました。
language: node_js node_js: - 7 go: - 1.9.x install: - yarn script: - yarn run build before_deploy: - go get github.com/aliyun/ossutil deploy: provider: script skip_cleanup: true script: - ossutil cp dist/ oss://test-cli --recursive -f -i $ACCESS_KEY_ID -k $ACCESS_KEY_SECRET -e oss-cn-shanghai.aliyuncs.com env: global: - secure: qEjBMfJ5NLNN8dNDGetvi1C8/QCTOfmukY9RGCsOpMlaHUZVdFCAMBtmbuHMcRqJSyu/fBEOL2Nj1dy66EYOFWNdy6Nit7urvjn6ch0RLbdsVC/suuIZv4ROtnYRMIORvvyvtLw7C3pt19bYeKY/iGuP9UizCuu+Q2nz0yoYDWg7HT+PPuYtCwZBFUe8OhQppQNdJw4/WdLr8nAPAP0slR0gIt6Vr54KjRjLPldIuKgsQVahNNaZdgGJ6LBBJuk1C7xfywOF04VfpKzE+3DlFt8Z7e8eP69+fJ5KUl+8RRM5OOeqDdElNeUm0yfnyvUfeMlRj8U9jVja5bdIXqtIIXpngxAM9JrwPhrnh4m1+DYtsPAyk7SiOGeBkBxLJdxvcHyMepxWOgXkNl1kqY6COGWLrtrTN7ljPWxDcZcgIRPumFxddnI0qgYCB6kG4bJHpOsO0CbaqlGggFmt0S03HW2GBX+ccZK/4bCZ83teoeANuvkONVwqRfiUgdmxJtSnr1lSsklpcu90fs2lDmXmbYfgyfbtPME02/pTRSVYE1LrWSzZKDBlAmoDUfsv9Xc6J0/Q5rzSMFV73835tbAR95zvUwiCYcGWvx/c+yvzcRO7eAVQoJr4Itpm06uQGXi96NeNR9ROChM3/GNs71llV8qtuO5VW83iRfLn2JrZZbU= - secure: rWQ+Jen7PQBT69x0Vqu4sZqZY/PbxXFe7+gyQcSDpEFCzZPdPzPDra599G8ft4cxs6FnqXINH88gF++cW1K51o4CU6zRPEML556rPBjATtn7Dy2utoWurWU4JKZZTwTgKkCOgSG/geKRZnq7qUw5dGQIn8ULIrWfqRG5CGljzLk1WqdYgc6jI1vuZLaXcsnyIdLqv5IVKpNM6KDlLmYhZaX8U9A/YFwtCTbEHo7fBlhV2S0bAi6qnMhsWozxY6ATX5pkZtX8RQaZATku25dyNV4cvX6Y9+GLj/9zB6BWtbcbfWU/6sWVbzwpXpo/kMtWpqYsMiT9mIEzAyNPyIhdi1mGo28w/jKh7rkNGqHjdYfgouINK7VCmTbV+hJVVlT7RmWf1YwXIofl3X89QE3/H83LxrToL/ze6qEBBZ+G4rTTcEzxJqqC4rggLOOmB4OYjhaMe1i0RgLtD3jlIMAsvxLbzV9kBdeMLHPpeq8hK2ClVomt8uXT7qadR+WwqPxexl0g/+mEpdCyXPv1nvA9iHh1l5IPDQvpnE7lTlEiplMuy9E3p/Ywq3Di3Xr/yhgUJZgGalwMD/0750XQapAldg0bV1UVsZLJsl3snVZhTedYOKI4+9w/irTtk+ET04VhuCVASWuAVBztfaWF0olh0YxCLwDZsxJhbDtB7Ni/8Q0=
ちなみにossutilコマンドを実行するとき-f
オプションがないとcp: overwrite "oss://test-cli/static/css/"(y or N)?
で動きを止めてしまいます。
鍵情報の暗号化
travisciにはそのままGitHubで公開したくない鍵情報などを暗号化してくれる機能があります。
travis encrypt
コマンドが環境変数を暗号化して.travis.ymlに追加してくれます。
$ travis encrypt ACCESS_KEY_ID=xxxxxxxxxxxxxxxx --add $ travis encrypt ACCESS_KEY_SECRET=xxxxxxxxxxxxxxxxxxxxxxx --add
まとめ
今回作成したリポジトリはこちらになります。
S3にTravisCIから自動でアップロードする
travis ciでVue.jsのプロジェクトをビルドしてdist/
をS3にアップロードするようにします。
.travis.ymlの設定
.travis.yml
の設定は、travis setup s3
コマンドが色々と便利にやってくれます。入れていない方はインストールしましょう。
$ gem install travis $ travis version 1.8.8 $ travis setup s3 [master] Detected repository as asmsuechan/asmsuechan.com, is this correct? |yes| yes Access key ID: xxxxxxxxxxxxxxxxxxxx Secret access key: **************************************** Bucket: s3://asmsuechan.com Local project directory to upload (Optional): S3 upload directory (Optional): dist S3 ACL Settings (private, public_read, public_read_write, authenticated_read, bucket_owner_read, bucket_owner_full_control): public_read Encrypt secret access key? |yes| yes Push only from asmsuechan/asmsuechan.com? |yes| yes
実際はこのままじゃ動かないので色々追記して以下のようになりました。
# .travis.yml language: node_js node_js: - 7 install: - yarn script: - yarn run lint - yarn run build deploy: provider: s3 skip_cleanup: true access_key_id: xxxxxxxxxxxxxxxxxxxx secret_access_key: secure: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx bucket: asmsuechan region: ap-northeast-1 local_dir: dist acl: public_read on: repo: asmsuechan/asmsuechan.com
bucket
の値にはs3://
を含めていはいけないようです。
やった事
以下の3つのことをしました。
- nodejsのバージョンを7に指定。
- regionをap-northeast-1に指定。
- local_dir: distを追加
- skip_cleanup: trueを追加
それぞれちょっと詳しく書いていきます。
nodejsのバージョンを7にする
何も指定しないとnodejsのバージョンは0.10.48らしいです。これだとyarnが動かない。
Node.js version v0.10.48 does not meet requirement for yarn. Please use Node.js 4 or later.
regionをap-northeast-1にする
regionを指定しないと以下のエラーが出ます。デフォルトのregionはus-east-1らしいです。
The bucket you are attempting to access must be addressed using the specified endpoint. Please send all future requests to this endpoint. (AWS::S3::Errors::PermanentRedirect)
skip_cleanup: trueを追加
local_dir: dist
を追加しても、そのままではデプロイ時にはscriptで生成されたdistディレクトリは消えてしまうようです。
出たエラーをググったらこのIssueコメント に行き着いたのでskip_cleanup: trueを追加しました。
/home/travis/.rvm/gems/ruby-2.2.7/gems/dpl-s3-1.9.8/lib/dpl/provider/s3.rb:56:in `chdir': No such file or directory @ dir_chdir - dist (Errno::ENOENT)
できたもの
私のポートフォリオサイトもどきができました。
http://asmsuechan.com/
-> https化しました。 https://asmsuechan.com
ちょっと注意
- デプロイ専用のIAMユーザーを作りましょう
- 403が出る方はポリシーの適用を忘れていませんか?ここなどを参考にしてみましょう