無印吉澤

ソフトウェア開発、運用管理(俗にいう DevOps)、クラウドコンピューティングなどについて、吉澤が調べたり試したことを書いていくブログです。

kintone devcamp 2018 にて AWS Lambda を使ったサービス間連携についてのハンズオンセッションを行いました

f:id:muziyoshiz:20180630010936p:plain

8/2(木) に開催された kintone devCamp 2018 にて、kintone と Backlog を API 連携させる方法についてのハンズオンセッションを実施しました。サイボウズの方から企画の提案があって、(資料を作りながら自分でも)手を動かしてみる良い機会と思い、引き受けさせていただきました。

kintone & Backlogハンズオン 〜利用シーンに応じたタスク管理ツールの使い分けをAPIで実現〜

目次はこんな感じ。90分のハンズオンセッションでした。

  • Backlogとは?
  • Backlogとkintoneの連携
  • Backlog APIによるサービス間連携
  • ハンズオン1. ノンプログラミングでの連携
  • AWS LambdaとFunction as a Service
  • ハンズオン2. AWSのサービスを用いた高度な連携

このハンズオンセッションは2部構成で、前半は kintone プラグインを使ったノンプログラミングでの連携。後半は AWS Lambda と Amazon API Gateway を使った連携でした。後半はスライドの p.94 からです。AWS Lambda だけ興味のある方は、そこから見てもらえれば。

スライドにはかなり細かく手順を書きましたし、ソースコードも GitHub で公開しているので、AWS Lambda を一度使ってみたい!という方は是非お試しください。

github.com

ちなみに、ハンズオン当日は、前半のハンズオンは7〜8割の参加者が設定完了して動かすことができたのですが、前半で時間を使いすぎて、後半はかなり駆け足で解説だけして終わってしまいました。それでも、後ほど頂いたアンケート結果で、8割近くが満足度「普通」以上の回答で一安心しました。

資料を作り始める前は、90分あれば余裕と思ってたんですが、実際に手を動かしてもらうハンズオンは時間調整が難しいですね……。次の機会があったらもう少し工夫します。

あわせて読みたい

最近書いた以下の記事は、このハンズオンの準備中に調べたことのまとめです。ハンズオンを触ってみる際は、こちらもあわせてご覧ください。

muziyoshiz.hatenablog.com

muziyoshiz.hatenablog.com

Amazon API Gateway で API (Webhook) の呼び出し元 IP アドレスを制限する

f:id:muziyoshiz:20180630010936p:plain

前回の記事では、Webhook の受け口を Amazon API Gateway で作る方法をまとめました。

muziyoshiz.hatenablog.com

この方法で作った API は、URLを知っている人なら誰でも叩けてしまいます。Amazon API Gateway は IAM や API キーでの認証をサポートしていますが、一般的な Webhook は指定された URL に POST を送るだけなので使いづらいです。

しかし、Amazon API Gateway のリソースポリシーを使うと、API の呼び出しを許可する IP アドレス(つまり Webhook の送信元 IP アドレス)を限定することができます。何も無いよりはずっと良いので、今回はこの設定方法をまとめておきおます。

Amazon API Gateway のリソースポリシー対応

昔の Amazon API Gateway には送信元 IP アドレスの制限機能はなく、Amazon API Gateway Lambda オーソライザー(カスタムオーソライザー)で自前で実装する必要があったそうです(※僕は使ったことないのですが、ググると出てきます)。

しかし、2018年4月2日に Amazon API Gateway が API リソースポリシーをサポート開始し、これで IP アドレス制限ができるようになりました。

https://aws.amazon.com/jp/about-aws/whats-new/2018/04/amazon-api-gateway-supports-resource-policies/aws.amazon.com

以下の AWS のドキュメントに、リソースポリシーの例が掲載されています。

docs.aws.amazon.com

Webhook の場合は「送信元 IP アドレスを特定のサービスに限定する」ことができれば十分でしょう。例えば、Backlog というサービスでは以下のように Webhook を実行するサーバーの IP アドレス範囲を公開しています。

backlog.com

この Backlog の Webhook のみを受け取りたい場合、以下のようにリソースポリシーを書けば OK です(リージョンなどは伏せてます)。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Deny",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": [
                "arn:aws:execute-api:region:account-id:api-id/*"
            ],
        },
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": [
                "arn:aws:execute-api:region:account-id:api-id/*"
            ],
            "Condition": {
                "NotIpAddress": {
                    "aws:SourceIp": [
                        "52.199.190.133/32",
                        "54.238.59.48/32",
                        "54.65.8.249/32",
                        "52.192.161.184/32",
                        "52.68.222.36/32"
                    ]
                }
            }
        }
    ]
}

Serverless Framework のリソースポリシー対応

Serverless Framework も、2018年4月7日リリースのバージョン1.28でリソースポリシーに対応しました。今では、serverless.yml 内でリソースポリシーを記述できます。

github.com

簡単な例が公式ドキュメントに載っています。

serverless.com

serverless.yml に以下のように書くと、先ほどの Backlog の例に挙げたリソースポリシーが自動設定されます。"103.79.14.0/24" のような範囲指定も可能です。

provider:
  name: aws
  runtime: nodejs8.10

  resourcePolicy:
    - Effect: Allow
      Principal: "*"
      Action: execute-api:Invoke
      Resource:
        - execute-api:/*/*/*
      Condition:
        IpAddress:
          aws:SourceIp:
            - "52.199.190.133"
            - "54.238.59.48"
            - "54.65.8.249"
            - "52.192.161.184"
            - "52.68.222.36"

設定結果は、以下のように AWS コンソールで確認できます。

f:id:muziyoshiz:20180729112632p:plain:w800

範囲外の IP アドレスからのアクセスはどのように拒否されるか

前回の記事で作った hello 関数で試してみます。

hello 関数は、以下のように JSON でメッセージを受け取って、文字列を付け足して返す関数でした。

$ curl -H 'Content-Type:application/json' -d '{"message":"world"}' http://localhost:3000/hello
{"message":"Hello world 2018"}

許可されていない IP アドレスから、この curl コマンドを実行すると、403 とエラーメッセージ(JSON)が返されます。

$ curl -v -H 'Content-Type:application/json' -d '{"message":"world"}' https://**********.execute-api.ap-northeast-1.amazonaws.com/dev/hello
*   Trying ***.***.***.***...
* TCP_NODELAY set
* Connected to **********.execute-api.ap-northeast-1.amazonaws.com (***.***.***.***) port 443 (#0)
* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
* Server certificate: *.execute-api.ap-northeast-1.amazonaws.com
* Server certificate: Amazon
* Server certificate: Amazon Root CA 1
* Server certificate: Starfield Services Root Certificate Authority - G2
> POST /dev/hello HTTP/1.1
> Host: **********.execute-api.ap-northeast-1.amazonaws.com
> User-Agent: curl/7.54.0
> Accept: */*
> Content-Type:application/json
> Content-Length: 19
>
* upload completely sent off: 19 out of 19 bytes
< HTTP/1.1 403 Forbidden
< Content-Type: application/json
< Content-Length: 165
< Connection: keep-alive
< Date: Sun, 29 Jul 2018 02:08:33 GMT
< x-amzn-RequestId: 4ba819ee-92d4-11e8-a7e3-7feb0ae86886
< x-amzn-ErrorType: AccessDeniedException
< x-amz-apigw-id: KxIxOGMatjMFhWg=
< X-Cache: Error from cloudfront
< Via: 1.1 7b63dc372a4330b28d4dd1f11ec139a7.cloudfront.net (CloudFront)
< X-Amz-Cf-Id: PPGEdw9vYkOwQejOGAztIO63Atb73po3rzaszbcRpJpXZEQbjVzPIg==
<
* Connection #0 to host **********.execute-api.ap-northeast-1.amazonaws.com left intact
{"Message":"User: anonymous is not authorized to perform: execute-api:Invoke on resource: arn:aws:execute-api:ap-northeast-1:********3987:**********/dev/POST/hello"}

一度設定したリソースポリシーの削除方法

ここはまだ自分でもよくわかっていない部分です。

一度設定したリソースポリシーを消すために、以下のように resourcePolicy 以下をコメントアウトして sls deploy を実行しても、AWS コンソール上のリソースポリシーは削除されませんでした。

provider:
  name: aws
  runtime: nodejs8.10

#  resourcePolicy:
#    - Effect: Allow
#      Principal: "*"
#      Action: execute-api:Invoke
#      Resource:
#        - execute-api:/*/*/*
#      Condition:
#        IpAddress:
#          aws:SourceIp:
#            - "52.199.190.133"
#            - "54.238.59.48"
#            - "54.65.8.249"
#            - "52.192.161.184"
#            - "52.68.222.36"

また、AWS コンソール上で直接、以下のようにリソースポリシーを削除しても、IP アドレス制限は有効なままでした。Amazon API Gateway の設定を変更するだけでは駄目? CloudFormation や CloudFront の設定も絡んでるんでしょうか?

f:id:muziyoshiz:20180729112647p:plain:w800

結局、以下のように、すべての IP アドレスを許可するリソースポリシーを設定したあとで、sls deploy を実行すると、IP アドレス制限を解除することができました。

  resourcePolicy:
    - Effect: Allow
      Principal: "*"
      Action: execute-api:Invoke
      Resource:
        - execute-api:/*/*/*

Webhook の受信を契機として複数の API を叩く Lambda 関数を Node.js で書きたいときのためのメモ

f:id:muziyoshiz:20180630010936p:plain

ときどき AWS Lambda の Lambda 関数を Python で書いてます。AWS Lambda 便利ですよね。

しかし最近、他の人に Lambda 関数をいじってほしいけど Python での開発環境を作ってもらうのが面倒なので、一度書いた Lambda 関数を Node.js で書き換える、という機会がありました。そのときに Node.js の流儀がわからず困ってしまったので、あとで自分が読み返すためのメモを書いておきます。

このメモの対象読者は次のような人です。

  • Webhook の受信を契機として、複数の API を叩くような処理が書きたい
  • その処理を AWS Lambda に乗せて、楽に運用したい
  • AWS Lambda と API Gateway へのデプロイを楽にしたいので Serverless Framework を使いたい
  • 開発環境構築を楽にするために Node.js で書きたい(Serverless Framework を使うなら Node.js もあるはず)

※注意事項:私は Node.js 初心者で、Node.js の書き方はよくわかってません。なので、もっと良い書き方がわかったら、このメモをちょっとずつ直していきます。

目次

Serverless Framework のインストール

  • Node.js のインストール
    • brew install npm
  • Serverless Framework のインストール
    • npm install -g serverless

参考:Serverless Framework - AWS Lambda Guide - Installing The Serverless Framework

AWS クレデンシャルの設定

AWS クレデンシャルを設定していない場合、sls コマンドで作れます。~/.aws/credentials に設定済みであれば、この手順は不要です。デフォルトプロファイル以外でも大丈夫です。

  • Administrator Access 権限のある IAM ユーザーの作成
  • AWS クレデンシャルの設定
    • sls config credentials --provider aws --key アクセスキーID --secret シークレットアクセスキー

参考:Serverless Framework - AWS Lambda Guide - Credentials

新しい Serverless service (Node.js 8.10) の作成

Lambda 関数のランタイムには、async/await が使える Node.js 8.10 を選択します。現時点で、AWS Lambda で使える最新の Node.js はコレです。

ただ、6/30現在では sls create -t aws-nodejs でひな型(boilerplate)を作ると、Node.js 6.10 が選択されてしまいます。

$ sls create -t aws-nodejs -p example
Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "/Users/myoshiz/example"
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.27.3
 -------'

Serverless: Successfully generated boilerplate for template: "aws-nodejs"

上記のコマンドを実行すると、以下のファイルが作られます。

  • .gitignore
  • handler.js
  • serverless.yml

この serverless.yml の

provider:
  name: aws
  runtime: nodejs6.10

となっている部分を

provider:
  name: aws
  runtime: nodejs8.10

に書き換えると、Node.js 8.10 が使えます。

参考:Node.js 8.10 runtime now available in AWS Lambda | AWS Compute Blog

デフォルトステージの設定

Serverless Framework には「ステージ」という概念があって、1個のコードを、ステージごとに Lambda 関数名や設定を変えてデプロイできます。ステージの主な用途は、開発環境と本番環境の切り替えです。

このステージを引数から指定できるようにするための設定を、serverless.yml に追加します。

provider:
  name: aws
  runtime: nodejs8.10
  stage: ${opt:stage, self:custom.defaultStage}
custom:
  defaultStage: dev

Serverless Framework のデフォルトのステージは dev なので、そのままで使うならこの設定は不要です。しかし dev が不要な場合は、デフォルトを production にしておくといいでしょう。

参考:Serverless Framework - AWS Lambda Guide - Deploying

デフォルトで使うAWSプロファイルおよびリージョンの設定

AWS プロファイルとリージョンも、引数から指定可能にしておきます。以下の設定を serverless.yml に追加します。

  profile: ${opt:profile, self:custom.defaultProfile}
  region: ${opt:region, self:custom.defaultRegion}
  defaultProfile: default
  defaultRegion: ap-northeast-1

ステージに応じて AWS プロファイルを切り替えたい場合は、以下の方法で実現できるようです。

参考:Serverless Frameworkで環境変数を外部ファイルから読み込み、環境毎に自動で切り替えてみる | Developers.IO

環境変数の定義

AWS Lambda で使いたい機密情報(パスワードや API キーなど)は、「環境変数」として AWS Lambda に登録できます。しかし、この環境変数は、git リポジトリには登録したくありません。

Serverless Framework では、

  • 環境変数を git リポジトリ管理外の YAML ファイルに書く
  • デプロイ時のみこの YAML ファイルを用意する

ということが可能です。

まず、以下の YAML ファイルを用意します。

conf/dev.yml - dev ステージで使う環境変数
conf/production.yml - production ステージで使う環境変数

そして、.gitignore に以下のように書いて、git リポジトリへの登録を禁止します。

# Secrets
conf/*.yml

最後に、serverless.yml に以下の設定(otherfile: 以下)を追加します。

custom:
  defaultStage: dev
  defaultProfile: default
  defaultRegion: ap-northeast-1
  otherfile:
    environment:
      dev: ${file(./conf/dev.yml)}
      production: ${file(./conf/production.yml)}

YAML ファイル(dev.yml, production.yml)には、以下のように記載します。環境変数に数値を代入したい場合は、ダブルクォートで囲んで、文字列にする必要があります。これは Serverless Framework の問題ではなく、AWS Lambda の制約です。

---
MESSAGE: Hello
YEAR: "2018"

参考:Serverless Frameworkで環境変数を外部ファイルから読み込み、環境毎に自動で切り替えてみる | Developers.IO

request-promise モジュールのインストール

API の呼び出しには request-promise モジュールを使います。このモジュールを使うためには request モジュールも必要です。

$ npm init
$ npm install --save request
$ npm install --save request-promise

package.json と package-lock.json が作成されます。このファイルは、ソースコードと一緒に git リポジトリにコミットします。

参考:request/request: Simplified HTTP request client., request/request-promise: The simplified HTTP request client 'request' with Promise support. Powered by Bluebird.

開発用プラグインの追加

Webhook を受け付けるためには、Amazon API Gateway と AWS Lambda を組み合わせて使うのが一般的です。

しかし、動作確認のためにいちいち API Gateway にデプロイするのは面倒なので、serverless-offline プラグインを使って、ローカルで動作確認できるようにします。

以下のコマンドでインストールできます。

$ npm install --save-dev serverless-offline

serverless.yml に以下の設定を追加。

plugins:
  - serverless-offline

sls offline start を実行すると、ローカルで HTTP サーバが動作します。

参考:dherault/serverless-offline: Emulate AWS λ and API Gateway locally when developing your Serverless project

handler の定義(Amazon API Gateway の設定)

sls create コマンドで作られた Lambda 関数 "hello" に handler の設定(events: 以下)を追加してみます。

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: hello
          method: post
    environment:
      MESSAGE: ${self:custom.otherfile.environment.${self:provider.stage}.MESSAGE}
      YEAR: ${self:custom.otherfile.environment.${self:provider.stage}.YEAR}

先ほど説明した環境変数を Lamnbda 関数内で使うには、environment: 以下で明示的に指定する必要があります。

そして、handler.js 内の hello 関数を以下のように修正してみましょう。リモートにデプロイすると、この関数は API Gateway から起動されます。

module.exports.hello = (event, context, callback) => {
    console.log(`MESSAGE: ${process.env.MESSAGE}`)
    console.log(`YEAR: ${process.env.YEAR}`)

    console.log(event);

    const body = event['body'];

    try {
        const json = JSON.parse(body);

        if (json && json.message) {
            callback(null, {
                statusCode: 200,
                body: JSON.stringify({message: `${process.env.MESSAGE} ${json.message} ${process.env.YEAR}`})
            })
        } else {
            callback(null, {statusCode: 400, body: JSON.stringify({error: 'No message'})});
        }
    } catch (e) {
        console.log(e);
        callback(null, {statusCode: 400, body: JSON.stringify({error: 'Invalid JSON'})});
    }
};

ローカルでの動作確認

この hello 関数をローカルで動作確認します。まず、以下のコマンドで HTTP サーバを起動します。

$ sls offline start
Serverless: Starting Offline: dev/ap-northeast-1.

Serverless: Routes for hello:
Serverless: POST /hello

Serverless: Offline listening on http://localhost:3000

他のターミナルから curl で JSON を POST し、応答が返されたら成功です。

$ curl -H 'Content-Type:application/json' -d '{"message":"world"}' http://localhost:3000/hello
{"message":"Hello world 2018"}

sls offline start を実行したターミナルには、console.log() に渡した event オブジェクトが以下のように出力されます。

Serverless: POST /hello (λ: hello)
MESSAGE: Hello
YEAR: 2018
{ headers:
   { Host: 'localhost:3000',
     'User-Agent': 'curl/7.54.0',
     Accept: '*/*',
     'Content-Type': 'application/json',
     'Content-Length': '19' },
  path: '/hello',
  pathParameters: null,
  requestContext:
   { accountId: 'offlineContext_accountId',
     resourceId: 'offlineContext_resourceId',
     apiId: 'offlineContext_apiId',
     stage: 'dev',
     requestId: 'offlineContext_requestId_27367216451093634',
     identity:
      { cognitoIdentityPoolId: 'offlineContext_cognitoIdentityPoolId',
        accountId: 'offlineContext_accountId',
        cognitoIdentityId: 'offlineContext_cognitoIdentityId',
        caller: 'offlineContext_caller',
        apiKey: 'offlineContext_apiKey',
        sourceIp: '127.0.0.1',
        cognitoAuthenticationType: 'offlineContext_cognitoAuthenticationType',
        cognitoAuthenticationProvider: 'offlineContext_cognitoAuthenticationProvider',
        userArn: 'offlineContext_userArn',
        userAgent: 'curl/7.54.0',
        user: 'offlineContext_user' },
     authorizer:
      { principalId: 'offlineContext_authorizer_principalId',
        claims: undefined },
     protocol: 'HTTP/1.1',
     resourcePath: '/hello',
     httpMethod: 'POST' },
  resource: '/hello',
  httpMethod: 'POST',
  queryStringParameters: null,
  stageVariables: null,
  body: '{"message":"world"}',
  isOffline: true }
Serverless: [200] {"statusCode":200,"body":"{\"message\":\"Hello world 2018\"}"}

デプロイ

ローカルで動作確認できたら、リモートの AWS Lambda と Amazon API Gateway にデプロイします。デプロイは sls deploy コマンドで行います。

$ sls deploy
Serverless: Packaging service...
Serverless: Excluding development dependencies...
Serverless: Creating Stack...
Serverless: Checking Stack create progress...
.....
Serverless: Stack create finished...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (2.28 MB)...
Serverless: Validating template...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..............................
Serverless: Stack update finished...
Service Information
service: example
stage: dev
region: ap-northeast-1
stack: example-dev
api keys:
  None
endpoints:
  POST - https://aaaaaaaa.execute-api.ap-northeast-1.amazonaws.com/dev/hello
functions:
  hello: example-dev-hello

サービス名-ステージ名-関数名 という名前の Lambda 関数が作られました(この例では example-dev-hello)。本番環境のステージを指定するときは sls コマンドに --stage production を付けてください。

デプロイに成功すると、ローカルの場合と同じように動作確認できます。

$ curl -H 'Content-Type:application/json' -d '{"message":"world"}' https://aaaaaaaa.execute-api.ap-northeast-1.amazonaws.com/dev/hello

1回デプロイしたあとなら、sls deploy -f hello のようにして、特定の Lambda 関数だけ更新することもできます。

$ sls deploy -f hello

ちなみに、Lambda 関数がもう要らなくなったという場合は sls remove で削除できます。

リモートでの動作確認

Lambda 関数がうまく動かないときにはログを確認したくなると思いますが、その場合は console.log() でログ出力すると、CloudWatch Logs から確認できます。

ログは /aws/lambda/example-dev-hello のような名前のロググループに登録されます。

Webhook を受信したら複数の API を叩く

ここまでで、Webhook を受信したらなにかする、という Lambda 関数は書けました。次は、複数の API を叩くコードの例を紹介します。

API を1個叩くだけなら、request モジュールのみで実現できます。

しかし、ある API を叩き、その返り値を元に別の API を叩き、さらにその返り値を元に……と処理が続く場合は、コールバック地獄を避けるために request-promise を使ったほうが楽です。また、Promise を使うだけでなく、async/await も使ったほうがコードが読みやすくなります。

ここでは、試しに、先ほどの hello を3回呼び出すエンドポイント hello_hello_hello を作ってみます。

まず、serverless.yml に以下を追加します。これはローカルでの動作確認(後述)を簡単にするために method: get にしていますが、実際に Webhook から起動するときは method: post にしてください。

  hello_hello_hello:
    handler: handler.helloHelloHello
    events:
      - http:
          path: hello_hello_hello
          method: get

そして、handler.js に以下のコードを追加します。await で1個目の API からの応答が来るのを待ってから、次の API 呼び出しをしているのがわかるでしょうか。

module.exports.helloHelloHello = (event, context, callback) => {
    const callHello = (message) => {
        const request = require('request-promise');

        const params = {
            message: message
        };

        const options = {
            method: 'POST',
            uri: `http://localhost:3000/hello`,
            body: params,
            json: true
        };

        return request(options)
            .then(function (parsedBody) {
                return parsedBody;
            })
            .catch(function (err) {
                console.log(err);
                return null;
            });
    };

    (async () => {
        const response1 = await callHello('world');
        console.log(response1);

        const response2 = await callHello(response1.message);
        console.log(response2);

        const response3 = await callHello(response2.message);
        console.log(response3);

        callback(null, {statusCode: 200, body: response3.message});
    })();
};

この関数は、以下のように動作します。

  • http://localhost:3000/hello に "world" を送り、"Hello world 2018" を受け取る
  • http://localhost:3000/hello に "Hello world 2018" を送り、"Hello Hello world 2018 2018" を受け取る
  • http://localhost:3000/hello に "Hello Hello world 2018 2018" を送り、"Hello Hello Hello world 2018 2018 2018" を受け取る
  • 最終的な結果 "Hello Hello world 2018 2018" を呼び出し元に返す。

sls offline start で HTTP サーバを起動して、curl で以下のコマンドを実行してみてください。

$ curl http://localhost:3000/hello_hello_hello
Hello Hello Hello world 2018 2018 2018

これと同じ要領で、hello 以外の API も呼び出すことができます。

基本は以上です。あとは頑張ってください。

付録1. Windows の場合

Windows の場合は、Serverless Framework のインストール方法が違います。また、普通は curl コマンドが使えないので、別の方法を使う必要があります。

Serverless Framework のインストール

まず node.js から Windows 用のインストーラをダウンロードして、Node.js をインストールします。

次に、Windows のメニューから "Node.js command prompt" を選択して、プロンプトを開きます。このプロンプトで npm install -g serverless を実行すれば、Serverless Framework をインストールできます。

これ以降は、"Node.js command prompt" から、上記の sls コマンドを実行できます。

curl コマンドの代わり

curlコマンドの代わりに、Node.js の REPL からテスト用のリクエストを送信できる。

JSON を POST して、HTTP リクエストのボディを標準出力に表示。

C:\> node
> const request = require('request');
> const options = {url: 'http://localhost:3000/hello', headers: {'Content-type': 'application/json'}, json: {message: 'world'} };
> request.post(options, function(error, response, body){ console.log(body); });

GET して、HTTP リクエストのボディを標準出力に表示。

C:\> node
> const request = require('request');
> request.get('http://localhost:3000/hello_hello_hello', function(error, response, body){ console.log(body); });

付録2. IntelliJ IDEA Ultimate で Node.js 8.10 を使う

僕は IndelliJ IDEA Ultimate でコードを書いているのですが、Node.js 8.10 でエラーが出ない状態にするには以下の設定が必要でした。

  • Preferences > Plugins > Install JetBrains Plugin... を選択し、その画面から NodeJS プラグインをインストールする
  • Preferences > Languages & Frameworks > Node.js and NPM を選択し、その画面から Node.js Core library を有効にする(Enable ボタンを押す)
  • Preferences > Languages & Frameworks > JavaScript を選択し、その画面上で JavaScript language version を "ECMAScript 6" に変更する

参考:javascript - Arrow function "expression expected" syntax error - Stack Overflow

その他の参考ページ

このブログ(無印吉澤)を HTTPS 対応させました

このブログ(無印吉澤)を HTTPS 対応させました。今後は HTTP の方にアクセスしても、HTTPS の方にリダイレクトされます。

今回はそのために必要だった作業と、結局どうしようもなかった部分のまとめです。

はてなブログの HTTPS 対応状況

はてなブログは2018年2月22日から HTTPS での配信に対応し、その後も改良が続いているようです。

staff.hatenablog.com

staff.hatenablog.com

staff.hatenablog.com

手順については、以下のヘルプを読めば大体のことはわかりました。

help.hatenablog.com

help.hatenablog.com

必要だった作業

1. 「HTTPS配信の状況」の「変更する」ボタンを押す

まず、はてなブログの「設定」→「詳細設定」→「HTTPS配信」から、URL 設定のページ を表示。

「HTTPS配信の状況」の「変更する」ボタンを押すと一瞬で切り替わります。

はてな内のサービスを見てみましたが、はてなブックマークの URL は https にすぐに切り替わったように見えました。

http://b.hatena.ne.jp/entrylist?url=muziyoshiz.hatenablog.com

2. サイト内に埋め込まれている自サイトの URL を https:// に書き換える

http:// にアクセスされてもリダイレクトされるので、基本的には書き換え不要なんですが、Feedly の Follow ボタンだけは https://muziyoshiz.hatenablog.com/feed で作り直しました。

Follower 0件になって寂しいんですが、まあ、今後 HTTPS が常識になるなら早いうちに合わせたほうがマシかなと。

3. Mixed Content の確認

ここからは、ひたすら Mixed Content の確認です。Chrome の開発者ツールを開いた状態で、ブログ一覧から「次のページ」を押し続けるという地味な作業をやりました。

うちはまだ記事数少ないので良かったですが、毎日ブログ投稿してるような人は死にそうですね……。

embed:cite 記法で書かれたリンクの修正

普通の [title](url) 記法で書かれたリンクは http でも問題ないんですが、はてなブログ独自の記法は、サムネイルが埋め込まれる関係か Mixed Content のエラーが出たので修正しました。

  • Before: [http://muziyoshiz.hatenablog.com/entry/2016/04/25/015644:embed:cite]
  • After: [http://muziyoshiz.hatenablog.com/entry/2016/04/25/015644:embed:cite]

はてな以外のサービスの URL も、http で書いていたものは https に修正しました。

  • Before: [http://twitter.com/okapies/status/856528271306928128:embed:cite]
  • After: [https://twitter.com/okapies/status/856528271306928128:embed:cite]

うちのサイトは、embed:cite 記法の URL をたまたま全部 https に直せましたが、リンク先が HTTPS 対応してない場合は [title](url) 記法に直したほうがいいかもしれません。

はてなフォトライフ(Fotolife)の画像の再読み込み

はてなブログに画像を貼るときは、Fotolife にアップロードして、以下の記法で埋め込むのが楽です。このブログでは主にアイキャッチ画像を貼るのに使っています。

<div align="center">[f:id:muziyoshiz:20160425013924j:plain]</div>

最近のブログに埋め込んだ画像は、何もしなくても最初から https になっていたんですが、2016年6月より前に埋め込んだアイキャッチ画像の URL は http になってしまっていました(Fotolife 自体がこのくらいの時期に HTTPS 化した?)。

対策としては、「ブログを開く」→「何かしら編集する」→「ブログを保存する」と、URL が再生成されて https に変わりました。これを、Fotolife の画像を埋め込んだ記事すべてでやらないといけないので、これはめんどくさい……。

どうしようもなかった部分

Amazon の商品画像埋め込み

はてなブログは、以下のような記法で Amazon の商品画像を埋め込めます。

[asin:B01FRIOYEC:detail]

この商品画像が、どうも HTTPS 配信のものと HTTP 配信のものが混在しているらしく、特に古い本は HTTP 配信のようでした。

例えば、以下の記事の最後にある「参考文献」で、「プログラミング Elixir」の画像は HTTPS、「Programming Phoenix」の画像は HTTP です。

muziyoshiz.hatenablog.com

ブログ記事を編集→保存しても直らなかったので、はてなではなくて Amazon 側の問題なんだろうと思って諦めました。

SlideShare のスライド埋め込みでエラー

HTTPS 化とは関係ないんですが、過去の記事を見返してみたら、SlideShare のスライドを埋め込んでいる箇所で大量のエラーが出てました。

f50c7d109361420bbc0ee31b2c44d54e:5 Uncaught SyntaxError: Unexpected token o in JSON at position 1
    at JSON.parse (<anonymous>)
    at n.receive (player-67e207486452a6edc528fc9bbb3a0e02.js:26)
    at player-67e207486452a6edc528fc9bbb3a0e02.js:26
    at nrWrapper (f50c7d109361420bbc0ee31b2c44d54e:5)

とか、

combined_base.js?f14b451c58:55 [Deprecation] chrome.loadTimes() is deprecated, instead use standardized API: Paint Timing. https://www.chromestatus.com/features/5637885046816768.

とか。

SlideShare が生成する iframe タグが古いからかな?と思って、SlideShare のサイトで iframe タグを生成し直してみました。でも、リンクの URL が http から https に変わっただけで、上記のエラーは消えませんでした。

あと、

Access to Font at 'https://public.slidesharecdn.com/fonts/fontawesome-webfont.woff2?v=4.3.0?cb=1526503678' from origin 'https://www.slideshare.net' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://www.slideshare.net' is therefore not allowed access.

という CORS のエラーが出ていたので、こちらも少し調べました。2017年1月に SlideShare に報告してくれた人がいる問題のようですが、いまも解決してませんでした。

github.com

このあたりのエラーが放置されてると、今後は SlideShare はなるべく使わないようにしようかなあと思っちゃいますね。

まとめ

結果的に大した手間もなく移行できて、移行から1週間くらい様子を見た感じでは、問題なく動いていそうです。

ただ、はてなフォトライフの URL を作り直すのだけは本当に面倒だったので、ブログ記事が多い場合は、なにか自動化手段を先に用意したほうがいいと思います。がんばってください。