Help us understand the problem. What is going on with this article?

Amazon SES経由のバウンスメールをCakephp3のWebアプリで受け取る際にハマったポイント

こんにちは、テックデザインカンパニーのマイロプスでエンジニアをしています後河内です。

以前、こちらに、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については、以下の公式サイトの文書を参考にしました。

RESTful なルーティング
REST

POSTされた情報を受け取るためにやらなければいけないことは大きく二つあります。

1.config/routes.phpでREST API用のルーティングの設定
2.データを受け取るコントローラhogehogeController.phpにaddファンクションの準備

config/routes.phpでREST API用のルーティングの設定

ルーティングの設定は非常に簡単に終わりました。
公式の文書を参考にし、以下の2行を追加しただけです。

config/routes.php
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データを扱おうと考えていました。

hogehogeController.php
        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に手動でアクセスという形で認証をおこなっています。)

hogehogeController.php
        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が出てますが、こちらでも同様のことが必要になったらまたハマりそうな気がしています。
また新たな問題点等やノウハウが見つかりましたら、別の記事として書いていけたらなと思っております。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away