From 7ea96244363689e8edaf16983b73f954dc143f48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Tue, 9 Jun 2026 17:20:40 +0200 Subject: [PATCH 1/6] feat: Add frankenphp worker support for more endpoints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- Caddyfile | 24 ++++++++++ index.php | 39 ++------------- lib/OC.php | 37 +++++++++++++++ ocs/v1.php | 136 ++++++++++++++++++++++++++++------------------------- remote.php | 126 ++++++++++++++++++++++++++----------------------- 5 files changed, 205 insertions(+), 157 deletions(-) diff --git a/Caddyfile b/Caddyfile index 032e5d32529c1..7472251129e62 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 d4bdd263f79f3..69e30049db688 100644 --- a/index.php +++ b/index.php @@ -10,7 +10,6 @@ require_once __DIR__ . '/lib/versioncheck.php'; -use OC\Files\Filesystem; use OC\ServiceUnavailableException; use OC\User\LoginException; use OCP\HintException; @@ -24,23 +23,11 @@ \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 () { +\OC::handleRequests(static function () { 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 @@ function resetStaticProperties(): 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 a39f15f0bba4b..8d67144e9046c 100644 --- a/lib/OC.php +++ b/lib/OC.php @@ -7,6 +7,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +use OC\Files\Filesystem; use OC\Profiler\BuiltInProfiler; use OC\Security\CSP\ContentSecurityPolicyNonceManager; use OC\Share20\GroupDeletedListener; @@ -1338,4 +1339,40 @@ protected static function tryAppAPILogin(OCP\IRequest $request): bool { 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 f97e69980f5c1..5816aa3207613 100644 --- a/ocs/v1.php +++ b/ocs/v1.php @@ -8,9 +8,6 @@ * 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 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 27fb703c87ce2..2e0d0d6970610 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); -} +}); From 500d84914685acbb66904b189df6c5b5474794fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Tue, 9 Jun 2026 18:10:18 +0200 Subject: [PATCH 2/6] chore: Use a better naming for init function MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- index.php | 3 +-- lib/OC.php | 12 ++++++++++-- lib/base.php | 2 +- ocs/v1.php | 3 +-- remote.php | 3 +-- 5 files changed, 14 insertions(+), 9 deletions(-) diff --git a/index.php b/index.php index 69e30049db688..192d72327f251 100644 --- a/index.php +++ b/index.php @@ -25,8 +25,7 @@ \OC::handleRequests(static function () { try { - \OC::resetStaticProperties(); - \OC::init(); + \OC::initForRequest(); \OC::handleRequest(); } catch (ServiceUnavailableException $ex) { Server::get(LoggerInterface::class)->error($ex->getMessage(), [ diff --git a/lib/OC.php b/lib/OC.php index 8d67144e9046c..d5ec15e8935a5 100644 --- a/lib/OC.php +++ b/lib/OC.php @@ -666,6 +666,9 @@ private static function addSecurityHeaders(): void { } } + /* + * Called only once at the beginning to setup things + */ public static function boot(): void { // prevent any XML processing from loading external entities libxml_set_external_entity_loader(static function () { @@ -726,7 +729,12 @@ public static function boot(): void { } } - public static function init(): void { + /* + * Called before each request served if the same worker serves several request + */ + public static function initForRequest(): void { + self::resetStaticProperties(); + // First handle PHP configuration and copy auth headers to the expected // $_SERVER variable before doing anything Server object related self::setRequiredIniValues(); @@ -1343,7 +1351,7 @@ protected static function tryAppAPILogin(OCP\IRequest $request): bool { /** * @internal */ - public static function resetStaticProperties(): void { + private static function resetStaticProperties(): void { // FIXME needed because these use a static var \OC_Hook::clear(); \OC_Util::$styles = []; diff --git a/lib/base.php b/lib/base.php index d38e1fe259080..7e098be7062c0 100644 --- a/lib/base.php +++ b/lib/base.php @@ -9,4 +9,4 @@ require_once __DIR__ . '/OC.php'; \OC::boot(); -\OC::init(); +\OC::initForRequest(); diff --git a/ocs/v1.php b/ocs/v1.php index 5816aa3207613..a5e3657e49ec3 100644 --- a/ocs/v1.php +++ b/ocs/v1.php @@ -31,8 +31,7 @@ \OC::boot(); \OC::handleRequests(static function () { - \OC::resetStaticProperties(); - \OC::init(); + \OC::initForRequest(); $request = Server::get(IRequest::class); $serveAppApiDuringMaintenance = false; diff --git a/remote.php b/remote.php index 2e0d0d6970610..3bfa67e9ec09d 100644 --- a/remote.php +++ b/remote.php @@ -103,8 +103,7 @@ function resolveService($service) { \OC::handleRequests(static function () { try { - \OC::resetStaticProperties(); - \OC::init(); + \OC::initForRequest(); // All resources served via the DAV endpoint should have the strictest possible // policy. Exempted from this is the SabreDAV browser plugin which overwrites From 0b737726a0e298341ae88f79f5112e2afc5d9e25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Fri, 12 Jun 2026 13:47:16 +0200 Subject: [PATCH 3/6] chore: Format Caddyfile MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- Caddyfile | 76 +++++++++++++++++++++++++++---------------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/Caddyfile b/Caddyfile index 7472251129e62..8d4bb390ca0a4 100644 --- a/Caddyfile +++ b/Caddyfile @@ -14,22 +14,22 @@ localhost { php_server { - worker { + worker { file index.php num 32 watch } - worker { + worker { file remote.php num 32 watch } - worker { + worker { file ocs/v1.php num 32 watch } - worker { + worker { file ocs/v2.php num 32 watch @@ -41,42 +41,42 @@ localhost { output stderr } - encode gzip + encode gzip - redir /.well-known/carddav /remote.php/dav 301 - redir /.well-known/caldav /remote.php/dav 301 + redir /.well-known/carddav /remote.php/dav 301 + redir /.well-known/caldav /remote.php/dav 301 - # Rule: Maps most RFC 8615 compliant well-known URIs to our main frontend controller (/index.php) by default - @wellKnown { - path "/.well-known/" - not { - path /.well-known/acme-challenge - path /.well-known/pki-validation - } - } - rewrite @wellKnown /index.php + # Rule: Maps most RFC 8615 compliant well-known URIs to our main frontend controller (/index.php) by default + @wellKnown { + path "/.well-known/" + not { + path /.well-known/acme-challenge + path /.well-known/pki-validation + } + } + rewrite @wellKnown /index.php - rewrite /ocm-provider/ /index.php + rewrite /ocm-provider/ /index.php - @forbidden { - path /.htaccess - path /data/* - path /config/* - path /db_structure - path /.xml - path /README - path /3rdparty/* - path /lib/* - path /templates/* - path /occ - path /build - path /tests - path /console.php - path /autotest - path /issue - path /indi - path /db_ - path /console - } - respond @forbidden 404 + @forbidden { + path /.htaccess + path /data/* + path /config/* + path /db_structure + path /.xml + path /README + path /3rdparty/* + path /lib/* + path /templates/* + path /occ + path /build + path /tests + path /console.php + path /autotest + path /issue + path /indi + path /db_ + path /console + } + respond @forbidden 404 } From b94d5847b85de9a92e26028adc8fabfb1658b430 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Mon, 29 Jun 2026 10:42:00 +0200 Subject: [PATCH 4/6] fix: Use require instead of require_once in remote.php to fix worker mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- remote.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/remote.php b/remote.php index 3bfa67e9ec09d..c8e1c8919137d 100644 --- a/remote.php +++ b/remote.php @@ -97,8 +97,6 @@ function resolveService($service) { return \OCP\Server::get(IConfig::class)->getAppValue('core', 'remote_' . $service); } -require_once __DIR__ . '/lib/OC.php'; - \OC::boot(); \OC::handleRequests(static function () { @@ -157,7 +155,7 @@ function resolveService($service) { break; } $baseuri = OC::$WEBROOT . '/remote.php/' . $service . '/'; - require_once $file; + require $file; } catch (Exception $ex) { handleException($ex); } catch (Error $e) { From 9f81a09373913aea94efb8026440c647ae6eac66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Mon, 29 Jun 2026 10:45:42 +0200 Subject: [PATCH 5/6] feat: Do more checks only once in worker mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Côme Chilliet --- index.php | 3 +-- lib/OC.php | 74 ++++++++++++++++++++++++++++++++++-------------------- 2 files changed, 48 insertions(+), 29 deletions(-) diff --git a/index.php b/index.php index 192d72327f251..ed0908836c2de 100644 --- a/index.php +++ b/index.php @@ -8,8 +8,6 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -require_once __DIR__ . '/lib/versioncheck.php'; - use OC\ServiceUnavailableException; use OC\User\LoginException; use OCP\HintException; @@ -19,6 +17,7 @@ use OCP\Template\ITemplateManager; use Psr\Log\LoggerInterface; +require_once __DIR__ . '/lib/versioncheck.php'; require_once __DIR__ . '/lib/OC.php'; \OC::boot(); diff --git a/lib/OC.php b/lib/OC.php index d5ec15e8935a5..7dff2c8764691 100644 --- a/lib/OC.php +++ b/lib/OC.php @@ -101,6 +101,11 @@ class OC { private static float $loaderStart; private static float $loaderEnd; + /** + * @psalm-suppress ImpureStaticProperty + */ + private static bool $oneTimeChecksDone = false; + /** * @throws \RuntimeException when the 3rdparty directory is missing or * the app path list is empty or contains an invalid path @@ -727,6 +732,40 @@ public static function boot(): void { print($e->getMessage()); exit(); } + self::setRequiredIniValues(); + + // initialize intl fallback if necessary + OC_Util::isSetLocaleWorking(); + } + + /** + * Run one time checks if not already run. This allows checking after server boot, to have access to translations and pretty error rendering while still checking only once in worker mode. + */ + private static function oneTimeChecks(): void { + if (self::$oneTimeChecksDone) { + return; + } + + // Check for PHP SimpleXML extension earlier since we need it before our other checks and want to provide a useful hint for web users + // see https://github.com/nextcloud/server/pull/2619 + if (!function_exists('simplexml_load_file')) { + throw new \OCP\HintException('The PHP SimpleXML/PHP-XML extension is not installed. Install the extension or make sure it is enabled.'); + } + + // Check whether the sample configuration has been copied + if (self::$config->getValue('copied_sample_config', false)) { + $l = Server::get(\OCP\L10N\IFactory::class)->get('lib'); + Server::get(ITemplateManager::class)->printErrorPage( + $l->t('Sample configuration detected'), + $l->t('It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php'), + 503 + ); + return; + } + + self::checkConfig(); + + self::$oneTimeChecksDone = true; } /* @@ -737,19 +776,23 @@ public static function initForRequest(): void { // First handle PHP configuration and copy auth headers to the expected // $_SERVER variable before doing anything Server object related - self::setRequiredIniValues(); self::handleAuthHeaders(); // setup the basic server self::$server = new \OC\Server(\OC::$WEBROOT, self::$config); self::$server->boot(); + self::oneTimeChecks(); + $loaderStart = microtime(true); + $config = Server::get(IConfig::class); + $request = Server::get(IRequest::class); + try { $profiler = new BuiltInProfiler( - Server::get(IConfig::class), - Server::get(IRequest::class), + $config, + $request, ); $profiler->start(); } catch (\Throwable $e) { @@ -769,10 +812,6 @@ public static function initForRequest(): void { error_reporting(E_ALL); } - // initialize intl fallback if necessary - OC_Util::isSetLocaleWorking(); - - $config = Server::get(IConfig::class); if (!defined('PHPUNIT_RUN')) { $errorHandler = new OC\Log\ErrorHandler( Server::get(\Psr\Log\LoggerInterface::class), @@ -796,12 +835,6 @@ public static function initForRequest(): void { $eventLogger->start('init_session', 'Initialize session'); - // Check for PHP SimpleXML extension earlier since we need it before our other checks and want to provide a useful hint for web users - // see https://github.com/nextcloud/server/pull/2619 - if (!function_exists('simplexml_load_file')) { - throw new \OCP\HintException('The PHP SimpleXML/PHP-XML extension is not installed.', 'Install the extension or make sure it is enabled.'); - } - $systemConfig = Server::get(\OC\SystemConfig::class); $appManager = Server::get(\OCP\App\IAppManager::class); if ($systemConfig->getValue('installed', false)) { @@ -811,7 +844,6 @@ public static function initForRequest(): void { self::initSession(); } $eventLogger->end('init_session'); - self::checkConfig(); self::checkInstalled($systemConfig); if (!self::$CLI) { @@ -905,18 +937,6 @@ public static function initForRequest(): void { $lockProvider = Server::get(\OCP\Lock\ILockingProvider::class); register_shutdown_function([$lockProvider, 'releaseAll']); - // Check whether the sample configuration has been copied - if ($systemConfig->getValue('copied_sample_config', false)) { - $l = Server::get(\OCP\L10N\IFactory::class)->get('lib'); - Server::get(ITemplateManager::class)->printErrorPage( - $l->t('Sample configuration detected'), - $l->t('It has been detected that the sample configuration has been copied. This can break your installation and is unsupported. Please read the documentation before performing changes on config.php'), - 503 - ); - return; - } - - $request = Server::get(IRequest::class); $host = $request->getInsecureServerHost(); /** * if the host passed in headers isn't trusted @@ -924,7 +944,7 @@ public static function initForRequest(): void { */ if (!OC::$CLI && !Server::get(\OC\Security\TrustedDomainHelper::class)->isTrustedDomain($host) - && $config->getSystemValueBool('installed', false) + && $config->getSystemValueBool('installed') ) { // Allow access to CSS resources $isScssRequest = false; From 4389cb2b97162e3269f0b7f4306270e910d95d52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=B4me=20Chilliet?= Date: Mon, 29 Jun 2026 10:46:19 +0200 Subject: [PATCH 6/6] chore: Explicitely match subpaths for each worker file MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not clear if this is needed or not, but it helps to be explicit here. Signed-off-by: Côme Chilliet --- Caddyfile | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Caddyfile b/Caddyfile index 8d4bb390ca0a4..7ed476abf1e79 100644 --- a/Caddyfile +++ b/Caddyfile @@ -18,21 +18,20 @@ localhost { file index.php num 32 watch + match /index.php/* } worker { file remote.php num 32 watch + match /remote.php/* } worker { file ocs/v1.php num 32 watch - } - worker { - file ocs/v2.php - num 32 - watch + match /ocs/v1.php/* + match /ocs/v2.php/* } }