こんにちは、**テックデザインカンパニーのマイロプス**でエンジニアをしています後河内です。
以前、こちらに、Amazon SES経由のバウンスメールをWebアプリで受け取るという記事を書きました。
こちらの記事ではCakePHP2でのバウンスメールの受け取り方法を書かせていただきましたが、同様のことをCakePHP3のWebアプリで実施する必要が別の案件で実施したいという要望が出てきました。
当初はCakePHP2で一度実装したことなので、CakePHP3でも簡単にできると思ったのですが、その実装の中でハマった点がありましたので、備忘録もかねてそのことについて書いていきます。
バウンス情報をWEBアプリにPOSTするところまでは、以前の記事、Amazon SES経由のバウンスメールをWebアプリで受け取るの『SNSでバウンス情報をPOSTする』と特に変わりありません。
以前との違いは、POSTされた情報の受け取り方になります。
POSTされた情報を受け取るために前回はCakePHP2でREST APIの実装をしましたが、今回はCakePHP3でREST APIの実装が必要になりました。
CakePHP3でREST API
POSTされた情報を受け取るためのCakePHP3でのREST APIについては、以下の公式サイトの文書を参考にしました。
POSTされた情報を受け取るためにやらなければいけないことは大きく二つあります。
1._config/routes.php_でREST API用のルーティングの設定
2.データを受け取るコントローラ_hogehogeController.php_にaddファンクションの準備
_config/routes.php_でREST API用のルーティングの設定
ルーティングの設定は非常に簡単に終わりました。
公式の文書を参考にし、以下の2行を追加しただけです。
Router::scope('/', function ($routes) {
// 3.5.0 より前は `extensions()` を使用
$routes->setExtensions(['json']);
$routes->resources('Recipes');
});
データを受け取るコントローラ_hogehogeController.php_にaddファンクションの準備
ルーティングの設定を行い、下記のようなURLにPOSTすると
https://hogehoge.jp/hogehoge/
_hogehogeController.php_のaddファンクションが呼ばれることになります
また公式の文書によると
REST アプリケーションの場合、様々なフォーマットのデータを扱います。 CakePHP では、 RequestHandlerComponent クラスが助けてくれます。 デフォルトでは、POST や PUT で送られてくる JSON/XML の入力データはデコードされ、 配列に変換されてから $this->request->getData() に格納されます
とのことでしたので、jsonデータであれば_$this->request->getData()_にデータが自動で格納されていることになり、その中身からバウンスメールのメールアドレス情報を抜き取れると考えました。
そのため、当初は以下のようなコードでPOSTデータを扱おうと考えていました。
public function add()
{
if ($this->request->is('post')) {
$this->data=$this->request->getData();
}
}
しかし実際には、_$this->request->getData()_の中身は空っぽでした。
Amazon SNS メッセージおよびJSON形式を見る限り下のようなjson情報を送ってるので、json情報は自動格納されるのではないかと、悩みました。
POST / HTTP/1.1
x-amz-sns-message-type: SubscriptionConfirmation
x-amz-sns-message-id: 165545c9-2a5c-472c-8df2-7ff2be2b3b1b
x-amz-sns-topic-arn: arn:aws:sns:us-west-2:123456789012:MyTopic
Content-Length: 1336
Content-Type: text/plain; charset=UTF-8
Host: myhost.example.com
Connection: Keep-Alive
User-Agent: Amazon Simple Notification Service Agent
{
"Type" : "SubscriptionConfirmation",
"MessageId" : "165545c9-2a5c-472c-8df2-7ff2be2b3b1b",
"Token" : "2336412f37...",
"TopicArn" : "arn:aws:sns:us-west-2:123456789012:MyTopic",
"Message" : "You have chosen to subscribe to the topic arn:aws:sns:us-west-2:123456789012:MyTopic.\nTo confirm the subscription, visit the SubscribeURL included in this message.",
"SubscribeURL" : "https://sns.us-west-2.amazonaws.com/?Action=ConfirmSubscription&TopicArn=arn:aws:sns:us-west-2:123456789012:MyTopic&Token=2336412f37...",
"Timestamp" : "2012-04-26T20:45:04.751Z",
"SignatureVersion" : "1",
"Signature" : "EXAMPLEpH+DcEwjAPg8O9mY8dReBSwksfg2S7WKQcikcNKWLQjwu6A4VbeS0QHVCkhRS7fUQvi2egU3N858fiTDN6bkkOxYDVrY0Ad8L10Hs3zH81mtnPk5uvvolIC1CXGu43obcgFxeL3khZl8IKvO61GWB6jI9b5+gLPoBc1Q=",
"SigningCertURL" : "https://sns.us-west-2.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem"
}
いろいろ調査し、実験していく中でわかったことは、CakePHP3は、POSTされた情報のヘッダー情報のコンテンツタイプをみてjsonかどうか判断しているようでした。今回SNSからPOSTされる情報のヘッダー情報のコンテンツタイプは_Content-Type: text/plain_だっためjsonと判定してなかったようです。
_Content-Type: application/json_にして同じようなデータをPOSTしたところ、$this->request->getData()に情報が自動格納されました。
ただし、SNSからPOSTされる情報のヘッダー情報はこちらで変えることはできないので、json以外の情報の受け取り方を調査したところ、
$this->request->input()
で扱えることがわかりました。
_$this->request->input()_内のデータをjson_decode関数で配列化することで、バウンスメールのメールアドレス情報を抜き取ることができました。
以下にPOSTされた情報を受け取り、バウンスメールのメールアドレス部分を抜き取るコードを記載します。
(なお最初にPOST先としての認証が必要ですが、最初にPOSTされたデータの中身を確認し、SubscribeURLに手動でアクセスという形で認証をおこなっています。)
public function add()
{
if ($this->request->is('post')) {
$this->data=json_decode($this->request->input(),true);
//受け取ったJSON情報からバウンスメールのメールアドレス部分を抜き取る
if(isset($this->data['Message'])){
$this->bounce=json_decode($this->data['Message'],true);
//バウンスされたメールアドレス
$mailaddress = $this->bounce['bounce']['bouncedRecipients'][0]['emailAddress'];
}
}
今回のハマりポイント
- CakePHP3のREST APIは、POSTされる情報のヘッダー情報のコンテンツタイプをみてjsonかどうか判断している
- Amazon SNSでPOSTされる情報のヘッダー情報のコンテンツタイプは_Content-Type:application/json_ではなく_Content-Type:text/plain_
すでにCakePHP4が出てますが、こちらでも同様のことが必要になったらまたハマりそうな気がしています。
また新たな問題点等やノウハウが見つかりましたら、別の記事として書いていけたらなと思っております。