Skip to content

Commit e5a6cb3

Browse files
Merge pull request #122 from miccehedin/app-engine
App engine
2 parents bf14e38 + a4a3098 commit e5a6cb3

12 files changed

+278
-158
lines changed

README.md

+21-7
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ Please check the [Laravel support policy](https://laravel.com/docs/master/releas
4646
'queue' => env('STACKKIT_CLOUD_TASKS_QUEUE', 'default'),
4747
'service_account_email' => env('STACKKIT_CLOUD_TASKS_SERVICE_EMAIL', ''),
4848
'signed_audience' => env('STACKKIT_CLOUD_TASKS_SIGNED_AUDIENCE', true),
49+
50+
// Required when using AppEngine
51+
'app_engine' => env('STACKKIT_APP_ENGINE_TASK', false),
52+
'app_engine_service' => env('STACKKIT_APP_ENGINE_SERVICE', ''),
53+
54+
// Required when not using AppEngine
55+
'handler' => env('STACKKIT_CLOUD_TASKS_HANDLER', ''),
56+
'service_account_email' => env('STACKKIT_CLOUD_TASKS_SERVICE_EMAIL', ''),
57+
'signed_audience' => env('STACKKIT_CLOUD_TASKS_SIGNED_AUDIENCE', true),
58+
4959
// Optional: The deadline in seconds for requests sent to the worker. If the worker
5060
// does not respond by this deadline then the request is cancelled and the attempt
5161
// is marked as a DEADLINE_EXCEEDED failure.
@@ -64,13 +74,17 @@ Now that the package is installed, the final step is to set the correct environm
6474

6575
Please check the table below on what the values mean and what their value should be.
6676

67-
| Environment variable | Description |Example
68-
--------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---
69-
| `STACKKIT_CLOUD_TASKS_PROJECT` | The project your queue belongs to. |`my-project`
70-
| `STACKKIT_CLOUD_TASKS_LOCATION` | The region where the project is hosted |`europe-west6`
71-
| `STACKKIT_CLOUD_TASKS_QUEUE` | The default queue a job will be added to |`emails`
72-
| `STACKKIT_CLOUD_TASKS_SERVICE_EMAIL` | The email address of the service account. Important, it should have the correct roles. See the section below which roles. |`[email protected]`
73-
| `STACKKIT_CLOUD_TASKS_HANDLER` (optional) | The URL that Cloud Tasks will call to process a job. This should be the URL to your Laravel app. By default we will use the URL that dispatched the job. |`https://<your website>.com`
77+
| Environment variable | Description |Example
78+
---------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------|---
79+
| `STACKKIT_CLOUD_TASKS_PROJECT` | The project your queue belongs to. |`my-project`
80+
| `STACKKIT_CLOUD_TASKS_LOCATION` | The region where the project is hosted. |`europe-west6`
81+
| `STACKKIT_CLOUD_TASKS_QUEUE` | The default queue a job will be added to. |`emails`
82+
| **App Engine**
83+
| `STACKKIT_APP_ENGINE_TASK` (optional) | Set to true to use App Engine task (else a Http task will be used). Defaults to false. |`true`
84+
| `STACKKIT_APP_ENGINE_SERVICE` (optional) | The App Engine service to handle the task (only if using App Engine task). |`api`
85+
| **Non- App Engine apps**
86+
| `STACKKIT_CLOUD_TASKS_SERVICE_EMAIL` (optional) | The email address of the service account. Important, it should have the correct roles. See the section below which roles. |`[email protected]`
87+
| `STACKKIT_CLOUD_TASKS_HANDLER` (optional) | The URL that Cloud Tasks will call to process a job. This should be the URL to your Laravel app. By default we will use the URL that dispatched the job. |`https://<your website>.com`
7488
| `STACKKIT_CLOUD_TASKS_SIGNED_AUDIENCE` (optional) | True or false depending if you want extra security by signing the audience of your tasks. May misbehave in certain Cloud Run setups. Defaults to true. | `true`
7589
</details>
7690
<details>

src/CloudTasksConnector.php

-2
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,6 @@ class CloudTasksConnector implements ConnectorInterface
99
{
1010
public function connect(array $config)
1111
{
12-
Config::validate($config);
13-
1412
// The handler is the URL which Cloud Tasks will call with the job payload. This
1513
// URL of the handler can be manually set through an environment variable, but
1614
// if it is not then we will choose a sensible default (the current app url)

src/CloudTasksQueue.php

+57-36
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
namespace Stackkit\LaravelGoogleCloudTasksQueue;
44

5+
use Google\Cloud\Tasks\V2\AppEngineHttpRequest;
6+
use Google\Cloud\Tasks\V2\AppEngineRouting;
57
use Google\Cloud\Tasks\V2\CloudTasksClient;
68
use Google\Cloud\Tasks\V2\HttpMethod;
79
use Google\Cloud\Tasks\V2\HttpRequest;
@@ -14,6 +16,7 @@
1416
use Illuminate\Support\Carbon;
1517
use Illuminate\Support\Str;
1618
use Stackkit\LaravelGoogleCloudTasksQueue\Events\TaskCreated;
19+
1720
use function Safe\json_decode;
1821
use function Safe\json_encode;
1922

@@ -36,7 +39,7 @@ public function __construct(array $config, CloudTasksClient $client, $dispatchAf
3639
/**
3740
* Get the size of the queue.
3841
*
39-
* @param string|null $queue
42+
* @param string|null $queue
4043
* @return int
4144
*/
4245
public function size($queue = null)
@@ -48,11 +51,11 @@ public function size($queue = null)
4851
/**
4952
* Fallback method for Laravel 6x and 7x
5053
*
51-
* @param \Closure|string|object $job
52-
* @param string $payload
53-
* @param string $queue
54-
* @param \DateTimeInterface|\DateInterval|int|null $delay
55-
* @param callable $callback
54+
* @param \Closure|string|object $job
55+
* @param string $payload
56+
* @param string $queue
57+
* @param \DateTimeInterface|\DateInterval|int|null $delay
58+
* @param callable $callback
5659
* @return mixed
5760
*/
5861
protected function enqueueUsing($job, $payload, $queue, $delay, $callback)
@@ -67,9 +70,9 @@ protected function enqueueUsing($job, $payload, $queue, $delay, $callback)
6770
/**
6871
* Push a new job onto the queue.
6972
*
70-
* @param string|object $job
71-
* @param mixed $data
72-
* @param string|null $queue
73+
* @param string|object $job
74+
* @param mixed $data
75+
* @param string|null $queue
7376
* @return void
7477
*/
7578
public function push($job, $data = '', $queue = null)
@@ -88,25 +91,25 @@ function ($payload, $queue) {
8891
/**
8992
* Push a raw payload onto the queue.
9093
*
91-
* @param string $payload
92-
* @param string|null $queue
93-
* @param array $options
94+
* @param string $payload
95+
* @param string|null $queue
96+
* @param array $options
9497
* @return string
9598
*/
9699
public function pushRaw($payload, $queue = null, array $options = [])
97100
{
98-
$delay = ! empty($options['delay']) ? $options['delay'] : 0;
101+
$delay = !empty($options['delay']) ? $options['delay'] : 0;
99102

100103
return $this->pushToCloudTasks($queue, $payload, $delay);
101104
}
102105

103106
/**
104107
* Push a new job onto the queue after a delay.
105108
*
106-
* @param \DateTimeInterface|\DateInterval|int $delay
107-
* @param string|object $job
108-
* @param mixed $data
109-
* @param string|null $queue
109+
* @param \DateTimeInterface|\DateInterval|int $delay
110+
* @param string|object $job
111+
* @param mixed $data
112+
* @param string|null $queue
110113
* @return void
111114
*/
112115
public function later($delay, $job, $data = '', $queue = null)
@@ -125,8 +128,8 @@ function ($payload, $queue, $delay) {
125128
/**
126129
* Push a job to Cloud Tasks.
127130
*
128-
* @param string|null $queue
129-
* @param string $payload
131+
* @param string|null $queue
132+
* @param string $payload
130133
* @param \DateTimeInterface|\DateInterval|int $delay
131134
* @return string
132135
*/
@@ -136,10 +139,6 @@ protected function pushToCloudTasks($queue, $payload, $delay = 0)
136139
$queueName = $this->client->queueName($this->config['project'], $this->config['location'], $queue);
137140
$availableAt = $this->availableAt($delay);
138141

139-
$httpRequest = $this->createHttpRequest();
140-
$httpRequest->setUrl($this->getHandler());
141-
$httpRequest->setHttpMethod(HttpMethod::POST);
142-
143142
$payload = json_decode($payload, true);
144143

145144
// Laravel 7+ jobs have a uuid, but Laravel 6 doesn't have it.
@@ -152,11 +151,38 @@ protected function pushToCloudTasks($queue, $payload, $delay = 0)
152151
// value and need to manually set and update the number of times a task has been attempted.
153152
$payload = $this->withAttempts($payload);
154153

155-
$httpRequest->setBody(json_encode($payload));
156-
157154
$task = $this->createTask();
158155
$task->setName($this->taskName($queue, $payload));
159-
$task->setHttpRequest($httpRequest);
156+
157+
if (!empty($this->config['app_engine'])) {
158+
$path = \Safe\parse_url(route('cloud-tasks.handle-task'), PHP_URL_PATH);
159+
160+
$appEngineRequest = new AppEngineHttpRequest();
161+
$appEngineRequest->setRelativeUri($path);
162+
$appEngineRequest->setHttpMethod(HttpMethod::POST);
163+
$appEngineRequest->setBody(json_encode($payload));
164+
if (!empty($service = $this->config['app_engine_service'])) {
165+
$routing = new AppEngineRouting();
166+
$routing->setService($service);
167+
$appEngineRequest->setAppEngineRouting($routing);
168+
}
169+
$task->setAppEngineHttpRequest($appEngineRequest);
170+
} else {
171+
$httpRequest = $this->createHttpRequest();
172+
$httpRequest->setUrl($this->getHandler());
173+
$httpRequest->setHttpMethod(HttpMethod::POST);
174+
175+
$httpRequest->setBody(json_encode($payload));
176+
177+
$token = new OidcToken;
178+
$token->setServiceAccountEmail($this->config['service_account_email']);
179+
if ($audience = $this->getAudience()) {
180+
$token->setAudience($audience);
181+
}
182+
$httpRequest->setOidcToken($token);
183+
$task->setHttpRequest($httpRequest);
184+
}
185+
160186

161187
// The deadline for requests sent to the app. If the app does not respond by
162188
// this deadline then the request is cancelled and the attempt is marked as
@@ -165,13 +191,6 @@ protected function pushToCloudTasks($queue, $payload, $delay = 0)
165191
$task->setDispatchDeadline(new Duration(['seconds' => $this->config['dispatch_deadline']]));
166192
}
167193

168-
$token = new OidcToken;
169-
$token->setServiceAccountEmail($this->config['service_account_email']);
170-
if ($audience = $this->getAudience()) {
171-
$token->setAudience($audience);
172-
}
173-
$httpRequest->setOidcToken($token);
174-
175194
if ($availableAt > time()) {
176195
$task->setScheduleTime(new Timestamp(['seconds' => $availableAt]));
177196
}
@@ -186,7 +205,7 @@ protected function pushToCloudTasks($queue, $payload, $delay = 0)
186205
private function withUuid(array $payload): array
187206
{
188207
if (!isset($payload['uuid'])) {
189-
$payload['uuid'] = (string) Str::uuid();
208+
$payload['uuid'] = (string)Str::uuid();
190209
}
191210

192211
return $payload;
@@ -227,7 +246,7 @@ private function withAttempts(array $payload): array
227246
/**
228247
* Pop the next job off of the queue.
229248
*
230-
* @param string|null $queue
249+
* @param string|null $queue
231250
* @return \Illuminate\Contracts\Queue\Job|null
232251
*/
233252
public function pop($queue = null)
@@ -251,11 +270,13 @@ public function delete(CloudTasksJob $job): void
251270

252271
$queue = $job->getQueue() ?: $this->config['queue']; // @todo: make this a helper method somewhere.
253272

273+
$headerTaskName = request()->headers->get('X-Cloudtasks-Taskname')
274+
?? request()->headers->get('X-AppEngine-TaskName');
254275
$taskName = $this->client->taskName(
255276
$config['project'],
256277
$config['location'],
257278
$queue,
258-
(string) request()->headers->get('X-Cloudtasks-Taskname')
279+
(string)$headerTaskName
259280
);
260281

261282
CloudTasksApi::deleteTask($taskName);

src/Config.php

+17-28
Original file line numberDiff line numberDiff line change
@@ -9,21 +9,6 @@
99

1010
class Config
1111
{
12-
public static function validate(array $config): void
13-
{
14-
if (empty($config['project'])) {
15-
throw new Error(Errors::invalidProject());
16-
}
17-
18-
if (empty($config['location'])) {
19-
throw new Error(Errors::invalidLocation());
20-
}
21-
22-
if (empty($config['service_account_email'])) {
23-
throw new Error(Errors::invalidServiceAccountEmail());
24-
}
25-
}
26-
2712
/**
2813
* @param Closure|string $handler
2914
*/
@@ -42,25 +27,29 @@ public static function getHandler($handler): string
4227
// (still) set to localhost. That will never work because Cloud Tasks
4328
// should always call a public address / hostname to process tasks.
4429
if (in_array($parse['host'], ['localhost', '127.0.0.1', '::1'])) {
45-
throw new Exception(sprintf(
46-
'Unable to push task to Cloud Tasks because the handler URL is set to a local host: %s. ' .
47-
'This does not work because Google is not able to call the given local URL. ' .
48-
'If you are developing on locally, consider using Ngrok or Expose for Laravel to expose your local ' .
49-
'application to the internet.',
50-
$handler
51-
));
30+
throw new Exception(
31+
sprintf(
32+
'Unable to push task to Cloud Tasks because the handler URL is set to a local host: %s. ' .
33+
'This does not work because Google is not able to call the given local URL. ' .
34+
'If you are developing on locally, consider using Ngrok or Expose for Laravel to expose your local ' .
35+
'application to the internet.',
36+
$handler
37+
)
38+
);
5239
}
5340

5441
// When the application is running behind a proxy and the TrustedProxy middleware has not been set up yet,
5542
// an error like [HttpRequest.url must start with 'https'] could be thrown. Since the handler URL must
5643
// always be https, we will provide a little extra information on how to fix this.
5744
if ($parse['scheme'] !== 'https') {
58-
throw new Exception(sprintf(
59-
'Unable to push task to Cloud Tasks because the hander URL is not https. Google Cloud Tasks ' .
60-
'will only call safe (https) URLs. If you are running Laravel behind a proxy (e.g. Ngrok, Expose), make sure it is ' .
61-
'as a trusted proxy. To quickly fix this, add the following to the [app/Http/Middleware/TrustProxies] middleware: ' .
62-
'protected $proxies = \'*\';'
63-
));
45+
throw new Exception(
46+
sprintf(
47+
'Unable to push task to Cloud Tasks because the hander URL is not https. Google Cloud Tasks ' .
48+
'will only call safe (https) URLs. If you are running Laravel behind a proxy (e.g. Ngrok, Expose), make sure it is ' .
49+
'as a trusted proxy. To quickly fix this, add the following to the [app/Http/Middleware/TrustProxies] middleware: ' .
50+
'protected $proxies = \'*\';'
51+
)
52+
);
6453
}
6554

6655
$trimmedHandlerUrl = rtrim($handler, '/');

src/DashboardService.php

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ public static function make(): DashboardService
2121

2222
private function getTaskBody(Task $task): string
2323
{
24-
$httpRequest = $task->getHttpRequest();
24+
$httpRequest = $task->getHttpRequest() ?: $task->getAppEngineHttpRequest();
2525

26-
if (! $httpRequest instanceof HttpRequest) {
26+
if (! $httpRequest) {
2727
throw new Exception('Task does not have a HTTP request.');
2828
}
2929

src/Errors.php

+5
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,9 @@ public static function invalidServiceAccountEmail(): string
1818
{
1919
return 'Google Service Account email address not provided. This is needed to secure the handler so it is only accessible by Google. To fix this, set the STACKKIT_CLOUD_TASKS_SERVICE_EMAIL environment variable';
2020
}
21+
22+
public static function serviceAccountOrAppEngine(): string
23+
{
24+
return 'A Google Service Account email or App Engine Request must be set. Set STACKKIT_CLOUD_TASKS_SERVICE_EMAIL or STACKKIT_APP_ENGINE_TASK';
25+
}
2126
}

0 commit comments

Comments
 (0)