diff --git a/Caddyfile b/Caddyfile index 5813be26a49..b18b3129783 100644 --- a/Caddyfile +++ b/Caddyfile @@ -1,9 +1,10 @@ localhost { php_server { - + worker index.php } log { + level ERROR output stderr } diff --git a/console.php b/console.php index b5a000e8873..ce7745333e8 100644 --- a/console.php +++ b/console.php @@ -30,6 +30,7 @@ function exceptionHandler($exception) { } try { require_once __DIR__ . '/lib/base.php'; + OC::init(); // set to run indefinitely if needed if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) { diff --git a/cron.php b/cron.php index f86d93b9a73..4ccb8b7c632 100644 --- a/cron.php +++ b/cron.php @@ -17,6 +17,8 @@ require_once __DIR__ . '/lib/versioncheck.php'; try { require_once __DIR__ . '/lib/base.php'; + OC::init(); + if (isset($argv[1]) && ($argv[1] === '-h' || $argv[1] === '--help')) { echo 'Description: Run the background job routine diff --git a/index.php b/index.php index b368462371d..411821af474 100644 --- a/index.php +++ b/index.php @@ -19,90 +19,113 @@ use OCP\Server; use OCP\Template\ITemplateManager; use Psr\Log\LoggerInterface; -try { - require_once __DIR__ . '/lib/base.php'; +require_once __DIR__ . '/lib/base.php'; - OC::handleRequest(); -} catch (ServiceUnavailableException $ex) { - Server::get(LoggerInterface::class)->error($ex->getMessage(), [ - 'app' => 'index', - 'exception' => $ex, - ]); - - //show the user a detailed error page - Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 503); -} catch (HintException $ex) { +$handler = static function () { try { - Server::get(ITemplateManager::class)->printErrorPage($ex->getMessage(), $ex->getHint(), 503); - } catch (Exception $ex2) { + // In worker mode, script name is empty in FrankenPHP + if ($_SERVER['SCRIPT_NAME'] === '') { + $_SERVER['SCRIPT_NAME'] = $_SERVER['PHP_SELF']; + } + OC::init(); + OC::handleRequest(); + } catch (ServiceUnavailableException $ex) { + Server::get(LoggerInterface::class)->error($ex->getMessage(), [ + 'app' => 'index', + 'exception' => $ex, + ]); + + //show the user a detailed error page + Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 503); + } catch (HintException $ex) { + try { + Server::get(ITemplateManager::class)->printErrorPage($ex->getMessage(), $ex->getHint(), 503); + } catch (Exception $ex2) { + try { + Server::get(LoggerInterface::class)->error($ex->getMessage(), [ + 'app' => 'index', + 'exception' => $ex, + ]); + Server::get(LoggerInterface::class)->error($ex2->getMessage(), [ + 'app' => 'index', + 'exception' => $ex2, + ]); + } catch (Throwable $e) { + // no way to log it properly - but to avoid a white page of death we try harder and ignore this one here + } + + //show the user a detailed error page + Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 500); + } + } catch (LoginException $ex) { + $request = Server::get(IRequest::class); + /** + * Routes with the @CORS annotation and other API endpoints should + * not return a webpage, so we only print the error page when html is accepted, + * otherwise we reply with a JSON array like the SecurityMiddleware would do. + */ + if (stripos($request->getHeader('Accept'), 'html') === false) { + http_response_code(401); + header('Content-Type: application/json; charset=utf-8'); + echo json_encode(['message' => $ex->getMessage()]); + exit(); + } + Server::get(ITemplateManager::class)->printErrorPage($ex->getMessage(), $ex->getMessage(), 401); + } catch (MaxDelayReached $ex) { + $request = Server::get(IRequest::class); + /** + * Routes with the @CORS annotation and other API endpoints should + * not return a webpage, so we only print the error page when html is accepted, + * otherwise we reply with a JSON array like the BruteForceMiddleware would do. + */ + if (stripos($request->getHeader('Accept'), 'html') === false) { + http_response_code(429); + header('Content-Type: application/json; charset=utf-8'); + echo json_encode(['message' => $ex->getMessage()]); + exit(); + } + http_response_code(429); + Server::get(ITemplateManager::class)->printGuestPage('core', '429'); + } catch (Exception $ex) { + Server::get(LoggerInterface::class)->error($ex->getMessage(), [ + 'app' => 'index', + 'exception' => $ex, + ]); + + //show the user a detailed error page + Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 500); + } catch (Error $ex) { try { Server::get(LoggerInterface::class)->error($ex->getMessage(), [ 'app' => 'index', 'exception' => $ex, ]); - Server::get(LoggerInterface::class)->error($ex2->getMessage(), [ - 'app' => 'index', - 'exception' => $ex2, - ]); - } catch (Throwable $e) { - // no way to log it properly - but to avoid a white page of death we try harder and ignore this one here - } + } catch (Error $e) { + http_response_code(500); + header('Content-Type: text/plain; charset=utf-8'); + print("Internal Server Error\n\n"); + print("The server encountered an internal error and was unable to complete your request.\n"); + print("Please contact the server administrator if this error reappears multiple times, please include the technical details below in your report.\n"); + print("More details can be found in the webserver log.\n"); - //show the user a detailed error page + throw $ex; + } Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 500); } -} catch (LoginException $ex) { - $request = Server::get(IRequest::class); - /** - * Routes with the @CORS annotation and other API endpoints should - * not return a webpage, so we only print the error page when html is accepted, - * otherwise we reply with a JSON array like the SecurityMiddleware would do. - */ - if (stripos($request->getHeader('Accept'), 'html') === false) { - http_response_code(401); - header('Content-Type: application/json; charset=utf-8'); - echo json_encode(['message' => $ex->getMessage()]); - exit(); - } - Server::get(ITemplateManager::class)->printErrorPage($ex->getMessage(), $ex->getMessage(), 401); -} catch (MaxDelayReached $ex) { - $request = Server::get(IRequest::class); - /** - * Routes with the @CORS annotation and other API endpoints should - * not return a webpage, so we only print the error page when html is accepted, - * otherwise we reply with a JSON array like the BruteForceMiddleware would do. - */ - if (stripos($request->getHeader('Accept'), 'html') === false) { - http_response_code(429); - header('Content-Type: application/json; charset=utf-8'); - echo json_encode(['message' => $ex->getMessage()]); - exit(); - } - http_response_code(429); - Server::get(ITemplateManager::class)->printGuestPage('core', '429'); -} catch (Exception $ex) { - Server::get(LoggerInterface::class)->error($ex->getMessage(), [ - 'app' => 'index', - 'exception' => $ex, - ]); +}; - //show the user a detailed error page - Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 500); -} catch (Error $ex) { - try { - Server::get(LoggerInterface::class)->error($ex->getMessage(), [ - 'app' => 'index', - 'exception' => $ex, - ]); - } catch (Error $e) { - http_response_code(500); - header('Content-Type: text/plain; charset=utf-8'); - print("Internal Server Error\n\n"); - print("The server encountered an internal error and was unable to complete your request.\n"); - print("Please contact the server administrator if this error reappears multiple times, please include the technical details below in your report.\n"); - print("More details can be found in the webserver log.\n"); +if (function_exists('frankenphp_handle_request')) { + $maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0); + for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests) { + $keepRunning = \frankenphp_handle_request($handler); - throw $ex; + // 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; + } } - Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 500); +} else { + $handler(); } diff --git a/lib/base.php b/lib/base.php index aa5b1d01b7a..c0d762c5c17 100644 --- a/lib/base.php +++ b/lib/base.php @@ -104,17 +104,6 @@ class OC { * the app path list is empty or contains an invalid path */ public static function initPaths(): void { - if (defined('PHPUNIT_CONFIG_DIR')) { - self::$configDir = OC::$SERVERROOT . '/' . PHPUNIT_CONFIG_DIR . '/'; - } elseif (defined('PHPUNIT_RUN') && PHPUNIT_RUN && is_dir(OC::$SERVERROOT . '/tests/config/')) { - self::$configDir = OC::$SERVERROOT . '/tests/config/'; - } elseif ($dir = getenv('NEXTCLOUD_CONFIG_DIR')) { - self::$configDir = rtrim($dir, '/') . '/'; - } else { - self::$configDir = OC::$SERVERROOT . '/config/'; - } - self::$config = new \OC\Config(self::$configDir); - OC::$SUBURI = str_replace('\\', '/', substr(realpath($_SERVER['SCRIPT_FILENAME'] ?? ''), strlen(OC::$SERVERROOT))); /** * FIXME: The following lines are required because we can't yet instantiate @@ -166,7 +155,7 @@ class OC { // Resolve /nextcloud to /nextcloud/ to ensure to always have a trailing // slash which is required by URL generation. if (isset($_SERVER['REQUEST_URI']) && $_SERVER['REQUEST_URI'] === \OC::$WEBROOT - && substr($_SERVER['REQUEST_URI'], -1) !== '/') { + && substr($_SERVER['REQUEST_URI'], -1) !== '/') { header('Location: ' . \OC::$WEBROOT . '/'); exit(); } @@ -186,6 +175,7 @@ class OC { OC::$APPSROOTS[] = ['path' => OC::$SERVERROOT . '/apps', 'url' => '/apps', 'writable' => true]; } + if (empty(OC::$APPSROOTS)) { throw new \RuntimeException('apps directory not found! Please put the Nextcloud apps folder in the Nextcloud folder' . '. You can also configure the location in the config.php file.'); @@ -664,12 +654,7 @@ class OC { } } - public static function init(): void { - // First handle PHP configuration and copy auth headers to the expected - // $_SERVER variable before doing anything Server object related - self::setRequiredIniValues(); - self::handleAuthHeaders(); - + public static function boot(): void { // prevent any XML processing from loading external entities libxml_set_external_entity_loader(static function () { return null; @@ -692,14 +677,32 @@ class OC { self::$composerAutoloader = require_once OC::$SERVERROOT . '/lib/composer/autoload.php'; self::$composerAutoloader->setApcuPrefix(null); + // setup 3rdparty autoloader + $vendorAutoLoad = OC::$SERVERROOT . '/3rdparty/autoload.php'; + if (!file_exists($vendorAutoLoad)) { + throw new \RuntimeException('Composer autoloader not found, unable to continue. Check the folder "3rdparty". Running "git submodule update --init" will initialize the git submodule that handles the subfolder "3rdparty".'); + } + require_once $vendorAutoLoad; + + $loaderEnd = microtime(true); + + // load configs + if (defined('PHPUNIT_CONFIG_DIR')) { + self::$configDir = OC::$SERVERROOT . '/' . PHPUNIT_CONFIG_DIR . '/'; + } elseif (defined('PHPUNIT_RUN') && PHPUNIT_RUN && is_dir(OC::$SERVERROOT . '/tests/config/')) { + self::$configDir = OC::$SERVERROOT . '/tests/config/'; + } elseif ($dir = getenv('NEXTCLOUD_CONFIG_DIR')) { + self::$configDir = rtrim($dir, '/') . '/'; + } else { + self::$configDir = OC::$SERVERROOT . '/config/'; + } + self::$config = new \OC\Config(self::$configDir); + + // Enable lazy loading if activated + \OC\AppFramework\Utility\SimpleContainer::$useLazyObjects = (bool)self::$config->getValue('enable_lazy_objects', true); + try { self::initPaths(); - // setup 3rdparty autoloader - $vendorAutoLoad = OC::$SERVERROOT . '/3rdparty/autoload.php'; - if (!file_exists($vendorAutoLoad)) { - throw new \RuntimeException('Composer autoloader not found, unable to continue. Check the folder "3rdparty". Running "git submodule update --init" will initialize the git submodule that handles the subfolder "3rdparty".'); - } - require_once $vendorAutoLoad; } catch (\RuntimeException $e) { if (!self::$CLI) { http_response_code(503); @@ -709,15 +712,24 @@ class OC { print($e->getMessage()); exit(); } - $loaderEnd = microtime(true); - // Enable lazy loading if activated - \OC\AppFramework\Utility\SimpleContainer::$useLazyObjects = (bool)self::$config->getValue('enable_lazy_objects', true); + //$eventLogger = Server::get(\OCP\Diagnostics\IEventLogger::class); + //$eventLogger->log('autoloader', 'Autoloader', $loaderStart, $loaderEnd); + //$eventLogger->start('init', 'Initialize'); + } - // setup the basic server + public static function init(): void { + // First handle PHP configuration and copy auth headers to the expected + // $_SERVER variable before doing anything Server object related + self::setRequiredIniValues(); + self::handleAuthHeaders(); + + // set up the basic server self::$server = new \OC\Server(\OC::$WEBROOT, self::$config); self::$server->boot(); + $loaderStart = microtime(true); + try { $profiler = new BuiltInProfiler( Server::get(IConfig::class), @@ -733,8 +745,7 @@ class OC { } $eventLogger = Server::get(\OCP\Diagnostics\IEventLogger::class); - $eventLogger->log('autoloader', 'Autoloader', $loaderStart, $loaderEnd); - $eventLogger->start('boot', 'Initialize'); + $eventLogger->start('init', 'Initialize'); // Override php.ini and log everything if we're troubleshooting if (self::$config->getValue('loglevel') === ILogger::DEBUG) { @@ -1307,4 +1318,4 @@ class OC { } } -OC::init(); +OC::boot(); diff --git a/lib/private/Route/Router.php b/lib/private/Route/Router.php index b75c3e69826..13029198028 100644 --- a/lib/private/Route/Router.php +++ b/lib/private/Route/Router.php @@ -280,6 +280,7 @@ class Router implements IRouter { $this->loadRoutes(); } + $this->eventLogger->start('route:url:match', 'Symfony url matcher call'); $matcher = new UrlMatcher($this->root, $this->context); try { diff --git a/ocs-provider/index.php b/ocs-provider/index.php index 74fe6a11034..9b7358242e0 100644 --- a/ocs-provider/index.php +++ b/ocs-provider/index.php @@ -11,6 +11,7 @@ use OCP\IRequest; use OCP\Server; require_once __DIR__ . '/../lib/base.php'; +OC::init(); header('Content-Type: application/json'); diff --git a/ocs/v1.php b/ocs/v1.php index 5c4d125f9eb..84b47c24dc3 100644 --- a/ocs/v1.php +++ b/ocs/v1.php @@ -28,6 +28,7 @@ use Psr\Log\LoggerInterface; use Symfony\Component\Routing\Exception\MethodNotAllowedException; use Symfony\Component\Routing\Exception\ResourceNotFoundException; +OC::init(); $request = Server::get(IRequest::class); if ((Util::needUpgrade() || Server::get(IConfig::class)->getSystemValueBool('maintenance')) && $request->getPathInfo() !== '/core/update') { diff --git a/remote.php b/remote.php index 27fb703c87c..b9d1e34f7a0 100644 --- a/remote.php +++ b/remote.php @@ -97,6 +97,8 @@ function resolveService($service) { try { require_once __DIR__ . '/lib/base.php'; + 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.