注目の記事

[アップデート]LambdaがHTTPSエンドポイントから実行可能になる、AWS Lambda Function URLsの機能が追加されました!

LambdaにHTTPSエンドポイントを追加して、Webフックみたいな使い方をすることができるようになる便利なアップデートです! Lambdaを使ったWebフックが作りやすくなって、かんたんに設定できるのでぜひお試しを!
2022.04.07

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

Lambdaに便利なアップデートが来ました!

LambdaにHTTPSエンドポイントを追加して、Webフックみたいな使い方をすることができるようになるアップデートです!

これで、ちょっとLambdaをHTTPS経由で実行したいなー。なんて時に、API Gatewayを使わずにLambdaにHTTPSエンドポイントを追加するだけで実行できるようになって超便利です!

早速試してみました。

費用について

費用については、Lambdaの実行費用が通常通りかかります。 Function URLsの機能を追加することによる追加の費用はありません。

試してみた

まずは、シンプルなLambdaから試してみます。

次のような、Node.js 14.xランタイムのシンプルなLambdaを作成します。

index.js

exports.handler = async (event) => {
    return 'Hello, World!!';
};

設定タブに「関数 URL」という項目が増えているので、選択して新しい関数URLを作成します。

認証タイプに「NONE」を選んで、保存すれば関数URLを作成できます。

これだけで、HTTPSエンドポイントから実行可能なLambdaが作成できました!

curl コマンドを使ってHTTPSエンドポイントへアクセスすると、Lambdaの実行結果が返ってくることが確認できます。

$ export FUNCTION_URL=<<Lambdaの関数URLをコピー&ペースト>>
$ curl ${FUNCTION_URL}
Hello, World!!

パラメーターを送ってみる

LambdaのHTTPSエンドポイントでは、GETメソッドでクエリストリングやPOSTメソッドでパラメーターを送ることもできます。

クエリストリングは、Lambdaが受け取るeventの内、 event.queryStringParameters に格納されます。 よって、Lambdaを次のように形にすると、クエリストリングを返すようなLambdaが作れます。

index.js

exports.handler = async (event) => {
    return JSON.stringify(event.queryStringParameters);
};
$ curl "${FUNCTION_URL}?key1=value1&key2=value2&key3=value3"
{"key1":"value1","key2":"value2","key3":"value3"}

POSTメソッドで送られたデータは、Lambdaが受け取るeventの内、 event.body に格納されます。 よって、Lambdaを次のような形にすると、POSTで送ったデータをそのまま返すようなLambdaが作れます。

index.js

exports.handler = async (event) => {
    return JSON.stringify(event.body);
};
$ curl -X POST \
    -H 'Content-Type: application/json' \
    -d '{ "example": "test" }' \
    ${FUNCTION_URL} 
"{ \"example\": \"test\" }"

レスポンスをカスタムしてみる

先程、かんたんにLambdaのreturn値を Hello, World!! という文字列にしましたが、 この場合HTTPSエンドポイントのレスポンスを次のようなデフォルト値としています。

  • statusCode: 200
  • content-type: application/json.
  • body: return値(Hello, World!!)
  • isBase64Encoded: false

curlコマンドでレスポンスヘッダーも表示するとこんな感じになります。

$ curl -i ${FUNCTION_URL}   
HTTP/1.1 200 OK
Date: Thu, 07 Apr 2022 00:50:03 GMT
Content-Type: application/json
Content-Length: 14
Connection: keep-alive
x-amzn-RequestId: xxxxxxxx-1234-1234-1234-xxxxxxxxxxxx
X-Amzn-Trace-Id: root=1-xxxxxxxx-123456781234567812345678;sampled=0

Hello, World!!

レスポンスとなるステータスコードや、Content-Typeはカスタムすることが可能です。

次のようにLambdaのreturn値にステータスコードやヘッダーを含めることで、デフォルト値ではないレスポンスにカスタムできます。

index.js

exports.handler = async (event) => {
    return {
        "statusCode": 201,
        "headers": {
            "Content-Type": "text/plain",
            "My-Custom-Header": "Custom Value"
        },
        "body": JSON.stringify({
            "message": "Hello, world!"
        }),
        "isBase64Encoded": false
    };
};
curl -i ${FUNCTION_URL}
HTTP/1.1 201 Created
Date: Thu, 07 Apr 2022 01:01:09 GMT
Content-Type: text/plain
Content-Length: 27
Connection: keep-alive
x-amzn-RequestId: xxxxxxxx-1234-1234-1234-xxxxxxxxxxxx
my-custom-header: Custom Value
X-Amzn-Trace-Id: root=1-xxxxxxxx-123456781234567812345678;sampled=0

{"message":"Hello, world!"}

レスポンスにcookieを追加したい場合は、 set-cookie ヘッダーを自分で追加しないようにしてください。 ドキュメントに明記されています。

代わりに、以下のようにreturn値に cookies オブジェクトを追加すると、Lambdaが自動的に解釈してレスポンスに set-cookie ヘッダーを追加します。

index.js

exports.handler = async (event) => {
    return {
        "statusCode": 201,
        "headers": {
            "Content-Type": "application/json",
            "My-Custom-Header": "Custom Value"
        },
        "body": JSON.stringify({
            "message": "Hello, world!"
        }),
        "cookies": [
            "Cookie_1=Value1; Expires=21 Oct 2021 07:48 GMT",
            "Cookie_2=Value2; Max-Age=78000"
        ],
        "isBase64Encoded": false
    };
};
$ curl -i ${FUNCTION_URL}
HTTP/1.1 201 Created
Date: Thu, 07 Apr 2022 01:34:53 GMT
Content-Type: application/json
Content-Length: 27
Connection: keep-alive
x-amzn-RequestId: xxxxxxxx-1234-1234-1234-xxxxxxxxxxxx
my-custom-header: Custom Value
Set-Cookie: Cookie_1=Value1; Expires=21 Oct 2021 07:48 GMT
Set-Cookie: Cookie_2=Value2; Max-Age=78000
X-Amzn-Trace-Id: root=1-xxxxxxxx-123456781234567812345678;sampled=0

{"message":"Hello, world!"}

CORSを有効にしてみる

デフォルトだとCORSが非有効であるため、ブラウザ上のjavascriptから動的にアクセスといったことをしようとすると、CORSエラーが発生してしまいます。 試しにやってみましょう。

Google Chromeの開発者ツールを使って、ブラウザ上からXMLHttpRequestで動的にFunction URLへアクセスしてみると、このようにCORSエラーが発生して実行に失敗しています。

let functionUrl = <<Lambdaの関数URLをコピー&ペースト>>
let xhr = new XMLHttpRequest()
xhr.open("GET", functionUrl, false)
xhr.send()

関数URLの編集画面から、CORSの設定をします。

今、 https://dev.classmethod.jp の画面からjavascriptで動的にアクセスしようとしているので、許可オリジンに https://dev.classmethod.jp を追加します。 GETメソッドでアクセスしようとしているので、許可メソッドに GET を選択して保存します。

これでCORSの設定ができたので、再度javascriptから動的にアクセスするとCORSのエラーが発生しなくなり、問題なくアクセスすることができました。

IAM認証をかけてみる

認証タイプに「NONE」を選んだため、誰でもアクセス可能であるパブリックなエンドポイントを作りましたが、 IAM認証をかけてアクセス制限をかけることもできます。

関数URLの設定画面で、認証タイプに「AWS_IAM」を選択するとIAM認証をかけることができます。

認証タイプを変更した後に curl コマンドでアクセスしようとするとエラーとなり、アクセスできなくなっていることがわかります。

$ curl -i ${FUNCTION_URL}
HTTP/1.1 403 Forbidden
Date: Thu, 07 Apr 2022 02:15:58 GMT
Content-Type: application/json
Content-Length: 23
Connection: keep-alive
x-amzn-RequestId: xxxxxxxx-1234-1234-1234-xxxxxxxxxxxx
x-amzn-ErrorType: AccessDeniedException

{"Message":"Forbidden"}

AWS_IAMで認証をかける場合、アクセスするためにはAWS Signature Version 4 (SigV4)を使用して、HTTPリクエストに対して署名する必要があります。

今回は、 awscurl というツールを使ってアクセスしてみます。

pip を使って次のようにインストールできます。

$ pip install awscurl

後は、AWS CLIが使えるように ~/.aws/credentials~/.aws/config が設定できていれば、curlのようにコマンドを実行するだけでAWS SigV4の署名付きリクエストにしてくれます。

$ export FUNCTION_URL=<<Lambdaの関数URLをコピー&ペースト>>
$ awscurl --service lambda \
    --region ap-northeast-1 \
    ${FUNCTION_URL}
{"message":"Hello, world!"}

AWS IAMによる制限がかかっているため、AWS SigV4の署名をしたとしても、Function URLの実行権限(lambda:InvokeFunctionUrl)が無いと、以下のようなエラーが発生します。

$ awscurl -i --service lambda \
    --region ap-northeast-1 \
    ${FUNCTION_URL}
{'Date': 'Thu, 07 Apr 2022 02:41:57 GMT', 'Content-Type': 'application/json', 'Content-Length': '23', 'Connection':
 'keep-alive', 'x-amzn-RequestId': 'xxxxxxxx-1234-1234-1234-xxxxxxxxxxxx', 'x-amzn-ErrorType': 'AccessDeniedExcepti
on'}

{"Message":"Forbidden"}
Traceback (most recent call last):
  File "/usr/local/bin/awscurl", line 8, in <module>
    sys.exit(main())
  File "/usr/local/lib/python3.9/site-packages/awscurl/awscurl.py", line 521, in main
    inner_main(sys.argv[1:])
  File "/usr/local/lib/python3.9/site-packages/awscurl/awscurl.py", line 515, in inner_main
    response.raise_for_status()
  File "/usr/local/lib/python3.9/site-packages/requests/models.py", line 943, in raise_for_status
    raise HTTPError(http_error_msg, response=self)
requests.exceptions.HTTPError: 403 Client Error: Forbidden for url: https://xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.lambda
-url.ap-northeast-1.on.aws/

注意点

Function URLに対して、急激なアクセスがあると、スロットリングが発生して実行エラーとなる可能性があります。

Function URLの1秒あたりの最大リクエストレート(RPS)は、設定されたreserved concurrencyの10倍と同等になります。 たとえば、reserved concurrencyを100としていた場合、最大RPSは1000になります。 もし、スロットリングが発生するようであれば、reserved concurrencyの設定値を見直してください。

終わりに

Lambdaの新しい機能、AWS Lambda Function URLsを紹介いたしました!

これでLambdaを使ったWebフックが更に作りやすくなったと思います。 かんたんに設定できるので、ぜひ試してみてください!