Melchior

バンクーバーで働くエンジニアの備忘録

Serverless + AWS Lambda + API Gatewayのヘッダマッピング

概要

AWS Lambda上でリクエストヘッダの取得とレスポンスヘッダの追加をServerless上で設定する方法がわからなくハマったので、それについてまとめました。

リクエストヘッダのマッピング

AWS Lambdaで生のリクエストヘッダを取得するためには、API Gateway上でマッピングテンプレートによりマッピングする必要があります。今回はいくつかのエンドポイントでヘッダ情報をチェックする必要があったため、こちらのドキュメントを参考に以下のように追記しました。

functions:
  hello:
    handler: header.hello
    events:
      - http:
          path: hello
          method: GET
          request: ${self:custom.request}
custom:
  request:
    template:
      application/json:
        method: $context.httpMethod,
        body : $input.json('$'),
        headers:
          #foreach($param in $input.params().header.keySet())
          $param: $util.escapeJavaScript($input.params().header.get($param))
          #if($foreach.hasNext),#end
          #end

これでヘッダ情報が取得できるようになります。

export const hello = async (event, context) => {
  cosnole.log(event.headers);
);

レスポンスヘッダのマッピング

今回、AWS Gatewayのデフォルトはapplication/jsonのようで、これをapplication/json; charset=utf-8に書き換える必要がありました。yaml上で解決したかったのですがうまく解決できず。今回はこちらを参考にコードに追記して解決しました。

export const hello = async (event, context) => ({
  statusCode: 200,
  headers: {
    'Content-Type': 'application/json; charset=utf-8',
  },
  body: JSON.stringify({
    message: 'Go Serverless v1.0! Your function executed successfully!',
    input: event,
  }),
});

共通関数の作成

以下の目的のため、共通の関数を作成しました、ソースコードこちらです。

  • 冗長なコードを減らす
  • リクエストヘッダのチェック
  • レスポンスヘッダに上記の情報を追記する
  • Serverlessの関数はasync関数を取らないといけないが、もっと自由に定義したい
  • 型推論を利用したい

header.ts -> src/funcitons/example.tsに移動し、serverless.yamlの記述を書き換えます。

functions:
  hello:
    handler: src/functions/example.hello
    events:
      - http:
          path: hello
          method: GET

ディレクトリ構造は以下のような感じです。

|--package.json
|--serverless.yml
|--src
|  |--errors.ts
|  |--functions
|  |  |--example.ts
|  |  |--util.ts
|--tsconfig.json
|--yarn.lock

次にaws-lambdaの型定義を追加します。

$ yarn add -D @types/aws-lambda

共通関数は以下のように書きました。

import { Handler, Context, APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';

import { CustomError } from '../errors';

interface HandlerResult {
  statusCode?: number; // defaultは200
  body?: string | object; // JSON.stringifyするのでなんでもOK
}

type CustomHandler = (
  event: APIGatewayProxyEvent,
  context: Context,
) => void | HandlerResult | Promise<HandlerResult>;

export const createAPIGatewayHandler = (handler: CustomHandler): Handler => async (
  event: APIGatewayProxyEvent,
  context: Context,
): Promise<APIGatewayProxyResult> => {
  try {
    // check headers

    const { statusCode = 200, body = '' } = (await handler(event, context)) || {};
    return createResponse({
      statusCode,
      body: JSON.stringify(body), // JSON.stringify(undefined)にならないようにbodyを初期化
    });
  } catch (error) {
    let { statusCode, message } = error;
    if (!(error instanceof CustomError)) {
      // unknownエラーは500エラー
      statusCode = 500;
      message = 'Internal Server Error';
    }
    return createResponse({
      statusCode,
      body: JSON.stringify({ message }),
    });
  }
};

const createResponse = (response: APIGatewayProxyResult): APIGatewayProxyResult => ({
  ...response,
  headers: {
    'Content-Type': 'application/json; charset=utf-8',
    ...response.headers,
  },
});

example.tsは以下のようになり、event, contextともに型推論が利くので型定義は不要です。

import { createAPIGatewayHandler } from './util';

export const hello = createAPIGatewayHandler(event => ({
  body: {
    message: 'Go Serverless v1.0! Your function executed successfully!',
    input: event,
  },
}));

まとめ

Lambda上でリクエストヘッダ情報のチェック・レスポンスヘッダの書き換えができるようになりました。認証のためのCustom Authorizersという機能もあるのでこちらもまたチェックしてみようと思います。

リンク