Serverless FrameworkでCognito UserPoolの認証をしばくときのメモ

  • 社内向けのサーバーレスなサービスが想定で、API GatewayでセキュアなAPIを作ることを考える。
  • そこでCognito UserPoolを使った場合のメモ

Severless Framework

  auth:
    handler: auth.default
    timeout : 5
    memorySize : 256

  secureCheck:
    handler: handler.secureCheck # 動かすlambda関数名
    timeout : 5
    memorySize : 128
    events:
      - http:
          method: get
          path: secureCheck
          cors : true
          authorizer: auth #authorizer: xxx:xxx:Lambda-Name も可能
          response:
            headers:
              Content-Type: "'application/json'"
            template: $input.path('$')

/secureCheck と云うAPIは,authと云うLambda Functionの認証を経てsecureCheck Functionに至るようになる。
そうでなければ403が返る。

この形式でsecureCheck APIにアクセスするとき、リクエストのAuthorizationヘッダーに
Cognito UserPoolでログイン認証したアクセストークンをつけること。
クライアント側の処理は今回は書かない。

auth.ts

const aws = require("aws-sdk");

var cognitoidentityserviceprovider = new aws.CognitoIdentityServiceProvider({
    apiVersion: '2016-04-18',
    region: 'ap-northeast-1'
});

const auth = (event, context, callback)=> {

    const token = event.authorizationToken != null ? event.authorizationToken : event.headers.Authorization

    var params = {
        AccessToken: token
    };
    cognitoidentityserviceprovider.getUser(params, function (err, data) {
        if (err) {
            console.log(err)
            callback(null, generatePolicy('user', 'Deny', event.methodArn , token));
            //callback(null, generatePolicy('user', 'Allow', event.methodArn, data));
        } else {
            console.log(data)
            callback(null, generatePolicy('user', 'Allow', event.methodArn , token));
        }
        return;
    });
};

//"methodArn":"arn:aws:execute-api:<regionId>:<accountId>:<apiId>/<stage>/<method>/<resourcePath>"
const generatePolicy = function generatePolicy(principalId, effect, resource , token) {
  return {
    principalId:principalId,
    policyDocument: {
      Version: '2012-10-17',
      Statement: [{
        Action: 'execute-api:Invoke',
        Effect: effect,
        Resource: "arn:aws:execute-api:ap-northeast-1:*:<API Gateway Id>/*” //CHECK!! =>  http://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/permissions.html#api-gateway-calling-api-permissions
      }]
    }
  };
};

export default  auth;

generatePolicyメソッドで、ユーザーに対して必要なAWSの権限をつけている。
ここでは、該当のAPI GateWayの各エンドポイントのinvoke権がつくように組んだ。

他メモ

Cognito UserPoolのAccessTokenやIdTokenのexpireはぴったり1時間なので、
APIを叩くたびに Cognateアクセストークンのexpireチェック => 必要ならばCognitoのrefresh叩いてToken更新 => セキュアなAPI gateway叩く、というフローを経ることになる。
Dynamoにセッションとか入れとくよりは楽なのだろうけど割とラフに非同期チェーンが入ってめんどくさい。

毎回リクエストヘッダにアクセストークンつけるのも、Ajax以外でのエンドポイントのアクセスが制限されてしまうので、なんとかCookie使う方向で今後模索してみる。

Read More

Go言語 関数の色々

この記事はWanoグループ Advent Calendar 2016の24日目の記事です。

Goの関数宣言方法

package main
import (
    "fmt"
    "math"
)

type Vertex struct {
    X, Y float64
}

//普通の使い方
func Abs(v Vertex) float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

//オブジェクトにメソッドを追加する
func (v Vertex) Abs() float64 {
    return math.Sqrt(v.X*v.X + v.Y*v.Y)
}

//自分のオブジェクトを作って、そこにプリミティブ型のメソッドを追加する
type MyFloat float64
func (f MyFloat) Abs() float64 {
    if f < 0 {
        return float64(-f)
    }
    return float64(f)
}

func main() {
    v := Vertex{6, 8}
    f := MyFloat(-10.3)
    fmt.Println("v.Abs():",v.Abs())
    fmt.Println("Abs(v):",Abs(v))
    fmt.Println(f.Abs())
}

Goは、関数の引数として関数をわたすことができる。

package main
import (
    "fmt"
    "math"
)

/*
funinside func(float64, float64) float64 ==> 関数の引数
  関数名     引数1,  引数2   戻り値
*/
func compute(funinside func(float64, float64) float64 ,x,y float64) float64 {
    return funinside(x,y)
}

func add(x,y float64) float64 {
    return x+y
}
/*
compute (関数の引数,引数A,引数B)  関数替わり可能
*/
func main() {
    fmt.Println(compute(add,3,3))      //=> 3+3 
    fmt.Println(compute(math.Pow,3,3)) //=> 3^3
}

結果:
6
27

クロージャの使い方

package main
import "fmt"
// fibonacci is a function that returns a function that returns an int.
func fibonacci() func() int {  //匿名関数
    a, b := 0, 1
    return func() int {
        a, b = b, a+b  // (a = b), (b = a + b) 同時実行の意味 =>
        /* 同じコード
        c:= a
        a = b
        b = c + b
        */
        return a
    }
}

func main() {
    f := fibonacci()
    for i := 0; i &lt; 10; i++ {
        fmt.Print(f()," ")
    }
}

結果:
1 1 2 3 5 8 13 21 34 55

Read More

serverless framework のデプロイは遅くないんだよ

これまで

serverless deploy

を叩き続けてました。
叩いては遅いなー、こんなんじゃ開発にならないでしょとか思ってました。
毎回cloudformation動くから遅いんじゃないのとか、分かった風な口叩いてました。
apexの方がデプロイ速いから、apex使ってました。
でも勘違いでした。

serverless deploy function -f YOUR_FUNCTION

で、指定したfunctionだけを爆速でデプロイできました。
調子こいてました。
わかってませんでした。
ごめんなさい。
serverless framework良いっすね。
ありがたく使わせていただきます。

Read More

AWS Step FunctionsでLambda Functionでのサムネイル画像作成をシンプルに

この記事はWanoグループ Advent Calendar 2016の23日目の記事です。
Wanoグループ内でのLambda推しエンジニアとして、前回のLambda@Edgeに引き続き、2016年Re:Inventで発表された、AWS Step Functionsについての書いてみたいと思います。

AWS Step Functionsで解決できること

なんと言っても以下の2点だと思います。

  • Lambda Functionを小さい単位で維持できる
  • Lambda Function間の連携にSQSやDynamoDB等を使用する必要がなくなる

Wanoでも複数のLambda Functionを活用していますが、1つのFunctionの中でいろいろなことをやりすぎたため、他のサービスにそのまま転用できなかったり、コードの見通しが悪くなったりと言ったことが出てきました。
1つ1つのFunctionを細かく分けて、SQS等を使用して連携されることも検討しましたが、リトライや分岐のためにFunctionを用意しないといけないといったことを考えると、腰が重くなっていました。
その問題点を解決してくれるのがAWS Step Functionsです。

Lambda Functionの事例で見かけるサムネイル画像作成をStep Functionsで

Lambda Functionsの事例として、サムネイル画像を作成するサンプルをよく見かけます。
この記事でもサムネイル画像作成を例にし、以下のサムネイル画像あるあるを解決する処理をLambda FunctionとStep Functionsで実現したいと思います。

サムネイル画像あるあるとは

  1. せっかくサムネイル画像で小さくしたのに、Google PageSpeedで調べたら、ロスレス圧縮したら46%小さくなるよと言われる
  2. 適切なContent-Typeヘッダーが付いておらずブラウザで想定外の挙動をする
  3. Cache-Controllヘッダーが付いておらずブラウザキャッシュが効かない

勝手にあるあると言っているだけで、私が良く忘れるだけという可能性が大きいです。。。が、今回はこれを解決することを前提として、進めたいと思います。

Lambda Functionを実装する

Lambda Functionを小さい単位で維持することが重要なので、以下の機能で分けてLambda Functionを実装したいと思います。

  1. ResizeImage: 画像をリサイズするFunction
  2. MinifyJpeg: JPEG画像をロスレス圧縮するFunction
  3. MinifyPng: PNG画像をロスレス圧縮するFunction
  4. SetMeta: メタデータを設定するFunction

実装できたら、これらのLambda Functionを予めデプロイしておきます。
ちなみに私は以前apexを使っていましたが、最近serverless frameworkに乗り換えを進めています。
その内この辺りも記事にできたらなと。

AWS Step Functionsを設定する

State Machineを作る

Step Functionsの設定画面でState Machineを作ります。
設定ファイルは以下です。

注意
2016年12月23日時点で、State Machineの修正はできません。State Machineの設定ファイルもバージョン管理対象としておくことを強くおすすめします(泣きを見ました。。。)
{
  "Comment": "An example of the Amazon States Language using a choice state.",
  "StartAt": "ResizeImage",
  "States": {
    "ResizeImage": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:FUNCTION_NAME",
      "Next": "JpegOrPng"
    },
    "JpegOrPng": {
      "Type" : "Choice",
      "Choices": [
        {
          "Variable": "$.object.fileType",
          "StringEquals": "jpeg",
          "Next": "MinifyJpeg"
        },
        {
          "Variable": "$.object.fileType",
          "StringEquals": "png",
          "Next": "MinifyPng"
        }
      ],
      "Default": "UnsupportedImage"
    },
    "MinifyJpeg": {
      "Type" : "Task",
      "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:MinifyJpeg",
      "Next": "SetMeta"
    },
    "MinifyPng": {
      "Type" : "Task",
      "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:MinifyPng",
      "Next": "SetMeta"
    },
    "UnsupportedImage": {
      "Type": "Fail",
      "Cause": "No Matches!"
    },
    "SetMeta": {
      "Type": "Task",
      "Resource": "arn:aws:lambda:REGION:ACCOUNT_ID:function:SetMeta",
      "End": true
    }
  }
}

上記ファイルでの設定結果として以下のState Machineができます。

State Machineを実行する

State Machineができたら、早速実行してみましょう。
Lambda Function同様コンソールからJSONを指定して実行できますので、そちらで実行します。

動きました!!
今回は、JPEG画像をイベントとして渡したので、MinifyJpegに遷移しています。
PNG画像を渡せばMinifyPngに遷移する。。。はず(もう23日が終わりそうなので、試す時間がなかったり。。。)

State Machine実行時の注意点

注意点としては、Lambda Functionの実行ではなく、State Machineの実行という点です。
今回StartAtに指定したResizeImageが起動するイベントを実行してもState Machineは実行されず、State Machineの起動が必要になります。
残念ながら、現時点ではState Machineをイベントドリブンにすることはできないようです。
実運用では監視したいイベントを割り当てたLambda Functionを用意して、そちらからState Machineを実行するような構成にする必要があります。

まとめ

いくつかの課題はあるものの、AWS Step Functionsを使うことで、Lambda Function間の連携がシンプルになることは間違いありません。
課題自体も恐らく時間の問題で改善されると思いますし、serverless frameworkといった周辺ツールも続々対応してくると思うので、今後も注目です!!

Read More

AWS CodeBuildで分課金のコンテナ実行環境を手に入れる

伏見です。この記事はWanoグループアドベントカレンダー2016の21日目の記事になります。年末感。

書くこと

  • NightmareでのWebサイトE2Eテスト実行環境を、Dockerで構築・CodeBuildで実行してみた
  • 実行時に環境変数も渡せるし割とCodeBuildには使いどころありそう
  • CodeBuild Tokyoリージョン はよ

AWS CodeBuild発表、着想

先日のAWSのイベントre:invent 2016 にて『CodeBuild』 の発表が行われた。
モバイル/Webアプリケーションのビルド/テストのCIのパイプライン上に載せることが想定されているようだ。
現在(2016/12/20) Tokyoリージョンはなく、バージニア、オレゴン、アイルランドでのサービス提供になる。

気になったポイントは2つ。

  • 自分で用意したDockerの実行環境が使える。
  • (ビルド)タスクの実行時間に応じた「分単位での課金体系」である。

自作Dockerイメージが使える

AWS Lambdaのようにコードをサクッと動かしたい時に自作dockerコンテナが使えないかなあ、というのはたまに思っていた。
CodeBuildで実行環境自作のDockerイメージが使えることにより、インスタント環境として AWS Lambda より便利そうな点がある。

  • 使用言語の制限がなくなる。
  • あらかじめカスタマイズ済のイメージを構築しておける。

0.005$~ /分の課金

AWS CodeBuild 料金
CodeBuildはビルド実行時間(分単位 , 1分あたり 0.005$)での課金と転送量での課金になる。
インスタンスのプランをあげると0.02$/分まで上がる。

課金体系がこの通りなら、用途によってはいろいろ捗りそう。

リリース予定の AWS Batch も優秀そうだし、実行環境に信頼度がおけそうな感じがあるが、
あっちは(多分)普通にEC2借りてて1時間単位での課金だし、スポットインスタンス狙っていく作業がちょっと大げさな時もあるかと思う。

何ができそうか考える

  • 普通にAndroidアプリとかのビルド/ DeviceFarmにテスト投げる
  • 最悪止まっても問題なさそうなE2Eテストやスクレイプ作業
  • apiなどの監視タスク

いわゆるCIの用途以外でも考えようとしたけど…。
まあ他にも即時性なくてもいいけどDockerでサクッと動かしたいタスクで何かしら使いどころはあるんじゃないだろうか。

動かしてみるもの

ちょうどWebサイト周りのE2Eテストについて勉強中なので、その実行環境にCodeBuildを是非使ってみたい。
ブラウザE2Eテスト実行環境を整えたDockerイメージを用意し、AWSコンソール上でCodeBuildを実行/テストを走らせてみるとこまでやってみる。

E2Eテストを仕込んだDockerイメージの用意

Nightmare基盤

テストフレームワークNightmareをdockerのコンテナ上で動かす上で,レンダリングのためにxvfbなどの仮想フレームをインストールしたり、その他必要なモジュールをとってこなければならない。
この辺のインストール作業は冗長で、そもそもの興味範囲とは違うところであれがない、これがない、とハマりがち。
その辺をあらかじめ織り込んだDockerfileを公開してくれている人はガンガンいるので、それを参考にイメージを構築することにする。

いざとなったらDockerfileにはlinux上でのモジュールのインストール手順がそのまま記載されているので,別にdocker使わずとも学ぶことが多い。

nightmareのDockerfileを参考に、以下のようなDockerfileを構築する。

FROM node:7.2
MAINTAINER mr.hoge <xxxx.co.jp>

# add our user and group first to make sure their IDs get assigned consistently, regardless of whatever dependencies get added
RUN groupadd --system nightmare && useradd --system --create-home --gid nightmare nightmare
ENV HOME "/home/nightmare"

ENV DEBUG=nightmare
ENV ARGUMENTS=()

RUN apt-get update && apt-get install -y \
  vim  gcc make cmake g++ \
  software-properties-common vim wget curl unzip zip \
  xvfb \
  x11-xkb-utils \
  xfonts-100dpi \
  xfonts-75dpi \
  xfonts-scalable \
  xfonts-cyrillic \
  x11-apps \
  clang \
  libdbus-1-dev \
  libgtk2.0-dev \
  libnotify-dev \
  libgnome-keyring-dev \
  libgconf2-dev \
  libasound2-dev \
  libcap-dev \
  libcups2-dev \
  libxtst-dev \
  libxss1 \
  libnss3-dev \
  gcc-multilib \
  g++-multilib && \
    rm -rf /var/lib/apt/lists/* && \
        find /usr/share/doc -depth -type f ! -name copyright | xargs rm || true && \
        find /usr/share/doc -empty | xargs rmdir || true && \
        rm -rf /usr/share/man/* /usr/share/groff/* /usr/share/info/* && \
        rm -rf /usr/share/lintian/* /usr/share/linda/* /var/cache/man/*
RUN apt-get clean

WORKDIR ${HOME}
COPY ./package.json ./
RUN npm install

VOLUME ${HOME}

オリジナルと違うところは、イメージのベースをNodeJS7系にしているところや、とりあえずvimやwgetも入れているところくらい。

E2Eテストに使うモジュールの準備

package.json

{
  "name": "docker-my-nightmare",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "./node_modules/.bin/mocha .test.js --timeout 6000000",
    "e2e_with_debug" : "DEBUG=nightmare:*,electron:* xvfb-run --server-args='-screen 0 1024x768x24' ./node_modules/.bin/mocha --harmony ./test.js  --timeout 600000",
    "e2e_on_docker" : "xvfb-run --server-args='-screen 0 1024x768x24' ./node_modules/.bin/mocha --harmony ./test.js  --timeout 600000"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
    "mocha": "^3.2.0",
    "mocha-co": "^1.17.2",
    "nightmare": "^2.8.1",
    "power-assert": "^1.4.2"
  }
}

ついでにテスト発火用コマンドをpackage.jsonに記載しておく。

テストスクリプトの用意

ブラウザテストそのものや、Nightmare周りについては今回は触れない。
簡単なjavascriptのコードだけ載せる。

test.js

const mocha = require('mocha-co');
const Nightmare = require('nightmare');
const assert = require('power-assert');

const sleep = (ms)=>{
  return new Promise((resolve , err)=>{
    setTimeout(()=>{
      resolve();
    },ms);
  });
};

describe("1st Test", function() {  

  let nightmare;

  before(function*() {
    console.log("Start Testing... #####################");
    nightmare = Nightmare();
  });

  it('googleでhogeを検索', function*() {
    const title = yield nightmare
      .viewport(1024,768)
      .goto('https://www.google.co.jp/?gfe_rd=cr&ei=qTBaWIX5OYqL8QeryomIDg#q=hoge&btnK=Google+%E6%A4%9C%E7%B4%A2')
      .title();

    assert(title === 'Google');
  });
});

ローカルのdocker上でちゃんと動くようであれば、おもむろにコンテナをcommitして、オールインワンなイメージを作っておく。
1.3GBほどになってしまった。

AWS ECRへの dockerリポジトリの登録

DockerHubのAWSバージョンと言えるAmazon EC2 Container Registry にDockerイメージを置くことにする。
ECRは、dockerのプライベートリポジトリを置く上で、割と安いサービス。

CodeBuildをOregon(us-nor)で立てているため、リポジトリもOregonに置く。
Tokyoリージョンに置いたリポジトリがCodeBuild上でうまく指定できなかったためだ。
この辺は後でIAM設定見直すが、できればCodeBuildが早くTokyoリージョンに来てほしい。

先ほどのビルド済イメージをECRにpushしておく。

docker push xxxxxxx.us-west-2.amazonaws.com/yyyyy:latest

ビルドソース準備

CodeBuildはビルドソースとしてS3/GitHub/CodeCommit(AWSのGitホスティング)が使える。
今回はアプリケーションのビルドをするわけじゃないのでソース設定は正直なんでもいいのだが、勉強のためにS3にする。
CodeBuildの設定ファイルであるbuildspec.ymlをS3に置き、そこに書かれているコマンドをdockerで実行する、という構成にしてみる。

buildspec.yml

version: 0.1
phases:
  install:
    commands:
  pre_build:
    commands:
      - echo $MY_VAR
  build:
    commands:
      - cd /home/nightmare && npm run e2e_on_docker
  post_build:
    commands:
      - echo Done!

中でディレクトリ移動しているのは、Dockerファイルに記載されているWORK_DIRがCodeBuildでの実行時に適用されずにnpmの実行エラーを起こしたため。

S3へのビルド対象のアップロード

先ほどのbuildspec.ymlをzipとしてS3にアップロードする。

zip buildspec.yml > buildspec.zip
aws s3 cp buildspec.zip s3://~~~~oregon-bucket/codebuild/

ここで注意点があって、S3のbucketのオリジンもCodeBuildと同じリージョンになければならない。
(IAMの設定割と見直したんだけど、CodeBuildでのビルド実行時にエラーが出てうまくいかなかった。)

CodeBuildのプロジェクトを作る

今回はConsole上で設定する。
regeonはオレゴン(us-west-2)を選択。

実行環境指定

Source: What to build

  • Source provider
    • Oregonに立てたbucketを指定

Environment: How to build

  • Environment image
    • Specify a Docker image
  • Custom image type
    • AmazonECR
  • Amazon ECR repository
    • ECRのリポジトリを指定
  • Amazon ECR image
    • イメージのタグ指定。履歴を残さなければlatestだけでいいはず
  • Build specification
    • ここに直接コマンドも書けるが、buildspec.ymlの設定を使うことにする。

Artifacts: Where to put the artifacts from this build project

  • Artifacts type
    • 本来ならここでビルド成果の置き場所を指定するが、No artifactsにする。
    • 今回のようなテスト用途なら、E2Eテストの結果やサイトのキャプチャをまとめてzipにするといいかもしれない。

Service role

  • ECR,S3に接続するための実行権限をここで付与する。

role周りについての設定は
Amazon ECR Sample for AWS CodeBuild
を参照。

continue -> OK を押してプロジェクト完成!

ビルド設定

“Start Build”ボタンでビルド設定画面に入る。
S3をビルドソースにしている場合、Source versionとしてs3 objectのバージョニング機能が使えるが、これで運用するのもあんまりうまくない気がする。

Environment variablesでコンテナ実行時に環境変数が渡せるため、これを積極的に使っていくことになりそう。(もちろんこの機能はaws-cliのcodebuildからも使える。)
name,valueは実際には

export MY_VAR=hogehoge

されているようなので、普通に$MY_VARとかで受け取れる。
コンテナの条件は変えずに、「動的にスクリプトやコマンドをダウンロードしてきてテストを実行する」基盤を整えるとなかなか便利になりそう。

実行

待つ。待つ。

上が実行ログの例。
テスト実行中のログはコンソール上で見れる他,CloudWatchのログとしても残る。

試算

1.3GBほどのDockerイメージで、実行開始までに1分半ほどかかった。
E2Eのjavascriptコード自体は短いので20秒ほど。
2分 * 0.005$ で 1回あたりの実行料金1円くらいになる。
これ自体はまあ大したことない。
一方のECRの転送料金がいまいち読めないが、こっちは1.3GB転送しているのでこれは金額的に大きそう。
EC2でECRを使うとき、同じリージョンの場合に EC2 <=> ECRの転送自体は無料なので、 CodeBuildでもこのプランが適用されているといいなーというか、してないときついと思う。

まとめ

CodeBuildはいろんな用途に使えそうですが、東京リージョン早くほしいです。
ついでにbitbucket対応もしてほしい。
(最後雑)

Read More

Swiftでぬるぽをなくす技術

夏休みの宿題は提出日まで何もしない子供でした。はい。
この文章はWanoグループAdvent Calendarの17日目として書かれたものですが、諸事情で公開が遅れました。
多分この記事はは20日分になると思います。
アドベントカレンダーは土日にしないほうが良いという気づきを得た
 

本題

Swiftは良い言語なんですよ。Swiftは。
去年辺りから弊社でiOSアプリの開発を担当するようになって、それまでサーバーサイドでPerlとかを触ってた人間がSwiftに触れて、思ったんですよ。あー、これ良い言語だなって。
今日はその辺をつらつら書いていきたいと思います。
 

みんなぬるぽで死ぬ

みんな大好きNull Pointer Exception!
 

これはサーバーサイド全般に言えると思うんですけど、何かのタイミングでぬるぽで死んでもまぁ、daemontoolsとかsupervisordとかで再起動すれば良いので、基本動作に問題なければ、そこまで致命的じゃないと思うんですよね。
エラー画面も出るし。いや、まぁ、ダメなんですけど。
 

ただアプリの場合は、ぬるぽで死んだときは突然アプリの画面からホーム画面に切り替わるんですよ。
突然ですよ?500エラー画面とかもなしですよ?ビビりません?
ユーザーからしたら、は?なにこれ何いきなりホーム画面に飛んでんの?意味わかんないんだけど?
ってなるじゃないですか。
 

しかもサーバーサイドはエラーを監視しておいて、エラー通知をslackかなんかに飛ばせば、開発者はそれを見てすぐに対応できるじゃないですか。
だからぶっちゃけ5分後にはエラーを修正することができる。理論的には。
いや、めっちゃうらやましいんですけど。なによそれ。ずるくない?
 

こちとら審査っていうね、Apple神にお伺いをたてる儀式をしてですよ、万が一お伺いで逆鱗に触れたら英語で罵倒しあうわけですよ。
しかも罵倒した結果、お前のアプリクソだからださねぇ、ってApple神に言われたらおしまいですからね。
そこは穏便に行かないといけないわけですよ。
 

いや、何が言いたいかって言うと、iOSアプリは修正版を適用するのに時間がかかるんですよね。
最近は審査が早くなったからまだマシになったけれども、昔は最低1週間とかかかってましたからね。
しかもそのあとアプリを更新するかどうかはユーザー次第じゃないですか。
あれ意外とみんな更新してくれないんですよ。
 

となるとアプリを制作するにあたって、最初からぬるぽをなるべく潰していきたいわけですよ、なるべく。
ところがPerlとかJavascriptとかどうよ。
実際に動かしてみて、warnとかconsole.logとかで出力して初めて、あ、この変数nullだわ。そりゃこの処理死ぬよなー、ってわかるじゃないですか。
なにそれ、いちいち全部の動作を試さないといけないの?苦行なの?
って思うわけですよ。
 

ところがですよ、Swiftはぬるぽかどうか明示しなければいけない言語(Null安全)なので、予めプログラムを静的解析する時点で、こいつぬるぽになりそうだな、となるとここで処理が死にそうだな、ってわかるんですよ。
っていうかそういう不定な部分がある時点でコンパイルが通らないようになってるんですよ。
 

しかも、じゃあそこでNullチェックを入れようと処理を書くんですけど、そもそもの言語仕様としてNullチェックが書きやすいようになってるんですよ。
なにこれ、神なの?Apple神がまた新たな神を作っちゃったの?って思ったわけです。
 

Optionalという考え方

とりあえず以下のSwiftで書かれたクラスを見てください。

class HogeClass {
    var hoge: String
    var fuga: String?
    var piyo: String!

    init(){
        self.hoge = "hogehoge"
    }
}

このように、Swiftには型名のあとに、?とか!とかがつけられます。
 

?が付いている型はOptional型というものです。Nullを許す型です。
!が付いている方はUnoptional型というものです。Nullを許さない型です。
何も付いていない場合は不定です。どこかで明示的に代入をしない限り(Unoptional型と明示しない限り)コンパイルが通りません。
このHogeClassの場合、hogeプロパティが不定なので、明示的にinitで代入処理をしているんですね。
 

hogeは確かに見ればNullじゃないことは明らかなので問題はないと。
あれ、じゃあこのfugaとかpiyoプロパティってどうなのよ、って思いますよね。思います。
ここでPerlとかにあるように、オブジェクトのメソッドを実行して、nullだったら処理がどうなるかみてみましょう。

class HogeClass {
    var hoge: String
    var fuga: String?
    var piyo: String!

    init(){
        self.hoge = "hogehoge"

        let splittedHoge = self.hoge.components(separatedBy: ".")
        let splittedFuga = self.fuga?.components(separatedBy: ".")
        let splittedPiyo = self.piyo.components(separatedBy: ".") //ここで死ぬ
    }
}

はい、死んだ。
(.components(separatedBy:)はPerlで言うsplitみたいなやつです。)
 

これを見ると、fugaプロパティの処理では死んでいませんが、piyoプロパティの処理で死んでますね。
これはどういうことかというと、

  • 明示的に!をつけた型の場合、コンパイラはNullではないことを信じて処理を実行します。
  • 明示的に?をつけた方の場合、コンパイラはNullである可能性があるので、勝手にチェックしてnullの場合は処理を実行しません。(なにもしないのと同じ。)
     

これの何が嬉しいの、って話ですけど、ようは明示的にNullであるOptional型の場合、実行しても死なないんですよ。
PerlでTengを使って処理をする時に、

my $row = $teng->single("hogeTable", {id => 1});
$row->get_columns() # 死んだ

ってことがないんですよ。
また、Nullじゃない型の場合は、死ぬ、逆を言えばそこで処理を殺すことができるんですね。
逆にここがNullだとアプリがまともに動かない、プロダクトとして成り立たない、みたいな話の場合、あえて殺すことでデバッグ時に気付けるようにすることもできるんです。
(普通はエラー処理とかするのであまりやらないけど。)
 

となると次は、Nullの時になにもしないのは困るし、かと言って死ぬのも困る。普通にNullの時に別の処理をしたいんだけど、ってなりますよね?なります。
その場合はfugaに対してこうやって書けば良いんです。

class HogeClass {
    var hoge: String
    var fuga: String?
    var piyo: String! = "piyopiyo"

    init(){
        guard let unwrappedFuga = self.fuga else {1
            print("Fuga is Null")
            return
        }

        self.hoge = "hogehoge"

        let splittedHoge = self.hoge.components(separatedBy: ".")
        let splittedFuga = unwrappedFuga.components(separatedBy: ".")
        let splittedPiyo = self.piyo.components(separatedBy: ".")
    }
}

guardはif notみたいな意味の処理だと思ってください。
これは、

  • self.fuga、という変数をUnoptionalの変数(unwrappedFuga)に代入できたら処理を続ける
  • それ以外ではprint文でエラーを吐いて処理を終わらせる

ということをやっているんです。
 

Swiftではif文(guard文)でこのようにUnoptional型の変数に突っ込んでキャストすることが出来ます。
この処理をunwrapする、と言います。(また、!をつけることをForce Unwrapする、と言います。)
こうすることで、Nullの場合のエラー処理を書けるし、以降の処理はNullではない前提で処理を書けるようになるので楽になる、という感じです。
 

また、if文と違い、guard文はSwiftの言語仕様として、処理の最後にかならずreturnかbreakを書かなければいけません。
どういうことかというと、guard文を書いたときは、自然と条件に合わない場合は処理を弾く、という書き方になるのです。
この場合、文字通りぬるぽをguardする処理になります。
これ結構便利なんですよ。
他の言語にもこの仕様がほしいと何度思ったことか。
 

まとめ

このように、SwiftではNull安全の考え方、guardのようなNull安全に書ける言語仕様がそろっているので、とても安全に書きやすいのです。
ほかにもprotocol、extensionをはじめ、色々と安全に書く仕様が揃っているので、アプリを作るのにはもってこいの言語だと思います。
もちろん適当にForce Unwrapしまくって処理を書いていればいくらでも死にますが、そうならないように書ける、もしくは書きやすい言語というのはエンジニアとしては嬉しいのではないでしょうか。
 

Swiftをちゃんと書くようになると、Perlのような型もNull安全もない言語は恐怖すごいと思います。
そのうち、サーバーサイドがそのような言語で書かれた場合にどうやって厳密にNull安全を担保するか、というつらみ方法を書けたらと思います。
 

みんなSwiftにすれば良いのに。

Read More

Lambda@Edgeを使って色々と試してみようとした

この記事はWanoグループ Advent Calendar 2016の18日目の記事です。

はじめに

Wanoでは、webサーバーの前にCloudFrontを配置し、全てのリクエストを一旦CloudFrontで受けて極力キャッシュを返す形を取ることが良くあります。
この構成のメリットは、サーバーサイドでキャッシュの仕組みを構築しなくても、CloudFrontを前に配置するだけで、EC2にリクエストを流すことなくパフォーマンスを改善できることにあります。

ただ、この構成には弱点も当然あり、ログイン機能が無いサービスの運用には最適なのですが、ログイン機能があるサービスの場合、ログイン状態によってキャッシュを返すかどうかの判定ができないため、ユーザーの9割がログインせずに使用するサービスでも、この構成が一切使えないといった弱点があります。

そんな中、先日のRe:Inventで発表されたのが、このLambda@Edgeです。
本当はLambda@Edgeを実際に使ってみていろいろ試してみようと思っていたんですが、プレビューへの申込みが遅かったためかまだ実際に動かすことができなかったので、ひとまず現時点で試したこと、わかったことを書いておきたいと思います。

Lambda@Edgeの設定

実際にCloudFrontに設定して動かすことはできなかったのですが、辛うじてLambdaのテスト機能で動かすことはできたので、blueprintのコードをベースに、少しだけ試してみました。
まずは設定画面です。

Lambda@Edgeは、CloudFrontのDistributionに対して設定するので、Distribution IDを指定する画面になっています。
その他にも、BehaviorとEventが指定できました。Behaviorが指定できるということは、LambdaFunctionを動かすURLと動かさないURLを分けることができるということなので、静的コンテンツ用にはBehaviorを分けて、LambdaFunctionを実行させずに、パフォーマンスとコスト面への考慮ができそうです。

Eventですが、以下の4つを指定できました。

今回は、この4つのEventからViewer Requestを指定してLambdaのblueprintの中から、cloudfront-ab-testを選択して設定します。

Viewer Request

ブラウザ等のクライアントからCloudFrontがリクエストを受け付けた時に発火するイベントです。
個々のリクエストに対して何か処理を追加する場面で使用するイベントになるため、4種類のイベントの中で最も使う頻度が多いのではないでしょうか。

cloudfront-ab-testを読んでみて

以下のコードが使い方を端的に表しているんじゃないでしょうか。
まだ動かせていないので、実際の挙動はわからないのですが、CookieやHeaderを見て条件に応じてURLをRewriteする。というパターンは使い勝手がありそうな気がします。

let modifiedUri = false;
if (headers.Cookie) {
    for (let i = 0; i < headers.Cookie.length; i++) {
        const experimentIndex = headers.Cookie[i].indexOf(experimentCookieName);
        console.error(experimentIndex);
        if (experimentIndex >= 0) {
            if (headers.Cookie[i][experimentIndex + experimentCookieName.length] === groupA) {
                request.uri = groupAObject;
                modifiedUri = true;
            } else if (headers.Cookie[i][experimentIndex + experimentCookieName.length] === groupB) {
                request.uri = groupBObject;
                modifiedUri = true;
            }
        }
    }
}

書いていて気づいたんですが、Wanoグループ Advent Calendar 2016の前日の記事nginx で特定のIPのみ別の upstream を使うについてもLambda@Edgeで同じようなことができそうですね。

少し前に発表されて、既にTokyoリージョンでも使用できるようになっているALBと組み合わせると、リクエストの捌き方をこれまで以上に柔軟に構成できそうです。

元々やりたかった、ログイン状態に応じてキャッシュを返すかどうかを切り替える場合も、少なくともRewriteすれば対応できそうなので、弊社でも使えるようになったら実際の挙動とパフォーマンスをしっかりチェックしたいと思います。

Read More

nginx で特定のIPのみ別の upstream を使う

この記事はWanoグループ Advent Calendar 2016の17日目の記事…が書かれなかったので代理で埋められた記事です。

例えば、新しい機能をリリースするが、一度、関係者のみ本番リリースしたいようなとき。
現状の設定が以下のようになっているなら、

upstream backend_server {
  server 192.168.0.1:8000;
}

location / {
  proxy_pass http://backend_server;
}

次のように変更する。

upstream backend_server {
  server 192.168.0.1:8000;
}
# テスト用のアップストリーム を設定する
upstream backend_server_test {
  server 192.168.0.2:8000;
}
set $backend_server 'backend_server';
if ($http_remote_addr = '192.168.0.5') {
  set $backend_server 'bakcend_server_test';
}

location / {
  # proxy_pass には変数で渡す
  proxy_pass http://$backend_server;
}

このようにすると、REMOTE_ADDRが192.168.0.5の場合のみ、backend_server_test がバックエンドとして使われ、それ以外の場合は、今までと同様、backend_server が使われます。

nginx の設定は柔軟でいいですねー。

Read More

MACで Go言語の開発環境(Go+Vundle+Vimgo+gocode)セットアップ

この記事はWanoグループ Advent Calendar 2016の16日目の記事です。

(2016年12月 Version)
1. GO install

https://golang.org/dl/

Apple OS X (OS X 10.8 or later)
go1.7.4.darwin-amd64.pkg (79MB)

2. Goのpath設定 (~/.zshrc編集)

export GOPATH=$HOME/go
export PATH=$PATH:$GOPATH/bin

テスト方法(hello world):
(1) Create the project

 mkdir -p $GOPATH/src/github.com/user
 mkdir $GOPATH/src/github.com/user/hello
 vim $GOPATH/src/github.com/user/hello/hello.go

(2) Install it

	go install github.com/user/hello

(3) Run it

cd $GOPATH/bin
./hello

3. Vundle install

	mkdir ~/.vim/bundle
	git clone https://github.com/VundleVim/Vundle.vim.git 

4. Vundleの設定(~/.vimrc編集)

	set nocompatible         " be iMproved, required
	filetype off                  " required
	set rtp+=~/.vim/bundle/Vundle.vim
	call vundle#begin()
	
	Plugin 'VundleVim/Vundle.vim'
	Plugin 'fatih/vim-go'
	Plugin 'nsf/gocode', {'rtp': 'vim/'}
	
	call vundle#end()            " required
	filetype plugin indent on    " required

5. Vimで:PluginInstall実行する (Vimgoとgocodeのインストール)
GoFmt :ソースコード整形
ctrl+x,ctrl+o (順番に連発) : 自動補完機能

Read More

OpenSSHの設定ファイルを分割する

この記事はWanoグループ Advent Calendar 2016の15日目の記事です。

以前、.ssh/config を分割するという話を以前どこそこで見た時に、自分もやってみようと思って、やったやり方を紹介します。

僕の環境は、Ubuntu 16.04 なので、incron という時間ではなくて監視先のディレクトリ内のファイルの状態に応じて処理を実行できるものを利用します。
ちなみに、macOS には incron はないようです。監視して処理を実行する wait_on というコマンドはあるようですが、僕は Mac は使ってないので知りません。

まず、incron を使うためには、/etc/incron.allow にユーザー名を書く必要があるかもしれません(Ubuntu 16.04では必要でしたが、CentOS(少なくとも6系)だと多分不要)。

% cat /etc/incron.allow
ktat

実際のファイル分割のためのディレクトリ構成は以下のようにします。

 .ssh
    +- /configs/
    |   +- .base (共通の設定等をいれておく)
    |   +- host_groupA.config
    |   +- host_groupB.config
    |
    +- /backup/

incrontab -e で、以下のように登録します。

/home/ユーザー名/.ssh/configs/ IN_MODIFY,IN_CREATE,IN_DELETE,IN_MOVE /home/ユーザー名/bin/make_ssh_config $@ $#

これで、.ssh/configs/ の中のファイルが変更/作成/削除/移動されたら、即座に make_ssh_config コマンドが実行されます。
$@ は監視対象のパス(/home/ユーザー名/.ssh/configs/)に置き換わります。$#は実際に変わったファイル。
make_ssh_config は、監視対象のパスをコマンドに渡して、そこからの相対パスで処理しているので、.ssh/ の下の階層のディレクトリであれば、別にディレクトリ名は何でも良いです。

make_ssh_config は、以下です。

#!/bin/sh

target=$2

# emacs のバックアップファイルを無視
if [[ "$target" =~ "~$" ]]; then
    exit
fi

config_dir=$1
ssh_dir=$1..
log_file=$config_dir../backup/log.$(date +%Y-%m-%d-%H-%M-%S)

{
  set -e
  chmod 0600 $ssh_dir/config
  cp $ssh_dir/config $ssh_dir/backup/config.$(date +%Y-%m-%d-%H-%M)
  cat $config_dir/.base $config_dir/*.config > $ssh_dir/config.tmp
  mv $ssh_dir/config.tmp $ssh_dir/config
  chmod 0400 $ssh_dir/config
} 2>> $log_file

test -s $log_file || rm $log_file
find $config_dir../backup/ -type f -ctime +2 | xargs -r rm

やってることは、

  1. .ssh/config に書き込み権限を与える
  2. 元ファイルを backup ディレクトリにコピー
  3. ファイルを結合して、.ssh/config.tmp に書き込み
  4. .ssh/config に mv
  5. 書き込み権限を削る
  6. backup から2日以上たってるのを削除

incrontab の中にリダイレクトが書ければ、さくっと書いてしまうのですが、コマンドにしてしまった関係で多少色をつけることにしました。

これで、.ssh/configs/ 以下のファイルを編集したら、即座に .ssh/config が結合されます。権限も変更しているので間違って編集することもありません、という感じです。

Read More