API Gateway + Lambdaを用いた外部APIリクエスト中継用エンドポイントの作成 メモ


* アプリから外部APIを呼び出す際、一度API Gateway + Lambdaの中継用エンドポイントで受けて転送したいケースがあった。

  ![]()![lambda_proxy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/586535/ac5e0f9b-ecbe-a6a1-ee79-390cf7281f23.png)


* SAMで作成してみたので、メモとして残しておく。



## 事前準備

* プロジェクト作成

  ```shell
  sam init
  ```

  ※RuntimeにはPython 3.8を選択し、テンプレートには、Hello Worldテンプレートを使用。

## コード

* `swagger.json`

  * API定義

  ```json
  {
     "openapi": "3.0.0",
     "info": {
        "version": "2016-09-12T17:50:37Z",
        "title": "ProxyIntegrationWithLambda"
     },
     "paths": {
        "/{proxy+}": {
           "x-amazon-apigateway-any-method": {
              "parameters": [
                 {
                    "name": "proxy",
                    "in": "path",
                    "required": true,
                    "schema": {
                       "type": "string"
                    }
                 }
              ],
              "responses": {},
              "x-amazon-apigateway-integration": {
                 "responses": {
                    "default": {
                       "statusCode": "200"
                    }
                 },
                 "uri": "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${BypassFunction.Arn}/invocations",
                 "passthroughBehavior": "when_no_match",
                 "type": "aws_proxy"
              }
           }
        }
     }
  }
  ```

* `template.yaml`

  ```yaml
  AWSTemplateFormatVersion: '2010-09-09'
  Transform: 'AWS::Serverless-2016-10-31'
  
  Globals:
    Api:
      EndpointConfiguration: REGIONAL
      Cors: "'*'"
  
  Resources:
    BypassFunction:
      Type: AWS::Serverless::Function
      Properties:
        Handler: app.lambda_handler
        Runtime: python3.8
        CodeUri: ./hello_world/
        Events:
          Callback:
            Type: Api
            Properties:
              Path: /proxy/{proxy+}
              Method: any
              RestApiId:
                Ref: BypassAPI
    BypassAPI:
      Type: AWS::Serverless::Api
      Properties:
        StageName: Dev
        DefinitionUri: ./swagger.json
  ```

* `app.py`

  * <http://localhost:3000/proxy/oauth2/v3/certs>のようにリクエストを受け取り、proxy以降のパス部とベースパスを連結して、外部APIにリクエストを投げる。

  * 今回の場合はgoogleのjwksエンドポイントで試した。

  ```python
  import json
  import urllib.request
  base_path = "https://www.googleapis.com/"
  
  def lambda_handler(event, context):
      req_url = base_path+event["pathParameters"]["proxy"]
      req_headers = event["headers"]
      req_body = event["body"]
      if event["body"] != None:
          req = urllib.request.Request(req_url, json.dumps(req_body).encode(), headers=req_headers, method=event["requestContext"]["httpMethod"])
      else:
          req = urllib.request.Request(req_url)
      body=""
      headers=""
      code=200
      with urllib.request.urlopen(req) as res:
          body = json.load(res)
          headers = dict(res.getheaders())
          code = res.getcode()
      response = {
              'statusCode': code,
              'body': body
      }
      #Content-LengthとTransfer-Encodingが共存することになりエラーになったため、コメントアウト...
      #response["headers"] = headers
      return response
  ```

  

## 動作確認

* ビルド

  ```shell
  sam build
  ```

* 起動

  ```shell
  sam local start-api
  ```

* リクエスト

  ```http
  GET /proxy/oauth2/v3/certs HTTP/1.1
  Host: localhost:3000
  ```

* レスポンス

  ```json
  {'keys': [
          {'kid': '7483a088d4ffc00609f0e22f3c22da5fe3906ccc', 'kty': 'RSA', 'n': 'yF2BwlYB66C_-FKSk4riKizNKGjTyi2LYz_MImk95lPcW3SDpheJRghc_5iaZxpf1xAMDwHphdkEPal_kNnqLu49B7XGv9iPRUgzAEBD-w9E1CjrxzCRhM8MRVgUo7Msnydd6wOqNDKFCD1TlKpkGrUS32PB1jsu-OuwG6HCL1cxSNzP7SCDgCKecPvnpwcNarc3FzzJDraUaPX639cvvBf5PDeltBzkAIbSY0YS6RVBmZpKEDO_C1GtbADqvpz95uSdYO6H4tjUQtFwooNsAL8tv1TmLSehClFyYzaESOBfSCpxudNmPgBEzfAZM_WoZ2JukAo73Ynu3YxJOJtMTQ', 'alg': 'RS256', 'use': 'sig', 'e': 'AQAB'
          },
          {'n': '9rWgi2qEYWWMfkF7U947MGGZBx6pNVCUi9tgTKqmo-8J4MaMSnu0I2-YsyLeXh5r3ztzuk65w8egHtudxE1Q47VquJIcBs4Zn95607VCJeZxYBG3CYk1OnUWGuTjPbpXIeAlnhc-MpgUvjphE6nMEsQsxd_PTh9rj_gUdahA0xRQBj-8kOhnOoFQAO0ZV5mP5gyhTBZubQOIeKVvjZJvxZqSoSWqocndVGrVEAYMdjXbxdw8Z9Gf0NiWk3rImEjzXu65a62cJ6Fn1PjQEhohvwM0cCvKS6kq7bZ3ABJ__9VQtC6SyN0jo_jxAIVfWyVmZG-o-JIClrxjeMlRkqbbZQ', 'alg': 'RS256', 'e': 'AQAB', 'kid': '580adb0c32a175d950a1c1901c182fc17341ddc4', 'kty': 'RSA', 'use': 'sig'
          }
      ]
  }
  ```

  

## 参考情報

* [Set up Lambda proxy integrations in API Gateway](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format)
* [【初心者向け】SwaggerとAWS SAMを使ってWebAPIを簡単に作ってみた](https://dev.classmethod.jp/articles/serverless-swagger-apigateway/)