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
4 thoughts on “Handling Amazon SNS messages with PHP, Lumen and CloudWatch”