mirror of
https://github.com/appwrite/appwrite.git
synced 2026-05-26 13:51:13 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2f94b7170e | ||
|
|
ba69d3397b | ||
|
|
dd2a04b362 | ||
|
|
bd1b548cd8 | ||
|
|
bc383fa3ec | ||
|
|
3a96d9667c | ||
|
|
3e74e39d1d | ||
|
|
aeb497a02a | ||
|
|
284032ac74 |
@@ -370,6 +370,11 @@ 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 headers for sync executions
|
||||
$currentTime = new \DateTime();
|
||||
$headers['x-appwrite-scheduled-at'] = $currentTime->format('Y-m-d\TH:i:s.v\Z');
|
||||
$headers['x-appwrite-executed-at'] = $currentTime->format('Y-m-d\TH:i:s.v\Z');
|
||||
$headers['x-appwrite-execution-delay'] = '0';
|
||||
|
||||
$ip = $headers['x-real-ip'] ?? '';
|
||||
if (!empty($ip)) {
|
||||
|
||||
@@ -71,8 +71,11 @@ $eventDatabaseListener = function (Document $project, Document $document, Respon
|
||||
->setParam('userId', $document->getId())
|
||||
->setPayload($response->output($document, Response::MODEL_USER));
|
||||
|
||||
$scheduledAt = new \DateTime();
|
||||
|
||||
// Trigger functions, webhooks, and realtime events
|
||||
$queueForFunctions
|
||||
->setHeaders(['x-appwrite-scheduled-at' => $scheduledAt->format('Y-m-d\TH:i:s.v\Z')])
|
||||
->from($queueForEvents)
|
||||
->trigger();
|
||||
|
||||
|
||||
@@ -144,7 +144,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', 'x-appwrite-execution-delay', 'x-appwrite-executed-at'];
|
||||
const FUNCTION_ALLOWLIST_HEADERS_RESPONSE = ['content-type', 'content-length'];
|
||||
// Message types
|
||||
const MESSAGE_TYPE_EMAIL = 'email';
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -228,6 +228,14 @@ 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');
|
||||
|
||||
if (!$async) {
|
||||
$executedAt = new \DateTime();
|
||||
$headers['x-appwrite-executed-at'] = $executedAt->format('Y-m-d\TH:i:s.v\Z');
|
||||
$headers['x-appwrite-execution-delay'] = '0';
|
||||
}
|
||||
|
||||
$ip = $headers['x-real-ip'] ?? '';
|
||||
if (!empty($ip)) {
|
||||
|
||||
@@ -164,7 +164,8 @@ class Functions extends Action
|
||||
method: 'POST',
|
||||
headers: [
|
||||
'user-agent' => 'Appwrite/' . APP_VERSION_STABLE,
|
||||
'content-type' => 'application/json'
|
||||
'content-type' => 'application/json',
|
||||
'x-appwrite-scheduled-at' => $headers['x-appwrite-scheduled-at'] ?? '',
|
||||
],
|
||||
data: null,
|
||||
user: $user,
|
||||
@@ -405,6 +406,13 @@ 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'] ?? '';
|
||||
if (!empty($scheduledAt)) {
|
||||
$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(max(0, $delay));
|
||||
}
|
||||
|
||||
/** Create execution or update execution status */
|
||||
$execution = $dbForProject->getDocument('executions', $executionId ?? '');
|
||||
@@ -447,8 +455,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);
|
||||
}
|
||||
|
||||
|
||||
@@ -1508,6 +1508,17 @@ class FunctionsCustomServerTest extends Scope
|
||||
$this->assertEquals(204, $lastExecution['responseStatusCode']);
|
||||
$this->assertStringContainsString($userId, $lastExecution['logs']);
|
||||
$this->assertStringContainsString('Event User', $lastExecution['logs']);
|
||||
|
||||
$requestHeaders = array_column($lastExecution['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', $lastExecution['logs']);
|
||||
$this->assertStringContainsString('scheduled-at-is-valid', $lastExecution['logs']);
|
||||
$this->assertStringContainsString('executed-at-is-valid', $lastExecution['logs']);
|
||||
|
||||
}, 10000, 500);
|
||||
|
||||
$this->cleanupFunction($functionId);
|
||||
@@ -2282,4 +2293,94 @@ class FunctionsCustomServerTest extends Scope
|
||||
|
||||
$this->cleanupFunction($functionId);
|
||||
}
|
||||
|
||||
public function testAsyncExecutionHeaders()
|
||||
{
|
||||
$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);
|
||||
}
|
||||
|
||||
public function testSyncExecutionHeaders()
|
||||
{
|
||||
$functionId = $this->setupFunction([
|
||||
'functionId' => ID::unique(),
|
||||
'name' => 'Test sync 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' => false,
|
||||
]);
|
||||
|
||||
$this->assertEquals(201, $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->assertEquals('0', $requestHeaders['x-appwrite-execution-delay']);
|
||||
$this->assertGreaterThanOrEqual($requestHeaders['x-appwrite-scheduled-at'], $requestHeaders['x-appwrite-executed-at']);
|
||||
|
||||
$this->cleanupFunction($functionId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,7 +118,7 @@ class FunctionsScheduleTest extends Scope
|
||||
$this->assertEquals('scheduled', $execution['body']['status']);
|
||||
$this->assertEquals('PATCH', $execution['body']['requestMethod']);
|
||||
$this->assertEquals('/custom-path', $execution['body']['requestPath']);
|
||||
$this->assertCount(0, $execution['body']['requestHeaders']);
|
||||
$this->assertCount(1, $execution['body']['requestHeaders']);
|
||||
|
||||
\sleep(120);
|
||||
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -1,5 +1,22 @@
|
||||
module.exports = async(context) => {
|
||||
context.log(context.req.body.$id);
|
||||
context.log(context.req.body.name);
|
||||
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");
|
||||
}
|
||||
return context.res.empty();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user