LoginSignup
24
24

More than 5 years have passed since last update.

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

Posted at

はじめに

皆さんご存知、SERVERLESSフレームワーク。僕は最近どっぷりこのフレームワークに浸かっております。進化が早くてついていくのが大変です。

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

こんな感じの構成で書いています。この記事は後編になります。前編で作ったServiceを書き換えて作っていきたいと思います。

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

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

また、基本的な使い方は下記記事を参考にしてください。
SERVERLESSフレームワークv1.0正式リリース版を触ってみた

オールスルーHTTPプロキシを作る

この記事のゴール

今回のプロキシ先も、図書館の検索ができるAPI カーリルを使わせていただきます。このAPIは、/libraryで図書館検索、/checkでその図書館に指定した本が存在しているかがわかるようになっています。これを、今回作成するAPIGWからパスやクエリストリングをそのままに、カーリルへ投げてくれるようにします。

完成イメージはこんな感じ。今回はCORSの設定までしましょう(本当にCORSが効いているかは未検証です)。
スクリーンショット 2016-11-08 16.28.49.png

具体的には、

という感じです。これを、1つ1つパスやクエリストリングを設定することなく実現します。柔軟に対応してくれるHTTPプロキシって感じですね。

HTTPプロキシの構築

さて、今回はさっそくメイン部分を触っていきます。完成したserverless.ymlはこんな感じになります(CORSはあとで)。

serverless.yml
frameworkVersion: "=1.1.0"
service: httpproxyonly
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:
        CatchAllTestResource: # {proxy+}のリソースID(Methodから参照される)
            Type: AWS::ApiGateway::Resource
            Properties:
                ParentId:
                    Fn::GetAtt:
                        - ApiGatewayRestApi # SERVERLESSフレームワークで作成されるAPI GatewayのID(だと思う)
                        - RootResourceId
                PathPart: "{proxy+}" # プロキシのエンドポイント
                RestApiId:
                    Ref: ApiGatewayRestApi
        CatchAllTestMethod: # {proxy+}のメソッドID
            Type: AWS::ApiGateway::Method
            Properties:
                AuthorizationType: NONE
                ResourceId:
                    Ref: CatchAllTestResource
                RestApiId:
                    Ref: ApiGatewayRestApi
                HttpMethod: ANY # このプロキシに入ってくるときのメソッド
                RequestParameters: # 必須かどうかの判断
                    method.request.path.proxy: false
                MethodResponses: # プロキシの呼び出し元へ返すHTTPステータスコードのうち、あり得るもの
                    - StatusCode: 200
                    - StatusCode: 400
                    - StatusCode: 403
                    - StatusCode: 500
                Integration:
                    IntegrationHttpMethod: ANY # プロキシ先に投げるときのメソッド
                    Type: HTTP_PROXY # HTTP or HTTP_PROXY
                    Uri: https://api.calil.jp/{proxy} # プロキシ先のURL(パラメータ不要)
                    RequestParameters:
                        integration.request.path.proxy: method.request.path.proxy
                    IntegrationResponses: # プロキシ先からのレスポンスHTTPステータスのマッピング
                        - StatusCode: 200
                        -
                            StatusCode: 400
                            SelectionPattern: "400" # レスポンスのHTTPステータスの正規表現
                        -
                            StatusCode: 403
                            SelectionPattern: "403"
                        -
                            StatusCode: 500
                            SelectionPattern: "500"

今回、重要なのは以下の点だと思います。

  • リソース(CatchAllTestResource)のPathPart
  • メソッド(CatchAllTestMethod)のRequestParameters(Integration内のも含む)
  • IntegrationのType
  • IntegrationのUri

順番に見ていきましょう。

PathPart

この部分は、特に難しくありません。APIGWのコンソール上で{proxy+}にする場合は2016/11/09現在ではチェックボックスをONにすれば勝手にリソースに{proxy+}となります。対して、CloudFormationではPathPartを{proxy+}と設定すれば勝手にCloudFormationが解釈してくれるようです。便利ですね。

RequestParameters

この部分は少しハマりました。

これをやり始めたときは、{proxy+}にしていればパスはいい感じにAPIGWがやってくれると思っていて、RequestParametersには何も設定しませんでした。そうしていると、例えば、http://~~~.amazonawz.com/dev/library?appkey=hogehoge&pref=沖縄にGETアクセスすると、APIGWはhttps://api.calil.jp/{proxy}?appkey=hogehoge&pref=沖縄というURLにGETリクエストしていました。これは、{proxy}という文字列がURLに入ってしまっていて、libraryが{proxy}にマッピングされていないということになります。

よって、上記(serverless.yml)のようにpathのproxyという変数をマッピングする設定を記述必要があります。

IntegrationのType

他のWebサーバへのHTTPプロキシを構築する場合、ここの設定はHTTPかHTTP_PROXYの2択になります(と思う)。

前編でも書きましたが、HTTPの場合はクエリストリングのマッピングを明示的に記述する必要があります。言い換えると、予め書いておいたクエリストリングのみマッピングしてくれるため、開発者の意図通りの動きをさせやすいです。

対してHTTP_PROXYの場合は、クエリストリングはすべてそのままのパラメータ名のまま、Webサーバへ渡してくれます。これは非常に便利ですが、例えばプロキシ先のAPIのパラメータ名を秘匿したい場合(こんなことあるのかな?)にはこれは使えないことになりそうです。

IntegrationのUri

ここには、プロキシ先のURLとして必ず{proxy}(proxy+ではない)を含めたURLにしなければなりません。APIGWのパスの{proxy+}に当たる部分が{proxy}へとマッピングされるので、それを考慮した設定をしてください。

長くなりましたが、上記serverless.ymlのように書けたら、デプロイしましょう。上に載せた画像のような設定になっているはずです(OPTIONSメソッドはまだ作られていないはず)。

APIを叩いてみる

この2つにブラウザアクセスしてみてください。1つ目はXML形式の図書館一覧、2つ目はJSON形式の書籍貸出情報らしきものが表示されていれば正しく構築できていると思います。

CORSの設定(未検証)

さて、APIをAjaxを使って叩けるようにするためにはCORSの設定が必要になります。APIGWのコンソールからCORSの設定をするのは非常に簡単で2〜3クリックでできてしまいます。しかし、しかし、しかーし、CloudFormationで構築したAPIにCORSを設定するには面倒な設定をリソースごとに書く必要があります。(Allow-Originの設定を細かくするのが正しいので、仕方ないんですけどね…。SERVERLESSフレームワークでLambdaとつなぐ際のCORSのAllow-Originを*にする場合は、trueと書くだけでいいので余計に面倒に感じてしまいます。)

CORSの設定は下記のように行います。

serverless.yml
frameworkVersion: "=1.1.0"
service: httpproxytest
provider:
    name: aws
    runtime: nodejs4.3
functions:
    # 省略

resources:
    Resources:
        CatchAllTestResource: # {proxy+}のリソースID(Methodから参照される)
            # 省略
        CatchAllTestMethod: # {proxy+}のメソッドID
            # 省略
        OptionsMethod:
            Type: AWS::ApiGateway::Method
            Properties:
                AuthorizationType: NONE
                RestApiId:
                    Ref: ApiGatewayRestApi
                ResourceId:
                    Ref: CatchAllTestResource # ここにCORSを適用したいリソースID
                HttpMethod: OPTIONS # CORSの設定はOPTIONSメソッド
                Integration:
                    IntegrationResponses:
                        -
                            StatusCode: 200
                            ResponseParameters: # MethodsやOriginは適宜変更が必要かと
                                method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token'"
                                method.response.header.Access-Control-Allow-Methods: "'DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT'"
                                method.response.header.Access-Control-Allow-Origin: "'*'"
                            ResponseTemplates:
                                application/json: ''
                    PassthroughBehavior: WHEN_NO_MATCH
                    RequestTemplates:
                        application/json: '{"statusCode": 200}'
                    Type: MOCK
                MethodResponses:
                    -
                        StatusCode: 200
                        ResponseModels:
                            application/json: 'Empty'
                        ResponseParameters:
                            method.response.header.Access-Control-Allow-Headers: false
                            method.response.header.Access-Control-Allow-Methods: false
                            method.response.header.Access-Control-Allow-Origin: false

簡単に使いたい方はコピペして使ってみてください。大変申し訳無いですが、Ajaxから叩けるようになっているのかは未検証です、ごめんなさい。どなたか報告をいただけると助かります。

番外編:Resourceを外部ファイルに書く

serverless.ymlがやたらと肥大化してしまう現実があります。嬉しいことにSERVERLESSフレームワークでは、設定内容を外部ファイルに書くことができます。書き方は以下のような形です。

serverless.yml
frameworkVersion: "=1.1.0"
service: httpproxytest
provider:
    name: aws
    runtime: nodejs4.3
functions:
    # 省略

resources: ${file(./resource.yml)}
resource.yml
Resources:
    CatchAllTestResource:
        # 省略
    CatchAllTestMethod:
        # 省略
    OptionsMethod:
        # 省略

こんな感じで、別ファイルにしてしまえば非常に見やすくなりますね。こうしておくと、resource.ymlファイルをJSON形式に変更してCloudFormationファイルにすることも簡単にできますし、逆にすでにあるCloudFormationファイルをSERVERLESSフレームワークで使いやすくなりますね。

まとめ

として書いてきました。AWSやSERVERLESSフレームワークの進化は非常に早くてついていくのが大変ですが、開発者としては非常にワクワクします。非常に便利に早く、そして何よりバックエンド側の構築が簡単にできるおかげで、フロントエンドの開発に注力できることがなによりありがたいです。

この記事が何かの役に立てば幸いです。

おわり

24
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
24
24