Amazon.Lambda.AspNetCoreServer を使ったときに、HTTP リクエストコンテキストを取得してみた

2022.07.18

いわさです。

Lambda 関数では、イベント情報を受信して、そこからHTTPリクエスト情報などを取得することが出来ます。
通常の Lambda 関数であればハンドラの引数の専用オブジェクトへアクセスすれば良さそうです。

一方で、前回 ASP.NET Core の標準実装した Web API をそのまま Lambda 関数としてデプロイする方法をご紹介しました。

デフォルトテンプレートではリクエスト情報などへのアクセスはしていないシンプルな Hello World でした。
この場合、HTTPリクエスト情報などはどのように取得したら良いでしょうか。

Amazon.Lambda.AspNetCoreServer を使わない Lambda

まずは、Amazon.Lambda.AspNetCoreServerを使用しない、Lambda 関数にあわせたハンドラを使った場合をおさらいします。
冒頭に引用した前回の記事で、Amazon.Lambda.Templatesを導入する方法をご紹介していました。
そのテンプレートのひとつにEmpty Top-level Functionというものがありますので、こちらを使ってみましょう。

% dotnet new lambda.EmptyTopLevelFunction
テンプレート "Empty Top-level Function" が正常に作成されました。

ハンドラはモジュールを指していますが、LambdaBootstrapBuilderによって内部でハンドラを生成して渡されています。
以下ではデフォルトinputがstringだとエラーが発生したため、objectに変更しています。

Function.cs

using Amazon.Lambda.Core;
using Amazon.Lambda.RuntimeSupport;
using Amazon.Lambda.Serialization.SystemTextJson;

// The function handler that will be called for each Lambda event
var handler = (object input, ILambdaContext context) =>
{
    LambdaLogger.Log(context.ClientContext.ToString());
    LambdaLogger.Log(input.ToString());
    return "success";
};

// Build the Lambda runtime client passing in the handler to call for each
// event and the JSON serializer to use for translating Lambda JSON documents
// to .NET types.
await LambdaBootstrapBuilder.Create(handler, new DefaultLambdaJsonSerializer())
        .Build()
        .RunAsync();

上記をデプロイし、API Gateway などからHTTPリクエストを中継すると、inputの中身は以下のようになります。
ここではリクエスト時にhogeヘッダーを付与しています。

2022-07-17T22:45:41.587Z    1185bb86-a4ef-4bf3-8d2e-70cf1721478f    {
    "version": "2.0",
    "routeKey": "ANY /hoge0717lambdadotnet",
    "rawPath": "/hoge0717lambdadotnet",
    "rawQueryString": "",
    "headers": {
        "accept": "*/*",
        "content-length": "0",
        "hoge": "111",
        "host": "2sewhgz0sb.execute-api.ap-northeast-1.amazonaws.com",
        "user-agent": "curl/7.79.1",
        "x-forwarded-for": "",
        "x-forwarded-port": "443",
        "x-forwarded-proto": "https"
    },
    "requestContext": {
        "accountId": "",
        "apiId": "2sewhgz0sb",
        "domainName": "2sewhgz0sb.execute-api.ap-northeast-1.amazonaws.com",
        "domainPrefix": "2sewhgz0sb",
        "http": {
            "method": "GET",
            "path": "/hoge0717lambdadotnet",
            "protocol": "HTTP/1.1",
            "sourceIp": "",
            "userAgent": "curl/7.79.1"
        },
        "requestId": "VbubUjdXtjMEMdg=",
        "routeKey": "ANY /hoge0717lambdadotnet",
        "stage": "$default",
        "time": "17/Jul/2022:22:45:41 +0000",
        "timeEpoch": 1658097941004
    },
    "isBase64Encoded": false
}

このように、Lambda 用のリクエストオブジェクトを解析して必要なリクエストコンテキストを取得していたかと思います。

Amazon.Lambda.AspNetCoreServer を使う Lambda

では、Amazon.Lambda.AspNetCoreServerを使うとどうなるでしょうか。
Amazon.Lambda.AspNetCoreServerを使う場合は Lambda 用にリファクタリングせずに標準の Web API をそのまま Lambda 化出来るのがメリットなので、Lambda 専用の実装はできれば避けたいところでしょう。

そして、Amazon.Lambda.AspNetCoreServerは標準実装と Lambda 実装(API Gateway / Application Load Balancer) の間をうまいことやってくれる思想なので、コンテキストまわりもうまいことやってくれる予感がしますね。
ということで、ASP.NET Core 標準の方法でヘッダー情報へアクセスしてみましょう。

こちらも、Amazon.Lambda.Templatesから Minimal API を作成します。

% dotnet new serverless.AspNetCoreMinimalAPI
テンプレート "Lambda ASP.NET Core Minimal API" が正常に作成されました。

マッピングされたラムダ式の引数にまずはシンプルにHttpRequestを指定します。
なお、実装方法は以下公式ドキュメントの「パラメータバインド」を参考にしています。

Program.cs

:
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();

app.MapGet("/", (HttpRequest request) => 
{
    return request.Headers["x-hoge"][0];
});

app.Run();

Minimal API で Lambda テンプレートを作成すると、SAMテンプレートも自動生成されます。
そして、dotnet lambda deploy-serverlessを実行すると API Gateway 含めてデプロイされます。

% dotnet lambda deploy-serverless --profile hoge
Amazon Lambda Tools for .NET Core applications (5.4.4)
Project Home: https://github.com/aws/aws-extensions-for-dotnet-cli, https://github.com/aws/aws-lambda-dotnet

:

Output Name                    Value                                             
------------------------------ --------------------------------------------------
ApiURL                         https://3nflaanqkk.execute-api.ap-northeast-1.amazonaws.com/Prod/

アクセスしてみましょう。

% curl https://3nflaanqkk.execute-api.ap-northeast-1.amazonaws.com/Prod/ -H "x-hoge:hogefuga"
hogefuga

おお、うまいこと使えますね!!

直接パラメータバインドも出来るか試してみます。
まぁここまで出来れば出来そうだなという感じではあります。

Program.cs

using Microsoft.AspNetCore.Mvc;

var builder = WebApplication.CreateBuilder(args);

:

app.MapGet("/", ([FromHeader(Name = "x-hoge")] string hoge) => 
{
    return hoge;
});

app.Run();
% curl https://3nflaanqkk.execute-api.ap-northeast-1.amazonaws.com/Prod/ -H "x-hoge:hogehoge"
hogehoge

おお、こちらもいけました。すごい。

さいごに

本日は、Amazon.Lambda.AspNetCoreServerを使って ASP.NET Core Web API を Lambda 関数としてデプロイした時に、リクエストコンテキストを取得してみました。
結果としては、Lambda でホスティングされていることを意識せずにHTTPリクエストへアクセスする実装を行うことが出来ました。

これはとても良いですね。
Lambda 用にリファクタリングせずに ASP.NET Core Web API を サーバーレス環境でホスティング出来そうです。
まだオーバーヘッド部分などを無視しているので、色々考慮事項などは出てきそうな気がしますが、.NET も Lambda でのメジャーな選択肢として考えても良さそうな気がしますね。

なお、色々うまいこと抽象化してくれて使いやすくなってますが、Lambda のコンテキストにもアクセスすることは可能です。
使わないほうが綺麗な実装になる気はしますが、アクセス出来なくも無いということだけ覚えておきたいです。

The original lambda request object and the ILambdaContext object can be accessed from the HttpContext.Items collection.

AWS Toolkit for Visual Studio の無い環境だと、.NET on Lambda に対する敷居が少し高かったのですが、今は以下の2つのコマンドだけで Lambda に依存しない .NET な Web API をすぐデプロイ出来るのでかなりお手軽です。.NET 使いの方は是非試していただきたいですね。

  1. dotnet new serverless.AspNetCoreMinimalAPI
  2. dotnet lambda deploy-serverless