ServerlessFrameworkでS3の静的サイトのホスティングをする

概要

S3に静的サイトのホスティング機能がある。静的ファイル(HTML, js, css等)をS3に配置すると、それらのファイルをウェブ公開してくれる機能だ。
ウェブホスティング用のS3バケットを作成したり、静的ファイルをS3にアップロードしたりするのをServerlessFramework経由で行う。

プロジェクトを作成

とりあえず以下のコマンドを実行してプロジェクトを作成する。プロジェクト名はs3-sync-testにした。

sls create --template aws-nodejs --path s3-sync-test

プラグイン導入

ファイルをS3にアップロードする機能はServerlessFramework標準には無いようなので以下のプラグインを利用する。

github.com

以下のコマンドでプラグインをインストールする。

sls plugin install -n serverless-s3-sync

serverless.ymlの末尾にpluginsの項目が追加されたはずだ。

plugins:
  - serverless-s3-sync

S3バケットの設定

resourcesの項目にS3バケット設定とS3バケットポリシーを記載する。

resources:
  Resources:
    StaticSite:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${self:custom.webSiteName}
        AccessControl: PublicRead
        WebsiteConfiguration:
          IndexDocument: index.html
          ErrorDocument: error.html
    StaticSiteS3BucketPolicy:
      Type: AWS::S3::BucketPolicy
      Properties:
        Bucket:
          Ref: StaticSite
        PolicyDocument:
          Statement:
            - Sid: PublicReadGetObject
              Effect: Allow
              Principal: "*"
              Action:
              - s3:GetObject
              Resource:
                Fn::Join: ["", ["arn:aws:s3:::",{"Ref": "StaticSite"},"/*"]]

customにウェブサイト名とS3にアップロードするファイルを配置しているディレクトリ名を記載する。

custom:
  webSiteName: s3-sync-test.com
  s3Sync:
    - bucketName: ${self:custom.webSiteName}
      localDir: static

HTML用意

<project>/static ディレクトリにindex.htmlerror.htmlを配置した。

デプロイ

以下のコマンドでデプロイする。

sls deploy -v

動作確認

AWSのマネジメントコンソールからS3のページへ遷移して、上記で作成したバケットがあるか確認する。
バケット内を確認し、HTMLファイルがアップロードされているか確認する。

f:id:hkou:20180507212858p:plain

ブラウザから確認

プロパティからStatic website hostingを選択してエンドポイントを開く。

f:id:hkou:20180507213123p:plain

ブラウザから表示することができた。

f:id:hkou:20180507213246p:plain

水辺の温泉 日帰りツーリングしてきた

GW, せっかく天気が良いのにずっと家に引き篭もっているのもアレなので、久しぶりに日本海側の温泉へ行くことにした。日本海側は天気が良い日が少ないので今日みたいな「全国的に晴れ」みたいな日を狙って行かないと満足度が低いのだ。

道程が長くなりそうなので、朝早めに出発した。
まずは滋賀県方面へ向かう。途中、木曽川河川敷で休憩した。

f:id:hkou:20180505065017j:plain

f:id:hkou:20180505065029j:plain

走ること2時間程度、琵琶湖の辺りに到着した。朝早めだったので他の車もおらず、快適に運転することができた(通常だと名古屋抜けるのに相当苦労する) f:id:hkou:20180505082216j:plain

敦賀湾のあたり。 f:id:hkou:20180505085802j:plain

敦賀湾を抜けると、道がどんどん低くなり殆ど海面と変わらないぐらいになってきた。波間の静かな湾と違い、ここまで来ると日本海の荒波を感じる。波消しブロックに波があたり鈍い低温が響いている。路面の位置も海と比べてそこまで高くないので、ちょっと波が強いと波しぶきが道路まではみ出していて、運転しているとちょっと怖かった。あと波しぶきでヘルメットが汚れた。 f:id:hkou:20180505091459j:plain

f:id:hkou:20180505091503j:plain

越前温泉露天風呂漁火

ここの温泉は道の駅に併設されており、食事施設と併せて利用できて便利。また、日本海側に面しているので大海原を見ながら露天風呂に浸かることができる。
以前、能登&金沢ツーリングへ行った時に見つけたスポットだ。
hkou.hatenablog.com

残念ながら、撮影禁止のため湯船の写真は無い。日本海の大海原を眺めながら露天風呂に浸かるとついつい長居してしまった。
朝ごはんを食べずに来たので、ここで朝ごはんを食べることにする。道の駅内のお食事処かねいちというお店でお刺身定食を食べた。 f:id:hkou:20180505110912j:plain

南砺市平ふれあい温泉センター ゆ~楽

予定よりも早く到着したので時間に余裕があった。このまま自宅へ帰るというのも味気ない感じがしたのでまた別の温泉へ行くことにした。
それがここである。少し距離は遠いが日本海側に来ることはあまりないのでせっかく来たのだしということで、足を伸ばそうという気分になった。 f:id:hkou:20180505155059j:plain

庄川の祖山ダム湖の辺りに併設された温泉で、高い位置にある露天風呂からダム湖全体を見渡せる景観の良い温泉です。
f:id:hkou:20180505155022j:plain

まとめ

f:id:hkou:20180506075117p:plain

  • 総走行距離508km(GoogleMapタイムラインから)
  • GWだからか結構ツーリングしている人達を見かけた
  • 日本海側のルートはヤエー返答率が高いような気がする
  • 一日中快晴だったが、早朝夜のあたりはバイクで走っているとまだまだ寒い

第6世代iPad(シルバーグレー)を購入した

概要

最近手書きでメモすることが多くなり、いつも紙に描いていたが、いつの間にかどこかへ行ってしまったり、整理がしづらかったのでデジタル化したいなぁと常々感じていた。
Apple PencilはiPad Proでしか利用できなかったが、この前新しく出た第6世代iPadでも利用できるようになったとのことで、 iPad or iPad Proのどちらにするかで迷っていた。

家電量販店に展示されていた実機を触ってみたところ、確かにiPad Proの方が書き味が良くレスポンスも早いが、自分にとっては無印iPadでも充分レスポンス早いし、いろいろ触っててもうこれで良いやと思えてきたので購入に踏み切った。

外箱

Apple製品らしいスッキリしたデザイン f:id:hkou:20180430122939j:plain

開封

f:id:hkou:20180430123058j:plain

Apple Pencil

Apple Pencilももちろん同時購入した。

f:id:hkou:20180430123146j:plain

電源ON

f:id:hkou:20180430123406j:plain

最近のiPadiPhoneと同期させることで初期設定をスキップすることができるようになったらしい。
画面中央のモヤモヤしたところをiPhoneのカメラで撮影すると同期される。

f:id:hkou:20180430123536j:plain

手書きノートアプリ

手書きノートアプリで一番有名どころのGoodNotes4をとりあえず購入してみた。

GoodNotes 4

GoodNotes 4

  • Time Base Technology Limited
  • 仕事効率化
  • ¥960

SplitView

iPadにはSplit Viewという画面分割してそれぞれ別のアプリを同時に動かす機能がある。
早速KindleとGoodNotes4で利用してみた。読書しながらメモが書けるようになった。 f:id:hkou:20180430153458j:plain

まとめ

良いところ

  • Apple Pencilの書き味、レスポンスは素晴らしい
    • GoodNotes4との組み合わせは最高だった
  • カメラの出っ張りがなくて良い
  • 低価格なので傷とかあまり気にせず使用することができる

悪いところ

  • Apple Pencilの持ち手がツルツルしてるので汗で滑る

木崎湖ツーリング行ってきた

今日は天気が良かったので木崎湖へツーリングしてきた。
山の上の方にはまだ雪が積もっているけど、下の方は初夏のような気温で、ツーリングするには最適だった。

f:id:hkou:20180421125910j:plain

ゆーぷる木崎湖で昼ごはんを食べる。休日のお昼時だというのに自分と他一組ぐらいしか客がいなくてガラガラだった。スキーの季節終わったから客が少なくなったのだろうか。木崎湖行くときはいつも利用するので潰れてしまうと困る。 f:id:hkou:20180421132319j:plain

今日は雲ひとつ無い快晴なので、パラグライダー場の方へ行ってみることにした。
通行止めの看板が立ててあったけど脇にずらされていた(これはゆるきゃんで見た通行できる通行止めというやつでは) f:id:hkou:20180421144613j:plain

こんな感じの道をずーっと登っていく。路面には砂利や木の枝、石や岩が転がってて何度かタイヤが滑りかけて怖かった。しかもこの道ガードレールないし。
途中ロードバイクで登っている人を何人か見かけた。 f:id:hkou:20180421143335j:plain

登っているとたまにこんな感じで切り開けた場所に出くわす。景色を見ながら小休止したりしていた。 f:id:hkou:20180421143326j:plain

40分ぐらいかかって、やっと頂上へ到着した。予想通り見渡す限りの絶景だった。 f:id:hkou:20180421141529j:plain

景色を堪能したので下に降りてきて湖畔を歩き回っていた。気温が高くなっているからかいつもよりも人が多いような気がする。 f:id:hkou:20180421150725j:plain

歩き周って温泉入りたくなってきたので、近くの「白馬八方温泉 みみずくの湯」へ行ってきた。ここの温泉は露天風呂から白馬三山を一望できて良い。 f:id:hkou:20180421153704j:plain

まとめ

  • 総走行距離465km
  • 木崎湖はいつ来ても良い
  • 久しぶりに長距離ツーリングしたので尻が痛くなった

f:id:hkou:20180421235041p:plain

API GatewayでAPIキー認証付きのHTTP APIを作る

やりたいこと

  • 以下の記事で作成したAPIは誰でも呼び出せる状態なので、ある特定のクライアントからしか呼び出せないように認証をかける

hkou.hatenablog.com

serverless.ymlを編集する

providerの項目にAPIキーと使用量プランの記述を追加する。

  • APIキー
    • とりあえず名前だけ指定
  • 使用量プラン
    • レート
    • バースト
    • クォーター
      • 1 日あたり 5,000 リクエスト数
provider:
  name: aws
  runtime: nodejs6.10
  region: ap-northeast-1
  apiKeys:
    - myFirstKey
  usagePlan:
    quota:
      limit: 5000
      offset: 0
      period: DAY
    throttle:
      burstLimit: 200
      rateLimit: 100

上記で設定したAPIキーを利用するAPIに対してprivate: trueを指定する。

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: api/hello
          method: get
          private: true
  add:
    handler: handler.add
    events:
      - http:
          path: api/add
          method: post
          private: true

デプロイする

deployコマンドでデプロイする。
作成されたAPIキーの値が出力されるので控えておく

sls deploy -v

(省略)

service: test-service
stage: dev
region: ap-northeast-1
stack: test-service-dev
api keys:
  test-api-key: (APIキーの値)
endpoints:
  GET - https://(URL).ap-northeast-1.amazonaws.com/dev/api/hello
  POST - https://(URL).ap-northeast-1.amazonaws.com/dev/api/add

動作確認

APIキーなしで呼び出す

403 ForbiddenがレスポンスされAPIが呼び出せなくなった。

f:id:hkou:20180419215925p:plain

APIキーありで呼び出す

APIキーをヘッダーに付加するとちゃんと呼び出せる。

f:id:hkou:20180419220121p:plain

ServerlessFrameworkでHTTP API (API Gateway + Lambda (Node.js) )を作成&デプロイする

やりたいこと

  • serverless frameworkを利用してHTTP APIを作成、デプロイする。
  • HTTP APIAWSのApiGateway + Lambdaの構成で作成する。

環境

serverless frameworkをインストールする

公式ページを参考にする。npmのグローバルインストールでいれる。

serverless.com

npm install -g serverless

インストールされたか確認

sls -version
1.26.1

AWSのアクセスキー、シークレットキーを作成する

AWSの管理コンソールからIAMを選択、サイドメニューのユーザーからユーザーの追加を押下 f:id:hkou:20180417224624p:plain

「AdministratorAccess」ポリシーを付加した任意のユーザーを作成する。
ユーザーを作成したら認証情報が記載されたCSVファイルがダウンロードできるので開いておく。

aws cliにアクセスキー、シークレットキーを登録する

docs.aws.amazon.com

$ aws configure
AWS Access Key ID [None]: アクセスキーを入力する
AWS Secret Access Key [None]: シークレットキーを入力する
Default region name [None]: ap-northeast-1 とりあえず東京リージョン
Default output format [None]: json

プロジェクトを作成する

Serverless Frameworkのcreateコマンドでサービスのテンプレートを作成する

sls create --template aws-nodejs --path test-service

カレントディレクトリにtest-serviceディレクトリが作成される。

handler.jsを編集する

先程のcreateコマンドでhandler.jsの雛形が作成されたので、これを開き編集していく。
作成するAPIは以下の2つ(内容に特に意味はない)

  • GETするとHelloWorldの文字列を返す簡単なAPI
  • 数値配列をPOSTすると合計値を返すAPI
'use strict';
module.exports.hello = (event, context, callback) => {
  const response = {
    statusCode: 200,
    body: JSON.stringify({
      message: 'Hello World'
    }),
  };
  callback(null, response);
};
module.exports.add = (event, context, callback) => {
  const response = {
    statusCode: 200,
    body: JSON.stringify({
      result: JSON.parse(event.body).reduce((acc, x) => acc + x)
    }),
  };
  callback(null, response);
};

serverless.ymlを編集する

先程のcreateコマンドでserverless.ymlの雛形が作成されたので、これを開き編集していく。

東京リージョンにデプロイしてほしいのでproviderの下にregion指定の記述をする。

provider:
  name: aws
  runtime: nodejs6.10
  region: ap-northeast-1

APIのエンドポイントを定義する。

functions:
  hello:
    handler: handler.hello
    events:
      - http:
          path: api/hello
          method: get
  add:
    handler: handler.add
    events:
      - http:
          path: api/add
          method: post

デプロイする

以下のコマンドを実行する。
AWSにデプロイされ、エンドポイント情報が出力される。

sls deploy -v

(省略)

Serverless: Stack update finished...
Service Information
service: test-service
stage: dev
region: ap-northeast-1
stack: test-service-dev
api keys:
  None
endpoints:
  GET - https://(API Gatewayのパス).ap-northeast-1.amazonaws.com/dev/api/hello
  POST - https://(API Gatewayのパス).ap-northeast-1.amazonaws.com/dev/api/add
functions:
  hello: test-service-dev-hello
  add: test-service-dev-add

APIを呼び出してみる

GET /api/hello

f:id:hkou:20180417230326p:plain

POST /api/add

f:id:hkou:20180417230158p:plain

Groovy, Spockを使ってJavaのテストを作成する

Gradleをインストールする

公式のインストールガイドを読むと、Chocolateyでインストールできるようなのでこちらでインストールした。 gradle.org

choco install gradle

プロジェクト作成

プロジェクト用のディレクトリを作成してその中で以下のコマンドを実行し、プロジェクトの雛形を作成する。

gradle init --type java-application
  • build.gradleファイルにSpockを追加する。
  • plugins, dependenciesにGroovyを追加する。
plugins {
    id 'java'
    id 'groovy'
}

dependencies {
    compile "org.codehaus.groovy:groovy-all:2.4.15"
    testCompile "org.spockframework:spock-core:1.1-groovy-2.4"
}

テスト対象のクラスを追加

Integerのコレクションを受け取り、合計値を返却する関数を作成した。中身は未実装なのでとりあえず0を返却する。

package app;

import java.util.Collection;

public class Calculator {
    public int sum(Collection<Integer> list) {
        return 0;
    }
}

Specificationを追加

import app.Calculator
import spock.lang.*

class CalculatorSpec extends Specification {
  def "リストの各要素を合計した値を返すこと"() {
    given:
      def calculator = new Calculator()
    when:
      def actual = calculator.sum(list)
    then:
      actual == expected
    where:
      list                   | expected
      Arrays.asList(1)       | 1
      Arrays.asList(1, 2, 3) | 6
  }
}

テストを実行

以下のコマンドでテストを実行する。

gradlew test

未実装なので当然テストが失敗する。

CalculatorSpec > リストの各要素を合計した値を返すこと FAILED
    org.spockframework.runtime.SpockComparisonFailure at CalculatorSpec.groovy:11
Condition not satisfied:

ret == expected
|   |  |
0   |  1
    false

Condition not satisfied:

ret == expected
|   |  |
0   |  1
    false

    at CalculatorSpec.リストの各要素を合計した値を返すこと(CalculatorSpec.groovy:11)

テストが通るように実装

public class Calculator {
    public int sum(Collection<Integer> list) {
        return list.stream()
                .reduce((x, y) -> x + y)
                .orElse(0);
    }
}

テストが通るようになった。

gradlew test
BUILD SUCCESSFUL in 1s
3 actionable tasks: 3 up-to-date

例外のテスト

素数が0のリストは受け付けないようにしたい。まずは要素数0の場合のテストを追加する。

  def "リストが空の場合はエラー"() {
    given:
      def calculator = new Calculator()
    when:
      calculator.sum(Arrays.asList())
    then:
      thrown(IllegalArgumentException)
  }

テストが失敗することを確認

Expected exception of type 'java.lang.IllegalArgumentException', but no exception was thrown
Expected exception of type 'java.lang.IllegalArgumentException', but no exception was thrown
    at org.spockframework.lang.SpecInternals.checkExceptionThrown(SpecInternals.java:85)
    at org.spockframework.lang.SpecInternals.thrownImpl(SpecInternals.java:72)
    at CalculatorSpec.リストが空の場合はエラー(CalculatorSpec.groovy:24)

    org.spockframework.runtime.WrongExceptionThrownError at CalculatorSpec.groovy:24
2 tests completed, 1 failed
:test FAILED

リストの要素が空の場合に例外を投げるように実装を修正

public int sum(Collection<Integer> list) {
    return list.stream()
            .reduce((x, y) -> x + y)
            .orElseThrow(() -> new IllegalArgumentException("list is empty"));
}

再度テストを実行して成功することを確認

BUILD SUCCESSFUL in 3s
3 actionable tasks: 3 executed

テストレポート

テストを実行する度にHTMLのテストレポートが生成される。
生成場所は{プロジェクトディレクトリ}/build/reports/tests/test/index.html

f:id:hkou:20180415111557p:plain