SERVERLESSフレームワークを使ってHTTPプロキシを作る【前編】

# はじめに
皆さんご存知、[SERVERLESSフレームワーク](https://serverless.com/)。僕は最近どっぷりこのフレームワークに浸かっております。進化が早くてついていくのが大変です。

SERVERLESSってAPI Gateway作るよね？ってことはHTTPプロキシとしても作れるんじゃね？ってなって試してみました。

- 前編：パスやクエリストリングが決まっている場合のHTTPプロキシの作り方
- [後編：流行りの{proxy+}（オールスルー）のHTTPプロキシの作り方](http://qiita.com/seiya_orz/items/9521a63ed1f0ceb01fa1)

を書く予定です。

実施環境は以下のとおりです。実施される方は、必要なものをインストールしてください。

- AWSアカウント
- aws-cli v1.10.47
- serverless v1.1.0

また、基本的な使い方は下記記事を参考にしてください。
[SERVERLESSフレームワークv1.0正式リリース版を触ってみた](http://qiita.com/seiya_orz/items/4bd7cc1010fafdbb8fd0)

# HTTPプロキシを作る
## この記事のゴール
プロキシ先は、図書館の検索ができるAPI [カーリル](https://calil.jp/doc/api_ref.html)を使わせていただきます。アカウントはすごく簡単に作れて、appkeyの発行も簡単でした。（そもそもこんなAPIあったんですね、何かに使えないかな…）

完成したときにできるAPI Gatewayはこんな感じです。
![スクリーンショット 2016-11-08 15.06.53.png](https://qiita-image-store.s3.amazonaws.com/0/74853/0b0ab1df-543b-8496-1490-3f6c2ddf5542.png)
今回作成するAPIGWに、GETアクセスされるとAPIGWはカーリルを叩いてくれるようにします。その際に、パスのマッピングやクエリストリングのマッピングが必要になってきます。

具体的には、

1. https://~~~~.amazonaws.com/library?appkey=hogehoge&pref=東京 のGETアクセスが来る
2. https://api.calil.jp/library?appkey=hogehoge&pref=東京 をAPIGWが投げる
3. レスポンスを受け取って呼び出し元に返す

といった感じを目指しましょう。単純ですね。

## serviceの作成
ちゃちゃっとServiceをCreateしましょう。今回はLambda使わないのでtemplateは必要ありませんが、serverless v1.1ではtemplateを指定しないとcreateできないのでひとまずnodeのテンプレートを使うことにしています。

```bash
$ sls create --template aws-nodejs --name httpproxytest 
```

1つのserviceが1つのAPI GatewayのEndpoint（https://~~~~.amazonaws.com/）に対応しています。これ以降のパスの設定は`serverless.yml`ファイルに記述します。

## エンドポイントの構築
今回、Lambdaを使わないと言っておきながら、捨てLambdaを作る必要があります。v1.1でFunctionsなしでDeployできるようになったと言っていますが、今回使いたいSERVERLESSの使い方をすることはできないようですね。

そもそもSERVERLESSフレームワークはそういう使い方をされることを想定していないと思うし、こういう使い方をしたいのであればCloudFormationで組めばいいと思います。ただ、1つのGWからLambdaへ流すのと、他のWebサーバへ流す、2つの流し方を共存させたい場合には今回の記事は役に立ちそうです。実際、オンプレAPIをまるごと置き換えるのは難しいので、前段にAPIGWを立てて、機能追加はLambdaでやり、徐々に置き換えるとかは有り得そうですね、なんとなく。

まあ今回はSERVERLESSフレームワークで頑張るということで、ひとまずこんな感じにしましょう。

```yaml:serverless.yml
frameworkVersion: "=1.1.0"
service: httpproxytest

provider:
    name: aws
    runtime: nodejs4.3

functions:
  hello:
    handler: handler.hello
    events:
        - http:
            path: trash
            method: get
```

捨てLambdaの名前はtrashとしています。これで、デプロイしちゃいましょう。

```bash
$ sls deploy
Serverless: Deprecation Notice: Starting with the next update, we will drop support for Lambda to implicitly create LogGroups. Please remove your log groups and set "provider.cfLogs: true", for Cl
oudFormation to explicitly create them for you.
Serverless: Packaging service…
Serverless: Uploading CloudFormation file to S3…

Serverless: Uploading service .zip file to S3…
Serverless: Updating Stack…
Serverless: Checking Stack update progress…
..........................
Serverless: Stack update finished…
Serverless: Removing old service versions…

Service Information
service: httpproxyonly
stage: dev
region: us-east-1
api keys:
  None
endpoints:
  GET - https://hogehoge.execute-api.us-east-1.amazonaws.com/dev/trash
functions:
  httpproxytest-dev-hello: arn:aws:lambda:us-east-1:xxxxxxxxxx:function:httpproxytest-dev-hello
```

こんな感じになればデプロイ成功です。

## HTTPプロキシ構築
さて、ここからが本番です。かなりCloudFormationの知識が必要になってきます。というかserverless.ymlにYAML形式でHTTPプロキシを作るCloudFormationを書くと言ってもいいです。僕の場合、今回始めてCloudFormationを書いたので、なかなか苦労しました。

書くとこんな感じになります。

```yaml:serverless.yml
frameworkVersion: "=1.1.0"
service: httpproxytest
provider:
    name: aws
    runtime: nodejs4.3
functions:
  hello:
    handler: handler.hello
    events:
        - http:
            path: trash
            method: get

# resourcesはCloudFormationをYAMLで書く感じ。
# ここにAPI GatewayをHTTPプロキシとして作る設定を書きます。
resources:
    Resources:
        ProxyTestResource: # libraryのリソースID（Methodから参照される）
            Type: AWS::ApiGateway::Resource
            Properties:
                ParentId:
                    Fn::GetAtt:
                        - ApiGatewayRestApi # SERVERLESSフレームワークで作成されるAPI GatewayのID（だと思う）
                        - RootResourceId # API Gateway コンソールの/のこと（だと思う）
                PathPart: library # プロキシのエンドポイント
                RestApiId:
                    Ref: ApiGatewayRestApi
        ProxyTestMethod: # libraryのメソッドID
            Type: AWS::ApiGateway::Method
            Properties:
                AuthorizationType: NONE
                ResourceId:
                    Ref: ProxyTestResource
                RestApiId:
                    Ref: ApiGatewayRestApi
                HttpMethod: GET # このプロキシに入ってくるときのメソッド
                RequestParameters: # 必須かどうかの判断、trueだと必須、falseだと必須ではないが存在する場合はマッピングする
                    method.request.querystring.appkey: true
                    method.request.querystring.pref: true
                MethodResponses: # プロキシの呼び出し元へ返すHTTPステータスコードのうち、あり得るもの
                    - StatusCode: 200
                    - StatusCode: 400
                    - StatusCode: 403
                    - StatusCode: 500
                Integration:
                    IntegrationHttpMethod: GET # プロキシ先に投げるときのメソッド
                    Type: HTTP  # HTTP or HTTP_PROXY クエリストリングをそのままプロキシ先に投げる場合はHTTP_PROXY
                    Uri: https://api.calil.jp/library # プロキシ先のURL（パラメータ不要）
                    RequestParameters:
                        integration.request.querystring.appkey: method.request.querystring.appkey
                        integration.request.querystring.pref: method.request.querystring.pref
                    IntegrationResponses: # プロキシ先からのレスポンスHTTPステータスのマッピング
                        - StatusCode: 200
                        -
                            StatusCode: 400
                            SelectionPattern: "400" # レスポンスのHTTPステータスの正規表現
                        -
                            StatusCode: 403
                            SelectionPattern: "403"
                        -
                            StatusCode: 500
                            SelectionPattern: "500"
```

基本的にはCloudFormationを勉強して、コメントを見ていただければ理解できると思います。

解説をすると、ApiGatewayRestApiはSERVERLESSフレームワークによって構築されるAPIGW自体のIDを指している（はずです）。そこに、リソース（コンソール上での/library）やメソッド（コンソール上でのGET）を設定していきます。

ややこしいのはメソッドの方だと思いますが、これはコンソール上のメソッドリクエスト・統合リクエスト・統合レスポンス・メソッドレスポンスを作っています。

RequestParametersはメソッドリクエストの設定です。先に話す、IntegrationのTypeがHTTPの場合、クエリストリングのマッピングを明示してやる必要があります。ですので、RequestParametersで書いておいてあげないとIntegrationでマッピングをしてくれなくなります（というかCloudFormationのエラーでデプロイできない）。

次に、MethodResponseですが、これはその名の通り、メソッドレスポンスの設定です。呼び出し元へ返すHTTPステータスで、返す可能性があるものを列挙しておく必要があります。

次に、Integrationですが、ここでは統合リクエスト・レスポンスの設定を行います。ここのTypeをHTTP_PROXYにすると、クエリストリングを自動的にマッピング（そのままの名前でマッピング）してくれますが、詳しくは後編で取り扱います。ここのRequestParametersで、クエリストリングのマッピングを実際に行います。IntegrationResponseは、プロキシ先のHTTPステータスがどういう場合にどのHTTPステータスを返すかを設定します。ここで少しハマったのが、SelectionPatternで書いた正規表現の検証対象は、**プロキシ先から返却されたレスポンスボディではなく、返却されたHTTPステータスコード**です。これは、APIGWが他のWebサーバへのプロキシとして使う場合の設定ですが、Lambdaの場合は正規表現の検証対象がレスポンスボディになっていることに注意です。

長くなりましたが、こんな感じでHTTPプロキシが作れます。デプロイしましょう。すると、上に載せた画像のような状態になっているはずです。

## APIを叩いてみる
デプロイされたAPIを叩いてみましょう。

[https://[人による].execute-api.us-east-1.amazonaws.com/dev/library?appkey=[人による]&pref=沖縄](https://[人による].execute-api.us-east-1.amazonaws.com/dev/library?appkey=[人による]&pref=沖縄)

これにアクセスしてみてください。人による部分は置き換えてください。

すると沖縄県の図書館一覧がxmlで表示されていると思います。このAPI、小学校の図書室まで図書館として扱っているんですね、すごい。

# まとめ
SERVERLESSフレームワークを使って、HTTPプロキシを作ってみたでした。上でも書きましたが、1つのGWからLambdaへ流すのと、他のWebサーバへ流す、2つの流し方を共存させたい場合には今回の記事は役に立ちそうです。

後編では、SERVERLESSフレームワークを使って、流行り？の{proxy+}（オールスルー）のHTTPプロキシの作り方を書きたいと思います。

[後編：流行りの{proxy+}（オールスルー）のHTTPプロキシの作り方](http://qiita.com/seiya_orz/items/9521a63ed1f0ceb01fa1)

おわり