diff --git a/Caddyfile b/Caddyfile index 032e5d32529..7472251129e 100644 --- a/Caddyfile +++ b/Caddyfile @@ -3,11 +3,35 @@ # # THIS IS AN EXPERIMENTAL FEATURE # DO NOT USE THIS IN PRODUCTION, YOU HAVE BEEN WARNED. +{ + metrics + frankenphp { + num_threads 192 + max_threads 256 + # max_requests 500 + } +} localhost { php_server { worker { file index.php + num 32 + watch + } + worker { + file remote.php + num 32 + watch + } + worker { + file ocs/v1.php + num 32 + watch + } + worker { + file ocs/v2.php + num 32 watch } } diff --git a/index.php b/index.php index cb59b75b3f7..bfad3dab6d6 100644 --- a/index.php +++ b/index.php @@ -10,7 +10,6 @@ declare(strict_types=1); require_once __DIR__ . '/lib/versioncheck.php'; -use OC\Files\Filesystem; use OC\ServiceUnavailableException; use OC\User\LoginException; use OCP\HintException; @@ -24,23 +23,11 @@ require_once __DIR__ . '/lib/OC.php'; \OC::boot(); -function resetStaticProperties(): void { - // FIXME needed because these use a static var - \OC_Hook::clear(); - \OC_Util::$styles = []; - \OC_Util::$headers = []; - \OC_User::setIncognitoMode(false); - \OC_User::$_setupedBackends = []; - \OC_App::reset(); - \OC_Helper::reset(); - Filesystem::reset(); -} - -$handler = static function (): void { +\OC::handleRequests(static function (): void { try { - resetStaticProperties(); - OC::init(); - OC::handleRequest(); + \OC::resetStaticProperties(); + \OC::init(); + \OC::handleRequest(); } catch (ServiceUnavailableException $ex) { Server::get(LoggerInterface::class)->error($ex->getMessage(), [ 'app' => 'index', @@ -124,20 +111,4 @@ $handler = static function (): void { } Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 500); } -}; - -if (function_exists('frankenphp_handle_request') && isset($_SERVER['FRANKENPHP_WORKER']) && $_SERVER['FRANKENPHP_WORKER'] === '1') { - $maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0); - for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests) { - $keepRunning = \frankenphp_handle_request($handler); - - // Call the garbage collector to reduce the chances of it being triggered in the middle of a page generation - gc_collect_cycles(); - - if (!$keepRunning) { - break; - } - } -} else { - $handler(); -} +}); diff --git a/lib/OC.php b/lib/OC.php index b2b4f65fee0..a7048f7b622 100644 --- a/lib/OC.php +++ b/lib/OC.php @@ -7,6 +7,7 @@ declare(strict_types=1); * SPDX-License-Identifier: AGPL-3.0-only */ +use OC\Files\Filesystem; use OC\NavigationManager; use OC\Profiler\BuiltInProfiler; use OC\Security\CSP\ContentSecurityPolicyNonceManager; @@ -1332,4 +1333,40 @@ class OC { return false; } } + + /** + * @internal + */ + public static function resetStaticProperties(): void { + // FIXME needed because these use a static var + \OC_Hook::clear(); + \OC_Util::$styles = []; + \OC_Util::$headers = []; + \OC_User::setIncognitoMode(false); + \OC_User::$_setupedBackends = []; + \OC_App::reset(); + \OC_Helper::reset(); + Filesystem::reset(); + } + + /** + * @internal + */ + public static function handleRequests(callable $handler): void { + if (function_exists('frankenphp_handle_request') && isset($_SERVER['FRANKENPHP_WORKER']) && $_SERVER['FRANKENPHP_WORKER'] === '1') { + $maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0); + for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests) { + $keepRunning = \frankenphp_handle_request($handler); + + // Call the garbage collector to reduce the chances of it being triggered in the middle of a page generation + gc_collect_cycles(); + + if (!$keepRunning) { + break; + } + } + } else { + $handler(); + } + } } diff --git a/ocs/v1.php b/ocs/v1.php index f97e69980f5..5816aa32076 100644 --- a/ocs/v1.php +++ b/ocs/v1.php @@ -8,9 +8,6 @@ declare(strict_types=1); * SPDX-License-Identifier: AGPL-3.0-only */ -require_once __DIR__ . '/../lib/versioncheck.php'; -require_once __DIR__ . '/../lib/base.php'; - use OC\OCS\ApiHelper; use OC\Route\Router; use OC\SystemConfig; @@ -28,75 +25,84 @@ use Psr\Log\LoggerInterface; use Symfony\Component\Routing\Exception\MethodNotAllowedException; use Symfony\Component\Routing\Exception\ResourceNotFoundException; -$request = Server::get(IRequest::class); +require_once __DIR__ . '/../lib/versioncheck.php'; +require_once __DIR__ . '/../lib/OC.php'; + +\OC::boot(); -$serveAppApiDuringMaintenance = false; -if (!Util::needUpgrade() && Server::get(IConfig::class)->getSystemValueBool('maintenance')) { - $pathInfo = $request->getPathInfo(); - // AppAPI must keep serving HaRP traffic (signed OCS calls and ExApp callbacks) - $serveAppApiDuringMaintenance - = ($pathInfo === '/apps/app_api' || str_starts_with($pathInfo, '/apps/app_api/')) - && Server::get(IAppManager::class)->isEnabledForAnyone('app_api'); -} +\OC::handleRequests(static function () { + \OC::resetStaticProperties(); + \OC::init(); + $request = Server::get(IRequest::class); -if ((Util::needUpgrade() - || (Server::get(IConfig::class)->getSystemValueBool('maintenance') && !$serveAppApiDuringMaintenance)) - && $request->getPathInfo() !== '/core/update') { - // since the behavior of apps or remotes are unpredictable during - // an upgrade, return a 503 directly - ApiHelper::respond(503, 'Service unavailable', ['X-Nextcloud-Maintenance-Mode' => '1'], 503); - exit; -} + $serveAppApiDuringMaintenance = false; + if (!Util::needUpgrade() && Server::get(IConfig::class)->getSystemValueBool('maintenance')) { + $pathInfo = $request->getPathInfo(); + // AppAPI must keep serving HaRP traffic (signed OCS calls and ExApp callbacks) + $serveAppApiDuringMaintenance + = ($pathInfo === '/apps/app_api' || str_starts_with($pathInfo, '/apps/app_api/')) + && Server::get(IAppManager::class)->isEnabledForAnyone('app_api'); + } -/* - * Try the appframework routes - */ -try { - $appManager = Server::get(IAppManager::class); - $appManager->loadApps(['session']); - $appManager->loadApps(['authentication']); - $appManager->loadApps(['extended_authentication']); + if ((Util::needUpgrade() + || (Server::get(IConfig::class)->getSystemValueBool('maintenance') && !$serveAppApiDuringMaintenance)) + && $request->getPathInfo() !== '/core/update') { + // since the behavior of apps or remotes are unpredictable during + // an upgrade, return a 503 directly + ApiHelper::respond(503, 'Service unavailable', ['X-Nextcloud-Maintenance-Mode' => '1'], 503); + exit; + } - $request->throwDecodingExceptionIfAny(); + /* + * Try the appframework routes + */ + try { + $appManager = Server::get(IAppManager::class); + $appManager->loadApps(['session']); + $appManager->loadApps(['authentication']); + $appManager->loadApps(['extended_authentication']); - if ($request->getPathInfo() !== '/core/update') { - if ($serveAppApiDuringMaintenance) { - // loadApps() below is a no-op during maintenance, load app_api explicitly - $appManager->loadApp('app_api'); - } - // load all apps to get all api routes properly setup - // FIXME: this should ideally appear after handleLogin but will cause - // side effects in existing apps - $appManager->loadApps(); - if (!Server::get(IUserSession::class)->isLoggedIn()) { - OC::handleLogin($request); + $request->throwDecodingExceptionIfAny(); + + if ($request->getPathInfo() !== '/core/update') { + if ($serveAppApiDuringMaintenance) { + // loadApps() below is a no-op during maintenance, load app_api explicitly + $appManager->loadApp('app_api'); + } + // load all apps to get all api routes properly setup + // FIXME: this should ideally appear after handleLogin but will cause + // side effects in existing apps + $appManager->loadApps(); + if (!Server::get(IUserSession::class)->isLoggedIn()) { + OC::handleLogin($request); + } + } else { + $appManager->loadApps(['core']); } - } else { - $appManager->loadApps(['core']); - } - Server::get(Router::class)->match('/ocsapp' . $request->getRawPathInfo()); -} catch (MaxDelayReached $ex) { - ApiHelper::respond(Http::STATUS_TOO_MANY_REQUESTS, $ex->getMessage()); -} catch (ResourceNotFoundException $e) { - $txt = 'Invalid query, please check the syntax. API specifications are here:' - . ' http://www.freedesktop.org/wiki/Specifications/open-collaboration-services.' . "\n"; - ApiHelper::respond(OCSController::RESPOND_NOT_FOUND, $txt); -} catch (MethodNotAllowedException $e) { - ApiHelper::setContentType(); - http_response_code(405); -} catch (LoginException $e) { - ApiHelper::respond(OCSController::RESPOND_UNAUTHORISED, 'Unauthorised'); -} catch (\Exception $e) { - Server::get(LoggerInterface::class)->error($e->getMessage(), ['exception' => $e]); + Server::get(Router::class)->match('/ocsapp' . $request->getRawPathInfo()); + } catch (MaxDelayReached $ex) { + ApiHelper::respond(Http::STATUS_TOO_MANY_REQUESTS, $ex->getMessage()); + } catch (ResourceNotFoundException $e) { + $txt = 'Invalid query, please check the syntax. API specifications are here:' + . ' http://www.freedesktop.org/wiki/Specifications/open-collaboration-services.' . "\n"; + ApiHelper::respond(OCSController::RESPOND_NOT_FOUND, $txt); + } catch (MethodNotAllowedException $e) { + ApiHelper::setContentType(); + http_response_code(405); + } catch (LoginException $e) { + ApiHelper::respond(OCSController::RESPOND_UNAUTHORISED, 'Unauthorised'); + } catch (\Exception $e) { + Server::get(LoggerInterface::class)->error($e->getMessage(), ['exception' => $e]); - $txt = 'Internal Server Error' . "\n"; - try { - if (Server::get(SystemConfig::class)->getValue('debug', false)) { - $txt .= $e->getMessage(); + $txt = 'Internal Server Error' . "\n"; + try { + if (Server::get(SystemConfig::class)->getValue('debug', false)) { + $txt .= $e->getMessage(); + } + } catch (\Throwable $e) { + // Just to be save } - } catch (\Throwable $e) { - // Just to be save + ApiHelper::respond(OCSController::RESPOND_SERVER_ERROR, $txt); } - ApiHelper::respond(OCSController::RESPOND_SERVER_ERROR, $txt); -} +}); diff --git a/remote.php b/remote.php index 27fb703c87c..2e0d0d69706 100644 --- a/remote.php +++ b/remote.php @@ -1,24 +1,27 @@ getAppValue('core', 'remote_' . $service); } -try { - require_once __DIR__ . '/lib/base.php'; +require_once __DIR__ . '/lib/OC.php'; - // All resources served via the DAV endpoint should have the strictest possible - // policy. Exempted from this is the SabreDAV browser plugin which overwrites - // this policy with a softer one if debug mode is enabled. - header("Content-Security-Policy: default-src 'none';"); +\OC::boot(); - if (Util::needUpgrade()) { - // since the behavior of apps or remotes are unpredictable during - // an upgrade, return a 503 directly - throw new RemoteException('Service unavailable', 503); - } +\OC::handleRequests(static function () { + try { + \OC::resetStaticProperties(); + \OC::init(); + + // All resources served via the DAV endpoint should have the strictest possible + // policy. Exempted from this is the SabreDAV browser plugin which overwrites + // this policy with a softer one if debug mode is enabled. + header("Content-Security-Policy: default-src 'none';"); + + if (Util::needUpgrade()) { + // since the behavior of apps or remotes are unpredictable during + // an upgrade, return a 503 directly + throw new RemoteException('Service unavailable', 503); + } - $request = \OCP\Server::get(IRequest::class); - $pathInfo = $request->getPathInfo(); - if ($pathInfo === false || $pathInfo === '') { - throw new RemoteException('Path not found', 404); - } - if (!$pos = strpos($pathInfo, '/', 1)) { - $pos = strlen($pathInfo); - } - $service = substr($pathInfo, 1, $pos - 1); + $request = \OCP\Server::get(IRequest::class); + $pathInfo = $request->getPathInfo(); + if ($pathInfo === false || $pathInfo === '') { + throw new RemoteException('Path not found', 404); + } + if (!$pos = strpos($pathInfo, '/', 1)) { + $pos = strlen($pathInfo); + } + $service = substr($pathInfo, 1, $pos - 1); - $file = resolveService($service); + $file = resolveService($service); - if (is_null($file)) { - throw new RemoteException('Path not found', 404); - } + if (is_null($file)) { + throw new RemoteException('Path not found', 404); + } - $file = ltrim($file, '/'); - - $parts = explode('/', $file, 2); - $app = $parts[0]; - - // Load all required applications - \OC::$REQUESTEDAPP = $app; - $appManager = \OCP\Server::get(IAppManager::class); - $appManager->loadApps(['authentication']); - $appManager->loadApps(['extended_authentication']); - $appManager->loadApps(['filesystem', 'logging']); - - switch ($app) { - case 'core': - $file = OC::$SERVERROOT . '/' . $file; - break; - default: - if (!$appManager->isEnabledForUser($app)) { - throw new RemoteException('App not installed: ' . $app); - } - $appManager->loadApp($app); - $file = $appManager->getAppPath($app) . '/' . ($parts[1] ?? ''); - break; + $file = ltrim($file, '/'); + + $parts = explode('/', $file, 2); + $app = $parts[0]; + + // Load all required applications + \OC::$REQUESTEDAPP = $app; + $appManager = \OCP\Server::get(IAppManager::class); + $appManager->loadApps(['authentication']); + $appManager->loadApps(['extended_authentication']); + $appManager->loadApps(['filesystem', 'logging']); + + switch ($app) { + case 'core': + $file = OC::$SERVERROOT . '/' . $file; + break; + default: + if (!$appManager->isEnabledForUser($app)) { + throw new RemoteException('App not installed: ' . $app); + } + $appManager->loadApp($app); + $file = $appManager->getAppPath($app) . '/' . ($parts[1] ?? ''); + break; + } + $baseuri = OC::$WEBROOT . '/remote.php/' . $service . '/'; + require_once $file; + } catch (Exception $ex) { + handleException($ex); + } catch (Error $e) { + handleException($e); } - $baseuri = OC::$WEBROOT . '/remote.php/' . $service . '/'; - require_once $file; -} catch (Exception $ex) { - handleException($ex); -} catch (Error $e) { - handleException($e); -} +});