appwrite/app/http.php

336 lines
12 KiB
PHP
Raw Normal View History

2020-06-26 09:54:37 +00:00
<?php
2024-10-08 07:54:40 +00:00
require_once __DIR__ . '/../vendor/autoload.php';
2020-06-26 09:54:37 +00:00
2024-03-06 17:34:21 +00:00
use Appwrite\Utopia\Request;
use Appwrite\Utopia\Response;
2024-10-08 07:54:40 +00:00
use Swoole\Constant;
use Swoole\Http\Request as SwooleRequest;
use Swoole\Http\Response as SwooleResponse;
use Swoole\Http\Server;
use Swoole\Process;
2024-08-23 17:02:54 +00:00
use Utopia\Abuse\Adapters\Database\TimeLimit;
2024-10-08 07:54:40 +00:00
use Utopia\App;
2024-03-06 17:34:21 +00:00
use Utopia\Audit\Audit;
2020-06-26 12:27:58 +00:00
use Utopia\CLI\Console;
2021-05-04 10:24:08 +00:00
use Utopia\Config\Config;
2024-03-06 17:34:21 +00:00
use Utopia\Database\Database;
use Utopia\Database\Document;
2022-12-14 15:42:25 +00:00
use Utopia\Database\Helpers\ID;
2022-12-14 16:04:06 +00:00
use Utopia\Database\Helpers\Permission;
use Utopia\Database\Helpers\Role;
2021-07-25 14:51:04 +00:00
use Utopia\Database\Validator\Authorization;
2024-10-08 07:54:40 +00:00
use Utopia\Logger\Log;
use Utopia\Logger\Log\User;
use Utopia\Pools\Group;
use Utopia\Swoole\Files;
2024-04-01 11:02:47 +00:00
use Utopia\System\System;
2020-06-26 09:54:37 +00:00
2024-10-08 07:54:40 +00:00
$http = new Server(
host: "0.0.0.0",
port: System::getEnv('PORT', 80),
mode: SWOOLE_PROCESS,
);
2020-07-01 08:55:14 +00:00
2024-05-09 09:41:02 +00:00
$payloadSize = 12 * (1024 * 1024); // 12MB - adding slight buffer for headers and other data that might be sent with the payload - update later with valid testing
2024-04-01 11:02:47 +00:00
$workerNumber = swoole_cpu_num() * intval(System::getEnv('_APP_WORKER_PER_CORE', 6));
2022-09-30 10:32:58 +00:00
2024-10-08 07:54:40 +00:00
$http
->set([
'worker_num' => $workerNumber,
'open_http2_protocol' => true,
'http_compression' => true,
'http_compression_level' => 6,
'package_max_length' => $payloadSize,
'buffer_output_size' => $payloadSize,
]);
$http->on(Constant::EVENT_WORKER_START, function ($server, $workerId) {
Console::success('Worker ' . ++$workerId . ' started successfully');
});
$http->on(Constant::EVENT_BEFORE_RELOAD, function ($server, $workerId) {
Console::success('Starting reload...');
});
$http->on(Constant::EVENT_AFTER_RELOAD, function ($server, $workerId) {
Console::success('Reload completed...');
});
include __DIR__ . '/controllers/general.php';
$http->on(Constant::EVENT_START, function (Server $http) use ($payloadSize, $register) {
$app = new App('UTC');
go(function () use ($register, $app) {
$pools = $register->get('pools');
/** @var Group $pools */
App::setResource('pools', fn () => $pools);
2022-08-02 18:56:45 +00:00
2024-10-08 07:54:40 +00:00
// wait for database to be ready
$attempts = 0;
$max = 10;
$sleep = 1;
2021-07-20 19:11:54 +00:00
2024-10-08 07:54:40 +00:00
do {
try {
2024-10-08 07:54:40 +00:00
$attempts++;
$dbForConsole = $app->getResource('dbForConsole');
/** @var Utopia\Database\Database $dbForConsole */
break; // leave the do-while if successful
} catch (\Throwable $e) {
2024-10-08 07:54:40 +00:00
Console::warning("Database not ready. Retrying connection ({$attempts})...");
if ($attempts >= $max) {
throw new \Exception('Failed to connect to database: ' . $e->getMessage());
}
sleep($sleep);
2022-01-10 06:31:33 +00:00
}
2024-10-08 07:54:40 +00:00
} while ($attempts < $max);
2022-06-22 21:11:42 +00:00
2024-10-08 07:54:40 +00:00
Console::success('[Setup] - Server database init started...');
2021-07-04 15:14:39 +00:00
2024-10-08 07:54:40 +00:00
try {
Console::success('[Setup] - Creating database: appwrite...');
$dbForConsole->create();
} catch (\Throwable $e) {
Console::success('[Setup] - Skip: metadata table already exists');
}
2021-07-04 12:05:46 +00:00
2024-10-08 07:54:40 +00:00
if ($dbForConsole->getCollection(Audit::COLLECTION)->isEmpty()) {
$audit = new Audit($dbForConsole);
$audit->setup();
}
2022-01-10 06:31:33 +00:00
2024-10-08 07:54:40 +00:00
if ($dbForConsole->getCollection(TimeLimit::COLLECTION)->isEmpty()) {
$adapter = new TimeLimit("", 0, 1, $dbForConsole);
$adapter->setup();
}
2024-10-08 07:54:40 +00:00
/** @var array $collections */
$collections = Config::getParam('collections', []);
$consoleCollections = $collections['console'];
foreach ($consoleCollections as $key => $collection) {
if (($collection['$collection'] ?? '') !== Database::METADATA) {
continue;
}
if (!$dbForConsole->getCollection($key)->isEmpty()) {
continue;
}
2022-05-23 14:54:50 +00:00
2024-10-08 07:54:40 +00:00
Console::success('[Setup] - Creating collection: ' . $collection['$id'] . '...');
$attributes = [];
$indexes = [];
foreach ($collection['attributes'] as $attribute) {
$attributes[] = new Document([
'$id' => ID::custom($attribute['$id']),
'type' => $attribute['type'],
'size' => $attribute['size'],
'required' => $attribute['required'],
'signed' => $attribute['signed'],
'array' => $attribute['array'],
'filters' => $attribute['filters'],
'default' => $attribute['default'] ?? null,
'format' => $attribute['format'] ?? ''
]);
2021-12-23 09:33:44 +00:00
}
2022-05-23 14:54:50 +00:00
2024-10-08 07:54:40 +00:00
foreach ($collection['indexes'] as $index) {
$indexes[] = new Document([
'$id' => ID::custom($index['$id']),
'type' => $index['type'],
'attributes' => $index['attributes'],
'lengths' => $index['lengths'],
'orders' => $index['orders'],
]);
}
2021-12-27 10:35:51 +00:00
2024-10-08 07:54:40 +00:00
$dbForConsole->createCollection($key, $attributes, $indexes);
}
2021-12-21 15:01:40 +00:00
2024-10-08 07:54:40 +00:00
if ($dbForConsole->getDocument('buckets', 'default')->isEmpty() && !$dbForConsole->exists($dbForConsole->getDatabase(), 'bucket_1')) {
Console::success('[Setup] - Creating default bucket...');
$dbForConsole->createDocument('buckets', new Document([
'$id' => ID::custom('default'),
'$collection' => ID::custom('buckets'),
'name' => 'Default',
'maximumFileSize' => (int) System::getEnv('_APP_STORAGE_LIMIT', 0), // 10MB
'allowedFileExtensions' => [],
'enabled' => true,
'compression' => 'gzip',
'encryption' => true,
'antivirus' => true,
'fileSecurity' => true,
'$permissions' => [
Permission::create(Role::any()),
Permission::read(Role::any()),
Permission::update(Role::any()),
Permission::delete(Role::any()),
],
'search' => 'buckets Default',
]));
$bucket = $dbForConsole->getDocument('buckets', 'default');
Console::success('[Setup] - Creating files collection for default bucket...');
$files = $collections['buckets']['files'] ?? [];
if (empty($files)) {
throw new Exception('Files collection is not configured.');
}
2021-12-21 15:01:40 +00:00
2024-10-08 07:54:40 +00:00
$attributes = [];
$indexes = [];
foreach ($files['attributes'] as $attribute) {
$attributes[] = new Document([
'$id' => ID::custom($attribute['$id']),
'type' => $attribute['type'],
'size' => $attribute['size'],
'required' => $attribute['required'],
'signed' => $attribute['signed'],
'array' => $attribute['array'],
'filters' => $attribute['filters'],
'default' => $attribute['default'] ?? null,
'format' => $attribute['format'] ?? ''
]);
2021-12-21 15:01:40 +00:00
}
2024-10-08 07:54:40 +00:00
foreach ($files['indexes'] as $index) {
$indexes[] = new Document([
'$id' => ID::custom($index['$id']),
'type' => $index['type'],
'attributes' => $index['attributes'],
'lengths' => $index['lengths'],
'orders' => $index['orders'],
]);
}
2021-12-21 15:01:40 +00:00
2024-10-08 07:54:40 +00:00
$dbForConsole->createCollection('bucket_' . $bucket->getInternalId(), $attributes, $indexes);
2021-12-21 15:01:40 +00:00
}
2024-10-08 07:54:40 +00:00
$pools->reclaim();
Console::success('[Setup] - Server database init completed...');
2024-10-01 14:30:47 +00:00
});
2021-12-21 15:01:40 +00:00
2024-10-08 07:54:40 +00:00
Console::success('Server started successfully (max payload is ' . number_format($payloadSize) . ' bytes)');
Console::info("Master pid {$http->master_pid}, manager pid {$http->manager_pid}");
// listen ctrl + c
Process::signal(2, function () use ($http) {
Console::log('Stop by Ctrl+C');
$http->shutdown();
2024-10-01 14:30:47 +00:00
});
2024-10-08 07:54:40 +00:00
});
$http->on('request', function (SwooleRequest $swooleRequest, SwooleResponse $swooleResponse) use ($register) {
App::setResource('swooleRequest', fn () => $swooleRequest);
App::setResource('swooleResponse', fn () => $swooleResponse);
$request = new Request($swooleRequest);
$response = new Response($swooleResponse);
if (Files::isFileLoaded($request->getURI())) {
$time = (60 * 60 * 24 * 365 * 2); // 45 days cache
$response
->setContentType(Files::getFileMimeType($request->getURI()))
->addHeader('Cache-Control', 'public, max-age=' . $time)
->addHeader('Expires', \date('D, d M Y H:i:s', \time() + $time) . ' GMT') // 45 days cache
->send(Files::getFileContents($request->getURI()));
return;
}
$app = new App('UTC');
$pools = $register->get('pools');
App::setResource('pools', fn () => $pools);
try {
Authorization::cleanRoles();
Authorization::setRole(Role::any()->toString());
$app->run($request, $response);
} catch (\Throwable $th) {
$version = System::getEnv('_APP_VERSION', 'UNKNOWN');
$logger = $app->getResource("logger");
if ($logger) {
try {
/** @var Utopia\Database\Document $user */
$user = $app->getResource('user');
} catch (\Throwable $_th) {
// All good, user is optional information for logger
}
$route = $app->getRoute();
$log = $app->getResource("log");
if (isset($user) && !$user->isEmpty()) {
$log->setUser(new User($user->getId()));
}
$log->setNamespace("http");
$log->setServer(\gethostname());
$log->setVersion($version);
$log->setType(Log::TYPE_ERROR);
$log->setMessage($th->getMessage());
$log->addTag('method', $route->getMethod());
$log->addTag('url', $route->getPath());
$log->addTag('verboseType', get_class($th));
$log->addTag('code', $th->getCode());
// $log->addTag('projectId', $project->getId()); // TODO: Figure out how to get ProjectID, if it becomes relevant
$log->addTag('hostname', $request->getHostname());
$log->addTag('locale', (string)$request->getParam('locale', $request->getHeader('x-appwrite-locale', '')));
$log->addExtra('file', $th->getFile());
$log->addExtra('line', $th->getLine());
$log->addExtra('trace', $th->getTraceAsString());
$log->addExtra('roles', Authorization::getRoles());
$action = $route->getLabel("sdk.namespace", "UNKNOWN_NAMESPACE") . '.' . $route->getLabel("sdk.method", "UNKNOWN_METHOD");
$log->setAction($action);
$isProduction = System::getEnv('_APP_ENV', 'development') === 'production';
$log->setEnvironment($isProduction ? Log::ENVIRONMENT_PRODUCTION : Log::ENVIRONMENT_STAGING);
try {
$responseCode = $logger->addLog($log);
Console::info('Error log pushed with status code: ' . $responseCode);
} catch (Throwable $th) {
Console::error('Error pushing log: ' . $th->getMessage());
}
}
Console::error('[Error] Type: ' . get_class($th));
Console::error('[Error] Message: ' . $th->getMessage());
Console::error('[Error] File: ' . $th->getFile());
Console::error('[Error] Line: ' . $th->getLine());
$swooleResponse->setStatusCode(500);
$output = ((App::isDevelopment())) ? [
'message' => 'Error: ' . $th->getMessage(),
'code' => 500,
'file' => $th->getFile(),
'line' => $th->getLine(),
'trace' => $th->getTrace(),
'version' => $version,
] : [
'message' => 'Error: Server Error',
'code' => 500,
'version' => $version,
];
$swooleResponse->end(\json_encode($output));
} finally {
$pools->reclaim();
}
});
2020-06-26 09:54:37 +00:00
2022-05-23 14:54:50 +00:00
$http->start();