Back-End

29 jan, 2018

Manuseando mensagens SNS da Amazon com PHP, Lumen e CloudWatch

Publicidade

Hoje em dia eu estou envolvido com o AWS da Amazon, e já que estou migrando meus backends para Lumen, eu vou brincar um pouco com AWS e Lumen. Hoje eu quero criar um servidor Lumen simples para manusear notificações SNS. Um endpoint para ouvir SNS e outro para emitir notificações. Eu também quero registrar logs no CloudWatch. Vamos começar.

Primeiro, o servidor Lumen.

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();

Como podemos ver, há uma rota para enviar notificações e outra para ler mensagens. Para trabalhar com SNS, criarei um provedor de serviços simples.

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));
    }
}

Agora podemos criar as rotas em SnsController. O Sns possui um mecanismo de confirmação para validar os endpoints. Isso está bem explicado aqui.

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";
    }
}

Finalmente eu quero usar o CloudWatch para configurar o Monolog com outro provedor de serviços. Isso também está bem explicado aqui:

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);
    }
}

Depurar esse tipo de webhooks com uma instância EC2 às vezes é um pouco difícil. Mas podemos facilmente expor nosso servidor web local à Internet com o ngrok.

Nós só precisamos iniciar o nosso servidor local.

php -S 0.0.0.0:8080 -t www

E criar um túnel com ngrok.

ngrok http 8080

E é isso. Lumen e SNS estão prontos e funcionando.

Código disponível no meu Github.

***

Gonzalo Ayuso faz parte do time de colunistas internacionais do iMasters. A tradução do artigo é feita pela Redação iMasters, com autorização do autor, e você pode acompanhar o artigo em inglês no link: https://gonzalo123.com/2018/01/22/handling-amazon-sns-messages-with-php-lumen-and-cloudwatch/