[アップデート] Amazon SNSのHTTP/HTTPSの配信においてContent-Typeヘッダが指定可能になりました

2023.03.24

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

初めに

昨日Amazon SNSのアップデートがありHTTP/HTTPS配信の際に、
リクエストヘッダ内のContent-Typeヘッダが指定可能となりました。

Amazon SNS announces support for setting content-type request headers for HTTP/S notifications

注意点としてはHTTPヘッダを自由に設定できる設定が追加されたというわけではなく
Content-Typeヘッダ個別の対応となります。

設定は配信ポリシーのパラメータで行います。

HTTP/S 配信ポリシーの作成

具体的にはrequestPolicyで設定します。
記事執筆時点では日本語ドキュメントの更新が追いついていないため必要であれば英語ドキュメントもご参照ください。

{
...
    "requestPolicy": {
        "headerContentType": "application/json"
    }
...
}

何が嬉しいか

アプリケーション側でコンテンツの種別をもとにした処理がしやすくなります。

利点についてはアプリケーションの仕様によりけりですが、
今回例の1つとしてPHPのWebフレームワークであるLaravelの例を見てみます。

例えば以下のコードがRouterに記載されている場合に リクエストボディが{"key": "value"}で/sns-serveに対してpostしたとします。

<?php

use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Log;
use Illuminate\Http\Request;

Route::post('/sns-serve', function (Request $request) {
    Log::debug("key parameter: " . $request->input('key'));
    return response('Hello!',200);
});

この場合Content-Typeapplication/jsonの場合Laravel側でJSONを解読してくれるため、 ログ側でkeyに対する値を出力してくれます。

[2023-03-24 05:31:44] local.DEBUG: key parameter: value

これがplain/textの場合は解釈されず値が出力されなくなってしまいます。

[2023-03-24 05:31:44] local.DEBUG: key parameter:

明確に独自のロジックとしてContent-Typeの値を解釈していなかったとしても、
フレームワークやライブラリによっては内々的には利用し良い感じに構造を読み取ってくれたりチェックしてくれるケースもあるためこの値が指定できることで実装が楽になるケースもあります。

利用できる値の制限について

When the raw message delivery is disabled for a subscription (default), or when the delivery policy is defined on the topic-level, the supported header content types are application/json and text/plain.

row配信を無効化もしくはTopicレベルで配信ポリシーが指定されている場合はtext/plainもしくはapplication/jsonのみの指定可能となります。

またこの2つ以外の値はサブスクリプション側の配信ポリシーのみで設定可能でトピック側では設定できません。  

その他の値については「配信ポリシーの作成」ページのheaderContentTypeのパラメータ部分をご確認いただければと思います。

delivery policy is definedの謎

現在delivery policy is definedに当てはまる条件は現在確認しています。

マネジメントコンソール上からトピック生成し、それに連なるサブスクリプションで編集した場合場合この条件に引っかかるのかtext/csvが指定できませんでした。

CLIからdefaultRequestPolicyを未指定で生成した場合、
作成後は未定義ではなくデフォルト値が指定し生成されている状態となりますが、
こののちにマネジメントコンソールからサブスクリプションを追加しtext/csvを指定したところエラーとなリませんでした。

$ aws sns create-topic --name from-cli-topic-1 \
  --attribute '{"DeliveryPolicy": "{\"http\":{\"defaultHealthyRetryPolicy\":{\"minDelayTarget\":20,\"maxDelayTarget\":20,\"numRetries\":3,\"numMaxDelayRetries\":0,\"numNoDelayRetries\":0,\"numMinDelayRetries\":0,\"backoffFunction\":\"linear\"},\"disableSubscriptionOverrides\":false}}"}'
{
    "TopicArn": "arn:aws:sns:ap-northeast-1:xxxxx:from-cli-topic-1"
}
# 未指定で生成しても強制的に初期値で補完されるようです
$ aws sns get-topic-attributes  --topic-arn arn:aws:sns:ap-northeast-1:xxxxxx:from-cli-topic-1
{
    "Attributes": {
        "Policy": "{\"Version\":\"2008-10-17\",\"Id\":\"__default_policy_ID\",\"Statement\":[{\"Sid\":\"__default_statement_ID\",\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"*\"},\"Action\":[\"SNS:GetTopicAttributes\",\"SNS:SetTopicAttributes\",\"SNS:AddPermission\",\"SNS:RemovePermission\",\"SNS:DeleteTopic\",\"SNS:Subscribe\",\"SNS:ListSubscriptionsByTopic\",\"SNS:Publish\"],\"Resource\":\"arn:aws:sns:ap-northeast-1:xxxx:from-cli-topic-1\",\"Condition\":{\"StringEquals\":{\"AWS:SourceOwner\":\"xxxx\"}}}]}",
        "Owner": "xxxx",
        "SubscriptionsPending": "0",
        "TopicArn": "arn:aws:sns:ap-northeast-1:xxxx:from-cli-topic-1",
        "EffectiveDeliveryPolicy": "{\"http\":{\"defaultHealthyRetryPolicy\":{\"minDelayTarget\":20,\"maxDelayTarget\":20,\"numRetries\":3,\"numMaxDelayRetries\":0,\"numNoDelayRetries\":0,\"numMinDelayRetries\":0,\"backoffFunction\":\"linear\"},\"disableSubscriptionOverrides\":false,\"defaultRequestPolicy\":{\"headerContentType\":\"text/plain; charset=UTF-8\"}}}",
        "SubscriptionsConfirmed": "0",
        "DisplayName": "",
        "DeliveryPolicy": "{\"http\":{\"defaultHealthyRetryPolicy\":{\"minDelayTarget\":20,\"maxDelayTarget\":20,\"numRetries\":3,\"numMaxDelayRetries\":0,\"numNoDelayRetries\":0,\"numMinDelayRetries\":0,\"backoffFunction\":\"linear\"},\"disableSubscriptionOverrides\":false}}",
        "SubscriptionsDeleted": "0"
    }
}

実際の動作を確認する

適当なトピックを作成したのち、それに対応するサブスクリプションを作成します。

検証にあたりサブスクリプション側の方が設定の幅が広そうであったため、
今回はトピックのポリシーではなくサブスクリプション側のポリシーを編集して試していきます。

また受信先として使うEC2を立ててContent-Typeヘッダとリクエストボディの値をログに残すようなLaravelのコードを設置しphp artisan serverで通信を受け付けておきます。

<?php

use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Log;
use Illuminate\Http\Request;

Route::post('/sns-serve', function (Request $request) {
    Log::debug("content-type value: " . $request->header("content-type"));
    Log::debug("request body: " . $request->getContent());
    return response('Hello!',200);
});

text/plainの場合

デフォルト値の配信ポリシーを設定したサブスクリプションを作成します。 またメッセージ部分が分かりやすいようにrowメッセージの配信を有効にしてます。 ポリシーは以下のようになります。

{
  "http": {
    "defaultHealthyRetryPolicy": {
      "numRetries": 3,
      "numNoDelayRetries": 0,
      "minDelayTarget": 20,
      "maxDelayTarget": 20,
      "numMinDelayRetries": 0,
      "numMaxDelayRetries": 0,
      "backoffFunction": "linear"
    },
    "disableSubscriptionOverrides": false,
    "defaultRequestPolicy": {
      "headerContentType": "text/plain; charset=UTF-8"
    }
  }
}

サブスクリプションを作成した際に利用確認のために指定した通知先に承認用の通知が送られるのでSubscribeURLにアクセスして承認しておきます。
このリクエストの時もContent-Typeの値は配信ポリシーで指定した値が利用されるようです。

[2023-03-24 03:21:22] local.DEBUG: content-type value: text/plain; charset=UTF-8
[2023-03-24 03:21:22] local.DEBUG: request body: {
  "Type" : "SubscriptionConfirmation",
  "MessageId" : "xxxx",
  "Token" : "xxxxx",
  "TopicArn" : "arn:aws:sns:ap-northeast-1:xxxxx:general-test-topic",
  "Message" : "You have chosen to subscribe to the topic arn:aws:sns:ap-northeast-1:xxxxx:general-test-topic.\nTo confirm the subscription, visit the SubscribeURL included in this message.",
  "SubscribeURL" : "https://sns.ap-northeast-1.amazonaws.com/?Action=ConfirmSubscription&TopicArn=xxxx&Token=xxxx",
  "Timestamp" : "2023-03-24T03:21:22.370Z",
  "SignatureVersion" : "1",
  "Signature" : "xxxx",
  "SigningCertURL" : "xxxx"
}

承認が終わった後マネジメントコンソールからメッセージを発行します。

[2023-03-24 04:47:43] local.DEBUG: content-type value: text/plain; charset=UTF-8
[2023-03-24 04:47:43] local.DEBUG: request body: テストデータ

文字コード部分を変えてみるとどうかと思い配信ポリシーをcharset=euc-jpに変更し再度配信しました。

UTF-8のコンソールでも正常に表示されるため内容がeuc-jpに変換されることはなくあくまでContent-Typeヘッダの付与だけにとどまっていそうです。

[2023-03-24 04:52:24] local.DEBUG: content-type value: text/plain; charset=euc-jp
[2023-03-24 04:52:24] local.DEBUG: request body: テストデータ

なお変更の適用は即時ではなく少しラグがあるようです。

何度か試しましたがほぼ即時反映されたケースもあれば5分くらい待ってようやく反映されたケースもありました。

application/jsonの場合

{
  "http": {
    "defaultHealthyRetryPolicy": {
      "numRetries": 3,
      "numNoDelayRetries": 0,
      "minDelayTarget": 20,
      "maxDelayTarget": 20,
      "numMinDelayRetries": 0,
      "numMaxDelayRetries": 0,
      "backoffFunction": "linear"
    },
    "disableSubscriptionOverrides": false,
    "defaultRequestPolicy": {
      "headerContentType": "application/json; charset=UTF-8"
    }
  }
}

同様にメッセージを送信します。 文字コード同様形式が実際にjsonであるかどうかのチェックはないためBODYがjson形式でなくても送付できます。

[2023-03-24 05:01:00] local.DEBUG: content-type value: text/plain; charset=UTF-8
[2023-03-24 05:01:00] local.DEBUG: request body: {"key": "value"}
[2023-03-24 05:01:16] local.DEBUG: content-type value: text/plain; charset=UTF-8
[2023-03-24 05:01:16] local.DEBUG: request body: plain-text

またヘッダに指定する値のチェックについてですが、
メディアタイプは制約外のものが入らないようにチェックされますがそれ以降はされていないのか、
適当に値を入れてもバリデーションチェックにひっかからず実際その値で送信されるので入力ミスには注意しましょう。

[2023-03-24 05:21:25] local.DEBUG: content-type value: text/csv; key-value
[2023-03-24 05:21:25] local.DEBUG: request body:

終わりに

もうすでに組み込んでいる環境ではないこと前提で組み込まれてるのも多く、
今すぐ誰もが嬉しいというアップデートではないもののではないものの今後の環境では嬉しいようなアップデートとなりました。

本来であればフレームワークやライブラリ側で処理できる部分を
仕方なくおまじない的に書いていた変換処理がなくすようなことも可能かと思いますので、
ぜひ機会を見て試してもらうのが良いのではないかと思います。