Note: I'm migrating from gonzalo123.com to here. When I finish I'll swap the DNS to here. The "official" blog will be always gonzalo123.com

      Handling Amazon SNS messages with PHP, Lumen and CloudWatch

      This days I’m involve with Amazon’s AWS and since I am migrating my backends to Lumen I’m going to play a little bit with AWS and Lumen. Today I want to create a simple Lumen server to handle SNS notifications. One end-point to listen to SNS and another one to emit notifications. I also want to register logs within CloudWatch. Let’s start.

      First the Lumen server.

      use Laravel\Lumen\Application;
       
      require __DIR__ . '/../vendor/autoload.php';
       
      (new Dotenv\Dotenv(__DIR__ . "/../env"))->load();
       
      $app = new Application();
       
      $app->register(App\Providers\LogServiceProvider::class);
      $app->register(App\Providers\AwsServiceProvider::class);
       
      $app->group(['namespace' => 'App\Http\Controllers'], function (Application $app) {
          $app->get("/push", "SnsController@push");
          $app->post("/read", "SnsController@read");
      });
       
      $app->run();
      

      As we can see there’s a route to push notifications and another one to read messages.

      To work with SNS I will create a simple service provider

      namespace App\Providers;
       
      use Illuminate\Support\ServiceProvider;
      use Aws\Sns\SnsClient;
       
      class AwsServiceProvider extends ServiceProvider
      {
          public function register()
          {
              $awsCredentials = [
                  'region'      => getenv('AWS_REGION'),
                  'version'     => getenv('AWS_VERSION'),
                  'credentials' => [
                      'key'    => getenv('AWS_CREDENTIALS_KEY'),
                      'secret' => getenv('AWS_CREDENTIALS_SECRET'),
                  ],
              ];
       
              $this->app->instance(SnsClient::class, new SnsClient($awsCredentials));
          }
      }
      

      Now We can create the routes in SnsController. Sns has a confirmation mechanism to validate endpoints. It’s well explained here

      namespace App\Http\Controllers;
       
      use Aws\Sns\SnsClient;
      use Illuminate\Http\Request;
      use Laravel\Lumen\Routing\Controller;
      use Monolog\Logger;
       
      class SnsController extends Controller
      {
          private $request;
          private $logger;
       
          public function __construct(Request $request, Logger $logger)
          {
              $this->request = $request;
              $this->logger  = $logger;
          }
       
          public function push(SnsClient $snsClient)
          {
              $snsClient->publish([
                  'TopicArn' => getenv('AWS_SNS_TOPIC1'),
                  'Message'  => 'hi',
                  'Subject'  => 'Subject',
              ]);
       
              return ['push'];
          }
       
          public function read(SnsClient $snsClient)
          {
              $data = $this->request->json()->all();
       
              if ($this->request->headers->get('X-Amz-Sns-Message-Type') == 'SubscriptionConfirmation') {
                  $this->logger->notice("sns:confirmSubscription");
                  $snsClient->confirmSubscription([
                      'TopicArn' => getenv('AWS_SNS_TOPIC1'),
                      'Token'    => $data['Token'],
                  ]);
              } else {
                  $this->logger->warn("read", [
                      'Subject'   => $data['Subject'],
                      'Message'   => $data['Message'],
                      'Timestamp' => $data['Timestamp'],
                  ]);
              }
       
              return "OK";
          }
      }
      

      Finally I want to use CloudWatch so I will configure Monolog with another service provider. It’s also well explained here:

      namespace App\Providers;
       
      use Aws\CloudWatchLogs\CloudWatchLogsClient;
      use Illuminate\Support\ServiceProvider;
      use Maxbanton\Cwh\Handler\CloudWatch;
      use Monolog\Formatter\LineFormatter;
      use Monolog\Logger;
       
      class LogServiceProvider extends ServiceProvider
      {
          public function register()
          {
              $awsCredentials = [
                  'region'      => getenv('AWS_REGION'),
                  'version'     => getenv('AWS_VERSION'),
                  'credentials' => [
                      'key'    => getenv('AWS_CREDENTIALS_KEY'),
                      'secret' => getenv('AWS_CREDENTIALS_SECRET'),
                  ],
              ];
       
              $cwClient = new CloudWatchLogsClient($awsCredentials);
       
              $cwRetentionDays      = getenv('CW_RETENTIONDAYS');
              $cwGroupName          = getenv('CW_GROUPNAME');
              $cwStreamNameInstance = getenv('CW_STREAMNAMEINSTANCE');
              $loggerName           = getenv('CF_LOGGERNAME');
       
              $logger  = new Logger($loggerName);
              $handler = new CloudWatch($cwClient, $cwGroupName, $cwStreamNameInstance, $cwRetentionDays);
              $handler->setFormatter(new LineFormatter(null, null, false, true));
       
              $logger->pushHandler($handler);
       
              $this->app->instance(Logger::class, $logger);
          }
      }
      

      Debugging those kind of webhooks with a EC2 instance sometimes is a bit hard. But we can easily expose our local webserver to internet with ngrok. We only need to start our local server

      php -S 0.0.0.0:8080 -t www
      

      And create a tunnel wiht ngrok

      ngrok http 8080
      

      And that’s up. Lumen and SNS up and running.

      Code available in my github

      comments powered by Disqus