Merge pull request #8323 from appwrite/feat-opr-v4-support
OPR v4 support
This commit is contained in:
commit
75cd558670
15 changed files with 417 additions and 45 deletions
|
@ -3029,7 +3029,7 @@ $projectCollections = array_merge([
|
||||||
'size' => 8,
|
'size' => 8,
|
||||||
'signed' => true,
|
'signed' => true,
|
||||||
'required' => false,
|
'required' => false,
|
||||||
'default' => 'v3',
|
'default' => 'v4',
|
||||||
'array' => false,
|
'array' => false,
|
||||||
'filters' => [],
|
'filters' => [],
|
||||||
],
|
],
|
||||||
|
|
|
@ -6,4 +6,4 @@
|
||||||
|
|
||||||
use Appwrite\Runtimes\Runtimes;
|
use Appwrite\Runtimes\Runtimes;
|
||||||
|
|
||||||
return (new Runtimes('v3'))->getAll();
|
return (new Runtimes('v4'))->getAll();
|
||||||
|
|
|
@ -223,7 +223,7 @@ App::post('/v1/functions')
|
||||||
'commands' => $commands,
|
'commands' => $commands,
|
||||||
'scopes' => $scopes,
|
'scopes' => $scopes,
|
||||||
'search' => implode(' ', [$functionId, $name, $runtime]),
|
'search' => implode(' ', [$functionId, $name, $runtime]),
|
||||||
'version' => 'v3',
|
'version' => 'v4',
|
||||||
'installationId' => $installation->getId(),
|
'installationId' => $installation->getId(),
|
||||||
'installationInternalId' => $installation->getInternalId(),
|
'installationInternalId' => $installation->getInternalId(),
|
||||||
'providerRepositoryId' => $providerRepositoryId,
|
'providerRepositoryId' => $providerRepositoryId,
|
||||||
|
@ -1743,10 +1743,7 @@ App::post('/v1/functions/:functionId/executions')
|
||||||
->setContext('function', $function);
|
->setContext('function', $function);
|
||||||
|
|
||||||
if ($async) {
|
if ($async) {
|
||||||
if ($function->getAttribute('logging')) {
|
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||||
/** @var Document $execution */
|
|
||||||
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
|
|
||||||
}
|
|
||||||
|
|
||||||
if(is_null($scheduledAt)) {
|
if(is_null($scheduledAt)) {
|
||||||
$queueForFunctions
|
$queueForFunctions
|
||||||
|
@ -1850,6 +1847,7 @@ App::post('/v1/functions/:functionId/executions')
|
||||||
method: $method,
|
method: $method,
|
||||||
headers: $headers,
|
headers: $headers,
|
||||||
runtimeEntrypoint: $command,
|
runtimeEntrypoint: $command,
|
||||||
|
logging: $function->getAttribute('logging', true),
|
||||||
requestTimeout: 30
|
requestTimeout: 30
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -1889,10 +1887,7 @@ App::post('/v1/functions/:functionId/executions')
|
||||||
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000)) // per function
|
->addMetric(str_replace('{functionInternalId}', $function->getInternalId(), METRIC_FUNCTION_ID_EXECUTIONS_COMPUTE), (int)($execution->getAttribute('duration') * 1000)) // per function
|
||||||
;
|
;
|
||||||
|
|
||||||
if ($function->getAttribute('logging')) {
|
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
|
||||||
/** @var Document $execution */
|
|
||||||
$execution = Authorization::skip(fn () => $dbForProject->createDocument('executions', $execution));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$roles = Authorization::getRoles();
|
$roles = Authorization::getRoles();
|
||||||
|
|
|
@ -273,6 +273,7 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
|
||||||
method: $method,
|
method: $method,
|
||||||
headers: $headers,
|
headers: $headers,
|
||||||
runtimeEntrypoint: $command,
|
runtimeEntrypoint: $command,
|
||||||
|
logging: $function->getAttribute('logging', true),
|
||||||
requestTimeout: 30
|
requestTimeout: 30
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -332,13 +333,6 @@ function router(App $utopia, Database $dbForConsole, callable $getProjectDB, Swo
|
||||||
|
|
||||||
$body = $execution['responseBody'] ?? '';
|
$body = $execution['responseBody'] ?? '';
|
||||||
|
|
||||||
$encodingKey = \array_search('x-open-runtimes-encoding', \array_column($execution['responseHeaders'], 'name'));
|
|
||||||
if ($encodingKey !== false) {
|
|
||||||
if (($execution['responseHeaders'][$encodingKey]['value'] ?? '') === 'base64') {
|
|
||||||
$body = \base64_decode($body);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$contentType = 'text/plain';
|
$contentType = 'text/plain';
|
||||||
foreach ($execution['responseHeaders'] as $header) {
|
foreach ($execution['responseHeaders'] as $header) {
|
||||||
if (\strtolower($header['name']) === 'content-type') {
|
if (\strtolower($header['name']) === 'content-type') {
|
||||||
|
|
16
composer.lock
generated
16
composer.lock
generated
|
@ -3406,16 +3406,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "nikic/php-parser",
|
"name": "nikic/php-parser",
|
||||||
"version": "v5.0.2",
|
"version": "v5.1.0",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/nikic/PHP-Parser.git",
|
"url": "https://github.com/nikic/PHP-Parser.git",
|
||||||
"reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13"
|
"reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13",
|
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/683130c2ff8c2739f4822ff7ac5c873ec529abd1",
|
||||||
"reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13",
|
"reference": "683130c2ff8c2739f4822ff7ac5c873ec529abd1",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -3426,7 +3426,7 @@
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"ircmaxell/php-yacc": "^0.0.7",
|
"ircmaxell/php-yacc": "^0.0.7",
|
||||||
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
|
"phpunit/phpunit": "^9.0"
|
||||||
},
|
},
|
||||||
"bin": [
|
"bin": [
|
||||||
"bin/php-parse"
|
"bin/php-parse"
|
||||||
|
@ -3458,9 +3458,9 @@
|
||||||
],
|
],
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
"issues": "https://github.com/nikic/PHP-Parser/issues",
|
||||||
"source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2"
|
"source": "https://github.com/nikic/PHP-Parser/tree/v5.1.0"
|
||||||
},
|
},
|
||||||
"time": "2024-03-05T20:51:40+00:00"
|
"time": "2024-07-01T20:03:41+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phar-io/manifest",
|
"name": "phar-io/manifest",
|
||||||
|
@ -5615,5 +5615,5 @@
|
||||||
"platform-overrides": {
|
"platform-overrides": {
|
||||||
"php": "8.3"
|
"php": "8.3"
|
||||||
},
|
},
|
||||||
"plugin-api-version": "2.6.0"
|
"plugin-api-version": "2.3.0"
|
||||||
}
|
}
|
||||||
|
|
|
@ -851,7 +851,7 @@ services:
|
||||||
hostname: exc1
|
hostname: exc1
|
||||||
<<: *x-logging
|
<<: *x-logging
|
||||||
stop_signal: SIGINT
|
stop_signal: SIGINT
|
||||||
image: openruntimes/executor:0.5.5
|
image: openruntimes/executor:0.6.0
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
networks:
|
networks:
|
||||||
- appwrite
|
- appwrite
|
||||||
|
@ -872,7 +872,7 @@ services:
|
||||||
- OPR_EXECUTOR_ENV=$_APP_ENV
|
- OPR_EXECUTOR_ENV=$_APP_ENV
|
||||||
- OPR_EXECUTOR_RUNTIMES=$_APP_FUNCTIONS_RUNTIMES
|
- OPR_EXECUTOR_RUNTIMES=$_APP_FUNCTIONS_RUNTIMES
|
||||||
- OPR_EXECUTOR_SECRET=$_APP_EXECUTOR_SECRET
|
- OPR_EXECUTOR_SECRET=$_APP_EXECUTOR_SECRET
|
||||||
- OPR_EXECUTOR_RUNTIME_VERSIONS=v2,v3
|
- OPR_EXECUTOR_RUNTIME_VERSIONS=v2,v4
|
||||||
- OPR_EXECUTOR_LOGGING_CONFIG=$_APP_LOGGING_CONFIG
|
- OPR_EXECUTOR_LOGGING_CONFIG=$_APP_LOGGING_CONFIG
|
||||||
- OPR_EXECUTOR_STORAGE_DEVICE=$_APP_STORAGE_DEVICE
|
- OPR_EXECUTOR_STORAGE_DEVICE=$_APP_STORAGE_DEVICE
|
||||||
- OPR_EXECUTOR_STORAGE_S3_ACCESS_KEY=$_APP_STORAGE_S3_ACCESS_KEY
|
- OPR_EXECUTOR_STORAGE_S3_ACCESS_KEY=$_APP_STORAGE_S3_ACCESS_KEY
|
||||||
|
|
|
@ -405,9 +405,7 @@ class Functions extends Action
|
||||||
'search' => implode(' ', [$functionId, $executionId]),
|
'search' => implode(' ', [$functionId, $executionId]),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($function->getAttribute('logging')) {
|
$execution = $dbForProject->createDocument('executions', $execution);
|
||||||
$execution = $dbForProject->createDocument('executions', $execution);
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: @Meldiron Trigger executions.create event here
|
// TODO: @Meldiron Trigger executions.create event here
|
||||||
|
|
||||||
|
@ -419,9 +417,7 @@ class Functions extends Action
|
||||||
if ($execution->getAttribute('status') !== 'processing') {
|
if ($execution->getAttribute('status') !== 'processing') {
|
||||||
$execution->setAttribute('status', 'processing');
|
$execution->setAttribute('status', 'processing');
|
||||||
|
|
||||||
if ($function->getAttribute('logging')) {
|
$execution = $dbForProject->updateDocument('executions', $executionId, $execution);
|
||||||
$execution = $dbForProject->updateDocument('executions', $executionId, $execution);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$durationStart = \microtime(true);
|
$durationStart = \microtime(true);
|
||||||
|
@ -490,7 +486,8 @@ class Functions extends Action
|
||||||
path: $path,
|
path: $path,
|
||||||
method: $method,
|
method: $method,
|
||||||
headers: $headers,
|
headers: $headers,
|
||||||
runtimeEntrypoint: $command
|
runtimeEntrypoint: $command,
|
||||||
|
logging: $function->getAttribute('logging', true),
|
||||||
);
|
);
|
||||||
|
|
||||||
$status = $executionResponse['statusCode'] >= 400 ? 'failed' : 'completed';
|
$status = $executionResponse['statusCode'] >= 400 ? 'failed' : 'completed';
|
||||||
|
@ -532,9 +529,9 @@ class Functions extends Action
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($function->getAttribute('logging')) {
|
|
||||||
$execution = $dbForProject->updateDocument('executions', $executionId, $execution);
|
$execution = $dbForProject->updateDocument('executions', $executionId, $execution);
|
||||||
}
|
|
||||||
/** Trigger Webhook */
|
/** Trigger Webhook */
|
||||||
$executionModel = new Execution();
|
$executionModel = new Execution();
|
||||||
$queueForEvents
|
$queueForEvents
|
||||||
|
|
147
src/Appwrite/Utopia/Fetch/BodyMultipart.php
Normal file
147
src/Appwrite/Utopia/Fetch/BodyMultipart.php
Normal file
|
@ -0,0 +1,147 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Appwrite\Utopia\Fetch;
|
||||||
|
|
||||||
|
class BodyMultipart
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array<string, mixed> $parts
|
||||||
|
*/
|
||||||
|
private array $parts = [];
|
||||||
|
private string $boundary = "";
|
||||||
|
|
||||||
|
public function __construct(string $boundary = null)
|
||||||
|
{
|
||||||
|
if (is_null($boundary)) {
|
||||||
|
$this->boundary = self::generateBoundary();
|
||||||
|
} else {
|
||||||
|
$this->boundary = $boundary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function generateBoundary(): string
|
||||||
|
{
|
||||||
|
return '-----------------------------' . \uniqid();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function load(string $body): self
|
||||||
|
{
|
||||||
|
$eol = "\r\n";
|
||||||
|
|
||||||
|
$sections = \explode('--' . $this->boundary, $body);
|
||||||
|
|
||||||
|
foreach ($sections as $section) {
|
||||||
|
if (empty($section)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strpos($section, $eol) === 0) {
|
||||||
|
$section = substr($section, \strlen($eol));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (substr($section, -2) === $eol) {
|
||||||
|
$section = substr($section, 0, -1 * \strlen($eol));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($section == '--') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$partChunks = \explode($eol . $eol, $section, 2);
|
||||||
|
|
||||||
|
if (\count($partChunks) < 2) {
|
||||||
|
continue; // Broken part
|
||||||
|
}
|
||||||
|
|
||||||
|
[ $partHeaders, $partBody ] = $partChunks;
|
||||||
|
$partHeaders = \explode($eol, $partHeaders);
|
||||||
|
|
||||||
|
$partName = "";
|
||||||
|
foreach ($partHeaders as $partHeader) {
|
||||||
|
if (!empty($partName)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$partHeaderArray = \explode(':', $partHeader, 2);
|
||||||
|
|
||||||
|
$partHeaderName = \strtolower($partHeaderArray[0] ?? '');
|
||||||
|
$partHeaderValue = $partHeaderArray[1] ?? '';
|
||||||
|
if ($partHeaderName == "content-disposition") {
|
||||||
|
$dispositionChunks = \explode("; ", $partHeaderValue);
|
||||||
|
foreach ($dispositionChunks as $dispositionChunk) {
|
||||||
|
$dispositionChunkValues = \explode("=", $dispositionChunk, 2);
|
||||||
|
if (\count($dispositionChunkValues) >= 2) {
|
||||||
|
if ($dispositionChunkValues[0] === "name") {
|
||||||
|
$partName = \trim($dispositionChunkValues[1], "\"");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($partName)) {
|
||||||
|
$this->parts[$partName] = $partBody;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function getParts(): array
|
||||||
|
{
|
||||||
|
return $this->parts ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getPart(string $key, mixed $default = ''): mixed
|
||||||
|
{
|
||||||
|
return $this->parts[$key] ?? $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPart(string $key, mixed $value): self
|
||||||
|
{
|
||||||
|
$this->parts[$key] = $value;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBoundary(): string
|
||||||
|
{
|
||||||
|
return $this->boundary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setBoundary(string $boundary): self
|
||||||
|
{
|
||||||
|
$this->boundary = $boundary;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exportHeader(): string
|
||||||
|
{
|
||||||
|
return 'multipart/form-data; boundary=' . $this->boundary;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function exportBody(): string
|
||||||
|
{
|
||||||
|
$eol = "\r\n";
|
||||||
|
$query = '--' . $this->boundary;
|
||||||
|
|
||||||
|
foreach ($this->parts as $key => $value) {
|
||||||
|
$query .= $eol . 'Content-Disposition: form-data; name="' . $key . '"';
|
||||||
|
|
||||||
|
if (\is_array($value)) {
|
||||||
|
$query .= $eol . 'Content-Type: application/json';
|
||||||
|
$value = \json_encode($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
$query .= $eol . $eol;
|
||||||
|
$query .= $value . $eol;
|
||||||
|
$query .= '--' . $this->boundary;
|
||||||
|
}
|
||||||
|
|
||||||
|
$query .= "--" . $eol;
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
}
|
|
@ -119,7 +119,7 @@ class Func extends Model
|
||||||
->addRule('version', [
|
->addRule('version', [
|
||||||
'type' => self::TYPE_STRING,
|
'type' => self::TYPE_STRING,
|
||||||
'description' => 'Version of Open Runtimes used for the function.',
|
'description' => 'Version of Open Runtimes used for the function.',
|
||||||
'default' => 'v3',
|
'default' => 'v4',
|
||||||
'example' => 'v2',
|
'example' => 'v2',
|
||||||
])
|
])
|
||||||
->addRule('installationId', [
|
->addRule('installationId', [
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
namespace Executor;
|
namespace Executor;
|
||||||
|
|
||||||
use Appwrite\Extend\Exception as AppwriteException;
|
use Appwrite\Extend\Exception as AppwriteException;
|
||||||
|
use Appwrite\Utopia\Fetch\BodyMultipart;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Utopia\System\System;
|
use Utopia\System\System;
|
||||||
|
|
||||||
|
@ -178,6 +179,7 @@ class Executor
|
||||||
string $method,
|
string $method,
|
||||||
array $headers,
|
array $headers,
|
||||||
string $runtimeEntrypoint = null,
|
string $runtimeEntrypoint = null,
|
||||||
|
bool $logging,
|
||||||
int $requestTimeout = null
|
int $requestTimeout = null
|
||||||
) {
|
) {
|
||||||
if (empty($headers['host'])) {
|
if (empty($headers['host'])) {
|
||||||
|
@ -189,7 +191,6 @@ class Executor
|
||||||
$params = [
|
$params = [
|
||||||
'runtimeId' => $runtimeId,
|
'runtimeId' => $runtimeId,
|
||||||
'variables' => $variables,
|
'variables' => $variables,
|
||||||
'body' => $body,
|
|
||||||
'timeout' => $timeout,
|
'timeout' => $timeout,
|
||||||
'path' => $path,
|
'path' => $path,
|
||||||
'method' => $method,
|
'method' => $method,
|
||||||
|
@ -201,15 +202,20 @@ class Executor
|
||||||
'memory' => $this->memory,
|
'memory' => $this->memory,
|
||||||
'version' => $version,
|
'version' => $version,
|
||||||
'runtimeEntrypoint' => $runtimeEntrypoint,
|
'runtimeEntrypoint' => $runtimeEntrypoint,
|
||||||
|
'logging' => $logging,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if(!empty($body)) {
|
||||||
|
$params['body'] = $body;
|
||||||
|
}
|
||||||
|
|
||||||
// Safety timeout. Executor has timeout, and open runtime has soft timeout.
|
// Safety timeout. Executor has timeout, and open runtime has soft timeout.
|
||||||
// This one shouldn't really happen, but prevents from unexpected networking behaviours.
|
// This one shouldn't really happen, but prevents from unexpected networking behaviours.
|
||||||
if ($requestTimeout == null) {
|
if ($requestTimeout == null) {
|
||||||
$requestTimeout = $timeout + 15;
|
$requestTimeout = $timeout + 15;
|
||||||
}
|
}
|
||||||
|
|
||||||
$response = $this->call(self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId ], $params, true, $requestTimeout);
|
$response = $this->call(self::METHOD_POST, $route, [ 'x-opr-runtime-id' => $runtimeId, 'content-type' => 'multipart/form-data', 'accept' => 'multipart/form-data' ], $params, true, $requestTimeout);
|
||||||
|
|
||||||
$status = $response['headers']['status-code'];
|
$status = $response['headers']['status-code'];
|
||||||
if ($status >= 400) {
|
if ($status >= 400) {
|
||||||
|
@ -217,6 +223,11 @@ class Executor
|
||||||
throw new \Exception($message, $status);
|
throw new \Exception($message, $status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$response['body']['headers'] = \json_decode($response['body']['headers'] ?? '{}', true);
|
||||||
|
$response['body']['statusCode'] = \intval($response['body']['statusCode'] ?? 500);
|
||||||
|
$response['body']['duration'] = \intval($response['body']['duration'] ?? 0);
|
||||||
|
$response['body']['startTime'] = \intval($response['body']['startTime'] ?? \microtime(true));
|
||||||
|
|
||||||
return $response['body'];
|
return $response['body'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -248,7 +259,13 @@ class Executor
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'multipart/form-data':
|
case 'multipart/form-data':
|
||||||
$query = $this->flatten($params);
|
$multipart = new BodyMultipart();
|
||||||
|
foreach ($params as $key => $value) {
|
||||||
|
$multipart->setPart($key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
$headers['content-type'] = $multipart->exportHeader();
|
||||||
|
$query = $multipart->exportBody();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -315,7 +332,16 @@ class Executor
|
||||||
$curlErrorMessage = curl_error($ch);
|
$curlErrorMessage = curl_error($ch);
|
||||||
|
|
||||||
if ($decode) {
|
if ($decode) {
|
||||||
switch (substr($responseType, 0, strpos($responseType, ';'))) {
|
$strpos = strpos($responseType, ';');
|
||||||
|
$strpos = \is_bool($strpos) ? \strlen($responseType) : $strpos;
|
||||||
|
switch (substr($responseType, 0, $strpos)) {
|
||||||
|
case 'multipart/form-data':
|
||||||
|
$boundary = \explode('boundary=', $responseHeaders['content-type'] ?? '')[1] ?? '';
|
||||||
|
$multipartResponse = new BodyMultipart($boundary);
|
||||||
|
$multipartResponse->load(\is_bool($responseBody) ? '' : $responseBody);
|
||||||
|
|
||||||
|
$responseBody = $multipartResponse->getParts();
|
||||||
|
break;
|
||||||
case 'application/json':
|
case 'application/json':
|
||||||
$json = json_decode($responseBody, true);
|
$json = json_decode($responseBody, true);
|
||||||
|
|
||||||
|
|
|
@ -163,7 +163,7 @@ class Client
|
||||||
* @return array
|
* @return array
|
||||||
* @throws Exception
|
* @throws Exception
|
||||||
*/
|
*/
|
||||||
public function call(string $method, string $path = '', array $headers = [], array $params = [], bool $decode = true): array
|
public function call(string $method, string $path = '', array $headers = [], mixed $params = [], bool $decode = true): array
|
||||||
{
|
{
|
||||||
$headers = array_merge($this->headers, $headers);
|
$headers = array_merge($this->headers, $headers);
|
||||||
$ch = curl_init($this->endpoint . $path . (($method == self::METHOD_GET && !empty($params)) ? '?' . http_build_query($params) : ''));
|
$ch = curl_init($this->endpoint . $path . (($method == self::METHOD_GET && !empty($params)) ? '?' . http_build_query($params) : ''));
|
||||||
|
@ -174,6 +174,7 @@ class Client
|
||||||
'application/json' => json_encode($params),
|
'application/json' => json_encode($params),
|
||||||
'multipart/form-data' => $this->flatten($params),
|
'multipart/form-data' => $this->flatten($params),
|
||||||
'application/graphql' => $params[0],
|
'application/graphql' => $params[0],
|
||||||
|
'text/plain' => $params,
|
||||||
default => http_build_query($params),
|
default => http_build_query($params),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -108,7 +108,6 @@ class HTTPTest extends Scope
|
||||||
'0.14.x',
|
'0.14.x',
|
||||||
];
|
];
|
||||||
|
|
||||||
// var_dump($files);
|
|
||||||
foreach ($files as $file) {
|
foreach ($files as $file) {
|
||||||
if (in_array($file, ['.', '..'])) {
|
if (in_array($file, ['.', '..'])) {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -1804,4 +1804,205 @@ class FunctionsCustomServerTest extends Scope
|
||||||
|
|
||||||
$this->assertEquals(204, $response['headers']['status-code']);
|
$this->assertEquals(204, $response['headers']['status-code']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testFunctionsDomainBianryResponse()
|
||||||
|
{
|
||||||
|
$timeout = 15;
|
||||||
|
$code = realpath(__DIR__ . '/../../../resources/functions') . "/php-binary-response/code.tar.gz";
|
||||||
|
$this->packageCode('php-binary-response');
|
||||||
|
|
||||||
|
$function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
], $this->getHeaders()), [
|
||||||
|
'functionId' => ID::unique(),
|
||||||
|
'name' => 'Test PHP Binary executions',
|
||||||
|
'runtime' => 'php-8.0',
|
||||||
|
'entrypoint' => 'index.php',
|
||||||
|
'timeout' => $timeout,
|
||||||
|
'execute' => ['any']
|
||||||
|
]);
|
||||||
|
|
||||||
|
$functionId = $function['body']['$id'] ?? '';
|
||||||
|
|
||||||
|
$this->assertEquals(201, $function['headers']['status-code']);
|
||||||
|
|
||||||
|
$rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
], $this->getHeaders()), [
|
||||||
|
'queries' => [
|
||||||
|
Query::equal('resourceId', [$functionId])->toString(),
|
||||||
|
Query::equal('resourceType', ['function'])->toString(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||||
|
$this->assertEquals(1, $rules['body']['total']);
|
||||||
|
$this->assertCount(1, $rules['body']['rules']);
|
||||||
|
$this->assertNotEmpty($rules['body']['rules'][0]['domain']);
|
||||||
|
|
||||||
|
$domain = $rules['body']['rules'][0]['domain'];
|
||||||
|
|
||||||
|
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
|
||||||
|
'content-type' => 'multipart/form-data',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
], $this->getHeaders()), [
|
||||||
|
'entrypoint' => 'index.php',
|
||||||
|
'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
|
||||||
|
'activate' => true
|
||||||
|
]);
|
||||||
|
|
||||||
|
$deploymentId = $deployment['body']['$id'] ?? '';
|
||||||
|
$this->assertEquals(202, $deployment['headers']['status-code']);
|
||||||
|
|
||||||
|
// Poll until deployment is built
|
||||||
|
while (true) {
|
||||||
|
$deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (
|
||||||
|
$deployment['headers']['status-code'] >= 400
|
||||||
|
|| \in_array($deployment['body']['status'], ['ready', 'failed'])
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
\sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
], $this->getHeaders()), []);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $deployment['headers']['status-code']);
|
||||||
|
|
||||||
|
// Wait a little for activation to finish
|
||||||
|
sleep(5);
|
||||||
|
|
||||||
|
$proxyClient = new Client();
|
||||||
|
$proxyClient->setEndpoint('http://' . $domain);
|
||||||
|
|
||||||
|
$response = $proxyClient->call(Client::METHOD_GET, '/', [], [], false);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response['headers']['status-code']);
|
||||||
|
$this->assertNotEmpty($response['body']);
|
||||||
|
$bytes = unpack('C*byte', $response['body']);
|
||||||
|
$this->assertCount(3, $bytes);
|
||||||
|
$this->assertEquals(0, $bytes['byte1']);
|
||||||
|
$this->assertEquals(10, $bytes['byte2']);
|
||||||
|
$this->assertEquals(255, $bytes['byte3']);
|
||||||
|
|
||||||
|
// Cleanup : Delete function
|
||||||
|
$response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||||
|
], []);
|
||||||
|
|
||||||
|
$this->assertEquals(204, $response['headers']['status-code']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testFunctionsDomainBianryRequest()
|
||||||
|
{
|
||||||
|
$timeout = 15;
|
||||||
|
$code = realpath(__DIR__ . '/../../../resources/functions') . "/php-binary-request/code.tar.gz";
|
||||||
|
$this->packageCode('php-binary-request');
|
||||||
|
|
||||||
|
$function = $this->client->call(Client::METHOD_POST, '/functions', array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
], $this->getHeaders()), [
|
||||||
|
'functionId' => ID::unique(),
|
||||||
|
'name' => 'Test PHP Binary executions',
|
||||||
|
'runtime' => 'php-8.0',
|
||||||
|
'entrypoint' => 'index.php',
|
||||||
|
'timeout' => $timeout,
|
||||||
|
'execute' => ['any']
|
||||||
|
]);
|
||||||
|
|
||||||
|
$functionId = $function['body']['$id'] ?? '';
|
||||||
|
|
||||||
|
$this->assertEquals(201, $function['headers']['status-code']);
|
||||||
|
|
||||||
|
$rules = $this->client->call(Client::METHOD_GET, '/proxy/rules', array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
], $this->getHeaders()), [
|
||||||
|
'queries' => [
|
||||||
|
Query::equal('resourceId', [$functionId])->toString(),
|
||||||
|
Query::equal('resourceType', ['function'])->toString(),
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $rules['headers']['status-code']);
|
||||||
|
$this->assertEquals(1, $rules['body']['total']);
|
||||||
|
$this->assertCount(1, $rules['body']['rules']);
|
||||||
|
$this->assertNotEmpty($rules['body']['rules'][0]['domain']);
|
||||||
|
|
||||||
|
$domain = $rules['body']['rules'][0]['domain'];
|
||||||
|
|
||||||
|
$deployment = $this->client->call(Client::METHOD_POST, '/functions/' . $functionId . '/deployments', array_merge([
|
||||||
|
'content-type' => 'multipart/form-data',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
], $this->getHeaders()), [
|
||||||
|
'entrypoint' => 'index.php',
|
||||||
|
'code' => new CURLFile($code, 'application/x-gzip', basename($code)),
|
||||||
|
'activate' => true
|
||||||
|
]);
|
||||||
|
|
||||||
|
$deploymentId = $deployment['body']['$id'] ?? '';
|
||||||
|
$this->assertEquals(202, $deployment['headers']['status-code']);
|
||||||
|
|
||||||
|
// Poll until deployment is built
|
||||||
|
while (true) {
|
||||||
|
$deployment = $this->client->call(Client::METHOD_GET, '/functions/' . $function['body']['$id'] . '/deployments/' . $deploymentId, [
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (
|
||||||
|
$deployment['headers']['status-code'] >= 400
|
||||||
|
|| \in_array($deployment['body']['status'], ['ready', 'failed'])
|
||||||
|
) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
\sleep(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$deployment = $this->client->call(Client::METHOD_PATCH, '/functions/' . $functionId . '/deployments/' . $deploymentId, array_merge([
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
], $this->getHeaders()), []);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $deployment['headers']['status-code']);
|
||||||
|
|
||||||
|
// Wait a little for activation to finish
|
||||||
|
sleep(5);
|
||||||
|
|
||||||
|
$proxyClient = new Client();
|
||||||
|
$proxyClient->setEndpoint('http://' . $domain);
|
||||||
|
|
||||||
|
$bytes = pack('C*', ...[0,20,255]);
|
||||||
|
|
||||||
|
$response = $proxyClient->call(Client::METHOD_POST, '/', [ 'content-type' => 'text/plain' ], $bytes, false);
|
||||||
|
|
||||||
|
$this->assertEquals(200, $response['headers']['status-code']);
|
||||||
|
$this->assertEquals(\md5($bytes), $response['body']);
|
||||||
|
|
||||||
|
// Cleanup : Delete function
|
||||||
|
$response = $this->client->call(Client::METHOD_DELETE, '/functions/' . $functionId, [
|
||||||
|
'content-type' => 'application/json',
|
||||||
|
'x-appwrite-project' => $this->getProject()['$id'],
|
||||||
|
'x-appwrite-key' => $this->getProject()['apiKey'],
|
||||||
|
], []);
|
||||||
|
|
||||||
|
$this->assertEquals(204, $response['headers']['status-code']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
6
tests/resources/functions/php-binary-request/index.php
Normal file
6
tests/resources/functions/php-binary-request/index.php
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return function ($context) {
|
||||||
|
$hash = md5($context->req->bodyBinary);
|
||||||
|
return $context->res->send($hash);
|
||||||
|
};
|
6
tests/resources/functions/php-binary-response/index.php
Normal file
6
tests/resources/functions/php-binary-response/index.php
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
return function ($context) {
|
||||||
|
$bytes = pack('C*', ...[0, 10, 255]);
|
||||||
|
return $context->res->binary($bytes);
|
||||||
|
};
|
Loading…
Reference in a new issue