Skip to content

Commit 297c31f

Browse files
miccehedinmarickvantuil
authored andcommitted
Add support for App Engine task
1 parent bf98321 commit 297c31f

File tree

4 files changed

+60
-30
lines changed

4 files changed

+60
-30
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,9 @@ Please check the table below on what the values mean and what their value should
8585
| `STACKKIT_CLOUD_TASKS_LOCATION` | The region where the project is hosted. |`europe-west6`
8686
| `STACKKIT_CLOUD_TASKS_QUEUE` | The default queue a job will be added to. |`emails`
8787
| `STACKKIT_CLOUD_TASKS_CREDENTIAL_FILE` (optional) | A json credential file to authenticate the connection (from outside AppEngine) |`project-123.json`
88-
| `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]`
88+
| `STACKKIT_APP_ENGINE_TASK` (optional) | Set to true to use App Engine task (else a Http task will be used). Defaults to false. |`true`
89+
| `STACKKIT_APP_ENGINE_SERVICE` (optional) | The App Engine service to handle the task (only if using App Engine task). |`api`
90+
| `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]`
8991
| `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`
9092
| `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`
9193
</details>
@@ -101,6 +103,7 @@ With Cloud Tasks, this is not the case. Instead, Cloud Tasks will schedule the j
101103

102104
#### Good to know
103105

106+
- If `STACKKIT_APP_ENGINE_TASK` is true, `STACKKIT_CLOUD_TASKS_SERVICE_EMAIL` and `STACKKIT_CLOUD_TASKS_HANDLER` will be ignored.
104107
- The "Min backoff" and "Max backoff" options in Cloud Tasks are ignored. This is intentional: Laravel has its own backoff feature (which is more powerful than what Cloud Tasks offers) and therefore I have chosen that over the Cloud Tasks one.
105108
- Similarly to the backoff feature, I have also chosen to let the package do job retries the 'Laravel way'. In Cloud Tasks, when a task throws an exception, Cloud Tasks will decide for itself when to retry the task (based on the backoff values). It will also manage its own state and knows how many times a task has been retried. This is different from Laravel. In typical Laravel queues, when a job throws an exception, the job is deleted and released back onto the queue. In order to support Laravel's backoff feature, this package must behave the same way about job retries.
106109

src/CloudTasksQueue.php

Lines changed: 32 additions & 14 deletions
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;
@@ -136,10 +138,6 @@ protected function pushToCloudTasks($queue, $payload, $delay = 0)
136138
$queueName = $this->client->queueName($this->config['project'], $this->config['location'], $queue);
137139
$availableAt = $this->availableAt($delay);
138140

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

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

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

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

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-
175193
if ($availableAt > time()) {
176194
$task->setScheduleTime(new Timestamp(['seconds' => $availableAt]));
177195
}

src/Config.php

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ public static function validate(array $config): void
1919
throw new Error(Errors::invalidLocation());
2020
}
2121

22-
if (empty($config['service_account_email'])) {
23-
throw new Error(Errors::invalidServiceAccountEmail());
22+
if (empty($config['service_account_email']) && empty($config['app-engine'])) {
23+
throw new Error(Errors::serviceAccountOrAppEngine());
2424
}
2525
}
2626

@@ -42,25 +42,29 @@ public static function getHandler($handler): string
4242
// (still) set to localhost. That will never work because Cloud Tasks
4343
// should always call a public address / hostname to process tasks.
4444
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-
));
45+
throw new Exception(
46+
sprintf(
47+
'Unable to push task to Cloud Tasks because the handler URL is set to a local host: %s. ' .
48+
'This does not work because Google is not able to call the given local URL. ' .
49+
'If you are developing on locally, consider using Ngrok or Expose for Laravel to expose your local ' .
50+
'application to the internet.',
51+
$handler
52+
)
53+
);
5254
}
5355

5456
// When the application is running behind a proxy and the TrustedProxy middleware has not been set up yet,
5557
// an error like [HttpRequest.url must start with 'https'] could be thrown. Since the handler URL must
5658
// always be https, we will provide a little extra information on how to fix this.
5759
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-
));
60+
throw new Exception(
61+
sprintf(
62+
'Unable to push task to Cloud Tasks because the hander URL is not https. Google Cloud Tasks ' .
63+
'will only call safe (https) URLs. If you are running Laravel behind a proxy (e.g. Ngrok, Expose), make sure it is ' .
64+
'as a trusted proxy. To quickly fix this, add the following to the [app/Http/Middleware/TrustProxies] middleware: ' .
65+
'protected $proxies = \'*\';'
66+
)
67+
);
6468
}
6569

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

src/Errors.php

Lines changed: 5 additions & 0 deletions
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)