Add execution delay headers to async executions

This commit is contained in:
Khushboo Verma
2025-07-23 12:50:49 +05:30
parent 891db255bc
commit 284032ac74
8 changed files with 88 additions and 3 deletions
+3
View File
@@ -57,6 +57,7 @@ Config::setParam('cookieSamesite', Response::COOKIE_SAMESITE_NONE);
function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, SwooleRequest $swooleRequest, Request $request, Response $response, Log $log, Event $queueForEvents, StatsUsage $queueForStatsUsage, Func $queueForFunctions, Executor $executor, Reader $geodb, callable $isResourceBlocked, string $previewHostname, ?Key $apiKey)
{
// add headers here as well
$host = $request->getHostname() ?? '';
if (!empty($previewHostname)) {
$host = $previewHostname;
@@ -370,6 +371,8 @@ function router(App $utopia, Database $dbForPlatform, callable $getProjectDB, Sw
$headers['x-appwrite-key'] = API_KEY_DYNAMIC . '_' . $jwtKey;
$headers['x-appwrite-trigger'] = 'http';
$headers['x-appwrite-user-jwt'] = '';
// add here
// in API - for sync executions, hard code delay to 0
$ip = $headers['x-real-ip'] ?? '';
if (!empty($ip)) {
+3
View File
@@ -713,7 +713,10 @@ App::shutdown()
$queueForEvents->setPayload($responsePayload);
}
$scheduledAt = new \DateTime();
$queueForFunctions
->setHeaders(['x-appwrite-scheduled-at' => $scheduledAt->format('Y-m-d\TH:i:s.v\Z')])
->from($queueForEvents)
->trigger();
+1 -1
View File
@@ -142,7 +142,7 @@ const APP_AUTH_TYPE_ADMIN = 'Admin';
// Response related
const MAX_OUTPUT_CHUNK_SIZE = 10 * 1024 * 1024; // 10MB
// Function headers
const FUNCTION_ALLOWLIST_HEADERS_REQUEST = ['content-type', 'agent', 'content-length', 'host'];
const FUNCTION_ALLOWLIST_HEADERS_REQUEST = ['content-type', 'agent', 'content-length', 'host', 'x-appwrite-scheduled-at'];
const FUNCTION_ALLOWLIST_HEADERS_RESPONSE = ['content-type', 'content-length'];
// Message types
const MESSAGE_TYPE_EMAIL = 'email';
+1 -1
View File
@@ -161,7 +161,7 @@ class Func extends Event
/**
* Sets custom headers for the function event.
*
* @param string $headers
* @param array $headers
* @return self
*/
public function setHeaders(array $headers): self
@@ -221,6 +221,8 @@ class Create extends Base
$headers['x-appwrite-country-code'] = '';
$headers['x-appwrite-continent-code'] = '';
$headers['x-appwrite-continent-eu'] = 'false';
$currentTime = new \DateTime();
$headers['x-appwrite-scheduled-at'] = $currentTime->format('Y-m-d\TH:i:s.v\Z');
$ip = $headers['x-real-ip'] ?? '';
if (!empty($ip)) {
+10 -1
View File
@@ -405,6 +405,11 @@ class Functions extends Action
$headers['x-appwrite-country-code'] = '';
$headers['x-appwrite-continent-code'] = '';
$headers['x-appwrite-continent-eu'] = 'false';
$scheduledAt = $headers['x-appwrite-scheduled-at'] ?? '';
$executedAt = new \DateTime();
$headers['x-appwrite-executed-at'] = $executedAt->format('Y-m-d\TH:i:s.v\Z');
$delay = $executedAt->getTimestamp() - (new \DateTime($scheduledAt))->getTimestamp();
$headers['x-appwrite-execution-delay'] = (string)floor($delay);
/** Create execution or update execution status */
$execution = $dbForProject->getDocument('executions', $executionId ?? '');
@@ -447,8 +452,12 @@ class Functions extends Action
}
if ($execution->getAttribute('status') !== 'processing') {
$headersFromExecution = $execution->getAttribute('requestHeaders', []);
$execution->setAttribute('status', 'processing');
$execution->setAttribute('requestHeaders', \array_merge($headersFromExecution, [
['name' => 'x-appwrite-executed-at', 'value' => $headers['x-appwrite-executed-at']],
['name' => 'x-appwrite-execution-delay', 'value' => $headers['x-appwrite-execution-delay'],
]]));
$execution = $dbForProject->updateDocument('executions', $executionId, $execution);
}
@@ -2282,4 +2282,54 @@ class FunctionsCustomServerTest extends Scope
$this->cleanupFunction($functionId);
}
public function testExecutionHeaders()
{
$functionId = $this->setupFunction([
'functionId' => ID::unique(),
'name' => 'Test execution headers',
'execute' => [Role::any()->toString()],
'runtime' => 'node-22',
'entrypoint' => 'index.js',
'timeout' => 10,
]);
$deploymentId = $this->setupDeployment($functionId, [
'code' => $this->packageFunction('basic'),
'activate' => true,
]);
$deployment = $this->getDeployment($functionId, $deploymentId);
$this->assertEquals(200, $deployment['headers']['status-code']);
$execution = $this->createExecution($functionId, [
'async' => true,
]);
$this->assertEquals(202, $execution['headers']['status-code']);
$this->assertNotEmpty($execution['body']['$id']);
$executionId = $execution['body']['$id'] ?? '';
sleep(5);
$execution = $this->getExecution($functionId, $executionId);
$this->assertEquals(200, $execution['headers']['status-code']);
$this->assertEquals('completed', $execution['body']['status']);
$this->assertEquals(200, $execution['body']['responseStatusCode']);
$this->assertGreaterThan(0, $execution['body']['duration']);
$this->assertNotEmpty($execution['body']['logs']);
$this->assertNotEmpty($execution['body']['responseHeaders']);
$requestHeaders = array_column($execution['body']['requestHeaders'], 'value', 'name');
$this->assertArrayHasKey('x-appwrite-scheduled-at', $requestHeaders);
$this->assertArrayHasKey('x-appwrite-executed-at', $requestHeaders);
$this->assertArrayHasKey('x-appwrite-execution-delay', $requestHeaders);
$this->assertIsNumeric($requestHeaders['x-appwrite-execution-delay']);
$this->assertGreaterThan($requestHeaders['x-appwrite-scheduled-at'], $requestHeaders['x-appwrite-executed-at']);
$this->assertStringContainsString('execution-delay-is-valid', $execution['body']['logs']);
$this->assertStringContainsString('scheduled-at-is-valid', $execution['body']['logs']);
$this->assertStringContainsString('executed-at-is-valid', $execution['body']['logs']);
$this->cleanupFunction($functionId);
}
}
+18
View File
@@ -7,6 +7,24 @@ module.exports = async(context) => {
} else {
context.log('jwt-is-invalid');
}
if (context.req.headers["x-appwrite-execution-delay"]) {
context.log("execution-delay-is-valid");
} else {
context.log("execution-delay-is-invalid");
}
if (context.req.headers["x-appwrite-scheduled-at"]) {
context.log("scheduled-at-is-valid");
} else {
context.log("scheduled-at-is-invalid");
}
if (context.req.headers["x-appwrite-executed-at"]) {
context.log("executed-at-is-valid");
} else {
context.log("executed-at-is-invalid");
}
if(context.req.path === '/custom-response') {
const code = +(context.req.query['code'] || '200');