はじめに
皆さんご存知、SERVERLESSフレームワーク。僕は最近どっぷりこのフレームワークに浸かっております。進化が早くてついていくのが大変です。
SERVERLESSってAPI Gateway作るよね?ってことはHTTPプロキシとしても作れるんじゃね?ってなって試してみました。
- 前編:パスやクエリストリングが決まっている場合のHTTPプロキシの作り方
- 後編:流行りの{proxy+}(オールスルー)のHTTPプロキシの作り方
こんな感じの構成で書いています。この記事は後編になります。前編で作ったServiceを書き換えて作っていきたいと思います。
実施環境は以下のとおりです。実施される方は、必要なものをインストールしてください。
- AWSアカウント
- aws-cli v1.10.47
- serverless v1.1.0
また、基本的な使い方は下記記事を参考にしてください。
SERVERLESSフレームワークv1.0正式リリース版を触ってみた
オールスルーHTTPプロキシを作る
この記事のゴール
今回のプロキシ先も、図書館の検索ができるAPI カーリルを使わせていただきます。このAPIは、/libraryで図書館検索、/checkでその図書館に指定した本が存在しているかがわかるようになっています。これを、今回作成するAPIGWからパスやクエリストリングをそのままに、カーリルへ投げてくれるようにします。
完成イメージはこんな感じ。今回はCORSの設定までしましょう(本当にCORSが効いているかは未検証です)。
具体的には、
- https://~~~~.amazonaws.com/library?appkey=hogehoge&pref=東京 のGETアクセスが来た場合
- https://~~~~.amazonaws.com/check?appkey=hogehoge&isbn=4834000826&systemid=Aomori_Pref&format=json のGETアクセスが来た場合
という感じです。これを、1つ1つパスやクエリストリングを設定することなく実現します。柔軟に対応してくれるHTTPプロキシって感じですね。
HTTPプロキシの構築
さて、今回はさっそくメイン部分を触っていきます。完成したserverless.ymlはこんな感じになります(CORSはあとで)。
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を叩いてみる
- https://[人による].execute-api.us-east-1.amazonaws.com/dev/library?appkey=[人による]&pref=沖縄
- https://[人による].execute-api.us-east-1.amazonaws.com/dev/check?appkey=[人による]&isbn=4834000826&systemid=Aomori_Pref&format=json
この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の設定は下記のように行います。
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フレームワークでは、設定内容を外部ファイルに書くことができます。書き方は以下のような形です。
frameworkVersion: "=1.1.0"
service: httpproxytest
provider:
name: aws
runtime: nodejs4.3
functions:
# 省略
resources: ${file(./resource.yml)}
Resources:
CatchAllTestResource:
# 省略
CatchAllTestMethod:
# 省略
OptionsMethod:
# 省略
こんな感じで、別ファイルにしてしまえば非常に見やすくなりますね。こうしておくと、resource.ymlファイルをJSON形式に変更してCloudFormationファイルにすることも簡単にできますし、逆にすでにあるCloudFormationファイルをSERVERLESSフレームワークで使いやすくなりますね。
まとめ
- 前編:パスやクエリストリングが決まっている場合のHTTPプロキシの作り方
- 後編:流行りの{proxy+}(オールスルー)のHTTPプロキシの作り方
として書いてきました。AWSやSERVERLESSフレームワークの進化は非常に早くてついていくのが大変ですが、開発者としては非常にワクワクします。非常に便利に早く、そして何よりバックエンド側の構築が簡単にできるおかげで、フロントエンドの開発に注力できることがなによりありがたいです。
この記事が何かの役に立てば幸いです。
おわり