├── CHANGELOG.md ├── LICENSE ├── README.md ├── composer.json ├── pint.json └── src ├── Providers └── EnhancedSqsServiceProvider.php ├── SqsConnector.php └── SqsQueue.php /CHANGELOG.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hammerstonedev/enhanced-sqs/9f7aabd7393cd8da4447bc1e77fe97c007776827/CHANGELOG.md -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Hammerstone 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Enhanced SQS Driver for Laravel 2 | 3 | @TODO -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hammerstone/sqs", 3 | "description": "A Laravel package to elegantly handle flaky operations.", 4 | "type": "library", 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Aaron Francis", 9 | "email": "aaron@hammerstone.dev" 10 | } 11 | ], 12 | "require": { 13 | "php": "^8.0", 14 | "illuminate/support": "^9.0|^10", 15 | "illuminate/cache": "^9.0|^10", 16 | "illuminate/console": "^9.0|^10" 17 | }, 18 | "require-dev": { 19 | "mockery/mockery": "^1.3.3", 20 | "phpunit/phpunit": ">=8.5.23|^9", 21 | "orchestra/testbench": "^6.0|^7.0|^8.0" 22 | }, 23 | "autoload": { 24 | "psr-4": { 25 | "Hammerstone\\EnhancedSqs\\": "src/" 26 | } 27 | }, 28 | "autoload-dev": { 29 | "psr-4": { 30 | "Hammerstone\\EnhancedSqs\\Tests\\": "tests/" 31 | } 32 | }, 33 | "extra": { 34 | "laravel": { 35 | "providers": [ 36 | "SqsServiceProvider" 37 | ] 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /pint.json: -------------------------------------------------------------------------------- 1 | { 2 | "preset": "laravel", 3 | "rules": { 4 | "not_operator_with_successor_space": false, 5 | "cast_spaces": false, 6 | "heredoc_to_nowdoc": false, 7 | "phpdoc_summary": false, 8 | "concat_space": { 9 | "spacing": "one" 10 | }, 11 | "trailing_comma_in_multiline": false 12 | } 13 | } -------------------------------------------------------------------------------- /src/Providers/EnhancedSqsServiceProvider.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace Hammerstone\EnhancedSqs\Providers; 7 | 8 | use App\Services\SqsConnector; 9 | use Illuminate\Support\Facades\Queue; 10 | use Illuminate\Support\ServiceProvider; 11 | 12 | class EnhancedSqsServiceProvider extends ServiceProvider 13 | { 14 | public function register() 15 | { 16 | // 17 | } 18 | 19 | public function boot() 20 | { 21 | Queue::extend('enhanced-sqs', function () { 22 | return new SqsConnector; 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/SqsConnector.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace Hammerstone\EnhancedSqs; 7 | 8 | use Aws\Sqs\SqsClient; 9 | use Illuminate\Support\Arr; 10 | 11 | class SqsConnector extends \Illuminate\Queue\Connectors\SqsConnector 12 | { 13 | /** 14 | * Establish a queue connection. 15 | * 16 | * @return \Illuminate\Contracts\Queue\Queue 17 | */ 18 | public function connect(array $config) 19 | { 20 | $config = $this->getDefaultConfiguration($config); 21 | 22 | if (!empty($config['key']) && !empty($config['secret'])) { 23 | $config['credentials'] = Arr::only($config, ['key', 'secret', 'token']); 24 | } 25 | 26 | return new SqsQueue( 27 | new SqsClient($config), 28 | $config['queue'], 29 | $config['prefix'] ?? '', 30 | $config['suffix'] ?? '', 31 | $config['after_commit'] ?? null 32 | ); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/SqsQueue.php: -------------------------------------------------------------------------------- 1 | 4 | */ 5 | 6 | namespace Hammerstone\EnhancedSqs; 7 | 8 | use Illuminate\Queue\Jobs\SqsJob; 9 | use Illuminate\Support\Arr; 10 | 11 | class SqsQueue extends \Illuminate\Queue\SqsQueue 12 | { 13 | /** 14 | * @var int The maximum number of seconds a job can be delayed for. 15 | */ 16 | public $maxDelaySeconds = 60 * 15; 17 | 18 | public function pop($queue = null) 19 | { 20 | $response = $this->sqs->receiveMessage([ 21 | 'QueueUrl' => $queue = $this->getQueue($queue), 22 | 'AttributeNames' => ['ApproximateReceiveCount'], 23 | ]); 24 | 25 | if (!is_null($response['Messages']) && count($response['Messages']) > 0) { 26 | $message = $response['Messages'][0]; 27 | 28 | if ($this->hasRemainingDelay($message)) { 29 | return $this->handleRemainingDelay($queue, $message); 30 | } 31 | 32 | // Default behavior from the parent class. 33 | return new SqsJob( 34 | $this->container, $this->sqs, $message, $this->connectionName, $queue 35 | ); 36 | } 37 | } 38 | 39 | protected function hasRemainingDelay($message) 40 | { 41 | $body = json_decode($message['Body'], true); 42 | 43 | return Arr::get($body, 'delayUntil', 0) > time(); 44 | } 45 | 46 | protected function handleRemainingDelay($queue, $message) 47 | { 48 | $payload = json_decode($message['Body'], true); 49 | $delaySeconds = Arr::get($payload, 'delayUntil') - time(); 50 | 51 | $messageId = $this->sqs->sendMessage([ 52 | 'QueueUrl' => $this->getQueue($queue), 53 | 'MessageBody' => $message['Body'], 54 | 'DelaySeconds' => min($delaySeconds, $this->maxDelaySeconds), 55 | ])->get('MessageId'); 56 | 57 | if ($messageId) { 58 | $this->sqs->deleteMessage([ 59 | 'QueueUrl' => $this->getQueue($queue), 60 | 'ReceiptHandle' => $message['ReceiptHandle'], 61 | ]); 62 | } 63 | } 64 | 65 | public function later($delay, $job, $data = '', $queue = null) 66 | { 67 | $delaySeconds = $this->secondsUntil($delay); 68 | 69 | // If it's under the limit, just defer to the parent. 70 | if ($delaySeconds <= $this->maxDelaySeconds) { 71 | return parent::later($delay, $job, $data, $queue); 72 | } 73 | 74 | // Copied directly from the parent class. 75 | $payload = $this->createPayload($job, $queue ?: $this->default, $data); 76 | 77 | $payload = json_decode($payload, true); 78 | 79 | // Use a timestamp instead of seconds because we don't know how 80 | // long it will sit in the queue waiting to be processed. 81 | $payload['delayUntil'] = time() + $this->secondsUntil($delay); 82 | 83 | // Repeat the json_encode flags of the parent class. 84 | $payload = json_encode($payload, JSON_UNESCAPED_UNICODE); 85 | 86 | // Delay for as long as we possibly can, but as short as we need. 87 | $delaySeconds = min($delaySeconds, $this->maxDelaySeconds); 88 | 89 | return $this->enqueueUsing( 90 | $job, 91 | $payload, 92 | $queue, 93 | $delaySeconds, 94 | function ($payload, $queue, $delay) { 95 | return $this->sqs->sendMessage([ 96 | 'QueueUrl' => $this->getQueue($queue), 97 | 'MessageBody' => $payload, 98 | 'DelaySeconds' => $delay, 99 | ])->get('MessageId'); 100 | } 101 | ); 102 | } 103 | } 104 | --------------------------------------------------------------------------------