Skip to content

Commit 9ff0dc1

Browse files
committed
feat: enabled frankenphp worker mode
Signed-off-by: Carl Schwan <[email protected]>
1 parent f0251c6 commit 9ff0dc1

File tree

11 files changed

+159
-113
lines changed

11 files changed

+159
-113
lines changed

Caddyfile

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
localhost {
22
php_server {
3-
3+
worker index.php
44
}
55

66
log {
7+
level ERROR
78
output stderr
89
}
910

console.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ function exceptionHandler($exception) {
3030
}
3131
try {
3232
require_once __DIR__ . '/lib/base.php';
33+
OC::init();
3334

3435
// set to run indefinitely if needed
3536
if (strpos(@ini_get('disable_functions'), 'set_time_limit') === false) {

core/ajax/update.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
}
3232

3333
require_once '../../lib/base.php';
34+
OC::init();
3435

3536
/** @var IL10N $l */
3637
$l = Server::get(IFactory::class)->get('core');

cron.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
try {
1818
require_once __DIR__ . '/lib/base.php';
1919

20+
OC::init();
21+
2022
if (isset($argv[1]) && ($argv[1] === '-h' || $argv[1] === '--help')) {
2123
echo 'Description:
2224
Run the background job routine

index.php

Lines changed: 96 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -19,90 +19,113 @@
1919
use OCP\Template\ITemplateManager;
2020
use Psr\Log\LoggerInterface;
2121

22-
try {
23-
require_once __DIR__ . '/lib/base.php';
22+
require_once __DIR__ . '/lib/base.php';
2423

25-
OC::handleRequest();
26-
} catch (ServiceUnavailableException $ex) {
27-
Server::get(LoggerInterface::class)->error($ex->getMessage(), [
28-
'app' => 'index',
29-
'exception' => $ex,
30-
]);
31-
32-
//show the user a detailed error page
33-
Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 503);
34-
} catch (HintException $ex) {
24+
$handler = static function () {
3525
try {
36-
Server::get(ITemplateManager::class)->printErrorPage($ex->getMessage(), $ex->getHint(), 503);
37-
} catch (Exception $ex2) {
26+
// In worker mode, script name is empty in FrankenPHP
27+
if ($_SERVER['SCRIPT_NAME'] === '') {
28+
$_SERVER['SCRIPT_NAME'] = $_SERVER['PHP_SELF'];
29+
}
30+
OC::init();
31+
OC::handleRequest();
32+
} catch (ServiceUnavailableException $ex) {
33+
Server::get(LoggerInterface::class)->error($ex->getMessage(), [
34+
'app' => 'index',
35+
'exception' => $ex,
36+
]);
37+
38+
//show the user a detailed error page
39+
Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 503);
40+
} catch (HintException $ex) {
41+
try {
42+
Server::get(ITemplateManager::class)->printErrorPage($ex->getMessage(), $ex->getHint(), 503);
43+
} catch (Exception $ex2) {
44+
try {
45+
Server::get(LoggerInterface::class)->error($ex->getMessage(), [
46+
'app' => 'index',
47+
'exception' => $ex,
48+
]);
49+
Server::get(LoggerInterface::class)->error($ex2->getMessage(), [
50+
'app' => 'index',
51+
'exception' => $ex2,
52+
]);
53+
} catch (Throwable $e) {
54+
// no way to log it properly - but to avoid a white page of death we try harder and ignore this one here
55+
}
56+
57+
//show the user a detailed error page
58+
Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 500);
59+
}
60+
} catch (LoginException $ex) {
61+
$request = Server::get(IRequest::class);
62+
/**
63+
* Routes with the @CORS annotation and other API endpoints should
64+
* not return a webpage, so we only print the error page when html is accepted,
65+
* otherwise we reply with a JSON array like the SecurityMiddleware would do.
66+
*/
67+
if (stripos($request->getHeader('Accept'), 'html') === false) {
68+
http_response_code(401);
69+
header('Content-Type: application/json; charset=utf-8');
70+
echo json_encode(['message' => $ex->getMessage()]);
71+
exit();
72+
}
73+
Server::get(ITemplateManager::class)->printErrorPage($ex->getMessage(), $ex->getMessage(), 401);
74+
} catch (MaxDelayReached $ex) {
75+
$request = Server::get(IRequest::class);
76+
/**
77+
* Routes with the @CORS annotation and other API endpoints should
78+
* not return a webpage, so we only print the error page when html is accepted,
79+
* otherwise we reply with a JSON array like the BruteForceMiddleware would do.
80+
*/
81+
if (stripos($request->getHeader('Accept'), 'html') === false) {
82+
http_response_code(429);
83+
header('Content-Type: application/json; charset=utf-8');
84+
echo json_encode(['message' => $ex->getMessage()]);
85+
exit();
86+
}
87+
http_response_code(429);
88+
Server::get(ITemplateManager::class)->printGuestPage('core', '429');
89+
} catch (Exception $ex) {
90+
Server::get(LoggerInterface::class)->error($ex->getMessage(), [
91+
'app' => 'index',
92+
'exception' => $ex,
93+
]);
94+
95+
//show the user a detailed error page
96+
Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 500);
97+
} catch (Error $ex) {
3898
try {
3999
Server::get(LoggerInterface::class)->error($ex->getMessage(), [
40100
'app' => 'index',
41101
'exception' => $ex,
42102
]);
43-
Server::get(LoggerInterface::class)->error($ex2->getMessage(), [
44-
'app' => 'index',
45-
'exception' => $ex2,
46-
]);
47-
} catch (Throwable $e) {
48-
// no way to log it properly - but to avoid a white page of death we try harder and ignore this one here
49-
}
103+
} catch (Error $e) {
104+
http_response_code(500);
105+
header('Content-Type: text/plain; charset=utf-8');
106+
print("Internal Server Error\n\n");
107+
print("The server encountered an internal error and was unable to complete your request.\n");
108+
print("Please contact the server administrator if this error reappears multiple times, please include the technical details below in your report.\n");
109+
print("More details can be found in the webserver log.\n");
50110

51-
//show the user a detailed error page
111+
throw $ex;
112+
}
52113
Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 500);
53114
}
54-
} catch (LoginException $ex) {
55-
$request = Server::get(IRequest::class);
56-
/**
57-
* Routes with the @CORS annotation and other API endpoints should
58-
* not return a webpage, so we only print the error page when html is accepted,
59-
* otherwise we reply with a JSON array like the SecurityMiddleware would do.
60-
*/
61-
if (stripos($request->getHeader('Accept'), 'html') === false) {
62-
http_response_code(401);
63-
header('Content-Type: application/json; charset=utf-8');
64-
echo json_encode(['message' => $ex->getMessage()]);
65-
exit();
66-
}
67-
Server::get(ITemplateManager::class)->printErrorPage($ex->getMessage(), $ex->getMessage(), 401);
68-
} catch (MaxDelayReached $ex) {
69-
$request = Server::get(IRequest::class);
70-
/**
71-
* Routes with the @CORS annotation and other API endpoints should
72-
* not return a webpage, so we only print the error page when html is accepted,
73-
* otherwise we reply with a JSON array like the BruteForceMiddleware would do.
74-
*/
75-
if (stripos($request->getHeader('Accept'), 'html') === false) {
76-
http_response_code(429);
77-
header('Content-Type: application/json; charset=utf-8');
78-
echo json_encode(['message' => $ex->getMessage()]);
79-
exit();
80-
}
81-
http_response_code(429);
82-
Server::get(ITemplateManager::class)->printGuestPage('core', '429');
83-
} catch (Exception $ex) {
84-
Server::get(LoggerInterface::class)->error($ex->getMessage(), [
85-
'app' => 'index',
86-
'exception' => $ex,
87-
]);
115+
};
88116

89-
//show the user a detailed error page
90-
Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 500);
91-
} catch (Error $ex) {
92-
try {
93-
Server::get(LoggerInterface::class)->error($ex->getMessage(), [
94-
'app' => 'index',
95-
'exception' => $ex,
96-
]);
97-
} catch (Error $e) {
98-
http_response_code(500);
99-
header('Content-Type: text/plain; charset=utf-8');
100-
print("Internal Server Error\n\n");
101-
print("The server encountered an internal error and was unable to complete your request.\n");
102-
print("Please contact the server administrator if this error reappears multiple times, please include the technical details below in your report.\n");
103-
print("More details can be found in the webserver log.\n");
117+
if (function_exists('frankenphp_handle_request')) {
118+
$maxRequests = (int)($_SERVER['MAX_REQUESTS'] ?? 0);
119+
for ($nbRequests = 0; !$maxRequests || $nbRequests < $maxRequests; ++$nbRequests) {
120+
$keepRunning = \frankenphp_handle_request($handler);
104121

105-
throw $ex;
122+
// Call the garbage collector to reduce the chances of it being triggered in the middle of a page generation
123+
gc_collect_cycles();
124+
125+
if (!$keepRunning) {
126+
break;
127+
}
106128
}
107-
Server::get(ITemplateManager::class)->printExceptionErrorPage($ex, 500);
129+
} else {
130+
$handler();
108131
}

lib/base.php

Lines changed: 41 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -83,17 +83,6 @@ class OC {
8383
* the app path list is empty or contains an invalid path
8484
*/
8585
public static function initPaths(): void {
86-
if (defined('PHPUNIT_CONFIG_DIR')) {
87-
self::$configDir = OC::$SERVERROOT . '/' . PHPUNIT_CONFIG_DIR . '/';
88-
} elseif (defined('PHPUNIT_RUN') && PHPUNIT_RUN && is_dir(OC::$SERVERROOT . '/tests/config/')) {
89-
self::$configDir = OC::$SERVERROOT . '/tests/config/';
90-
} elseif ($dir = getenv('NEXTCLOUD_CONFIG_DIR')) {
91-
self::$configDir = rtrim($dir, '/') . '/';
92-
} else {
93-
self::$configDir = OC::$SERVERROOT . '/config/';
94-
}
95-
self::$config = new \OC\Config(self::$configDir);
96-
9786
OC::$SUBURI = str_replace('\\', '/', substr(realpath($_SERVER['SCRIPT_FILENAME'] ?? ''), strlen(OC::$SERVERROOT)));
9887
/**
9988
* FIXME: The following lines are required because we can't yet instantiate
@@ -145,7 +134,7 @@ public static function initPaths(): void {
145134
// Resolve /nextcloud to /nextcloud/ to ensure to always have a trailing
146135
// slash which is required by URL generation.
147136
if (isset($_SERVER['REQUEST_URI']) && $_SERVER['REQUEST_URI'] === \OC::$WEBROOT
148-
&& substr($_SERVER['REQUEST_URI'], -1) !== '/') {
137+
&& substr($_SERVER['REQUEST_URI'], -1) !== '/') {
149138
header('Location: ' . \OC::$WEBROOT . '/');
150139
exit();
151140
}
@@ -165,6 +154,7 @@ public static function initPaths(): void {
165154
OC::$APPSROOTS[] = ['path' => OC::$SERVERROOT . '/apps', 'url' => '/apps', 'writable' => true];
166155
}
167156

157+
168158
if (empty(OC::$APPSROOTS)) {
169159
throw new \RuntimeException('apps directory not found! Please put the Nextcloud apps folder in the Nextcloud folder'
170160
. '. You can also configure the location in the config.php file.');
@@ -643,12 +633,7 @@ private static function addSecurityHeaders(): void {
643633
}
644634
}
645635

646-
public static function init(): void {
647-
// First handle PHP configuration and copy auth headers to the expected
648-
// $_SERVER variable before doing anything Server object related
649-
self::setRequiredIniValues();
650-
self::handleAuthHeaders();
651-
636+
public static function boot(): void {
652637
// prevent any XML processing from loading external entities
653638
libxml_set_external_entity_loader(static function () {
654639
return null;
@@ -671,15 +656,32 @@ public static function init(): void {
671656
self::$composerAutoloader = require_once OC::$SERVERROOT . '/lib/composer/autoload.php';
672657
self::$composerAutoloader->setApcuPrefix(null);
673658

659+
// setup 3rdparty autoloader
660+
$vendorAutoLoad = OC::$SERVERROOT . '/3rdparty/autoload.php';
661+
if (!file_exists($vendorAutoLoad)) {
662+
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".');
663+
}
664+
require_once $vendorAutoLoad;
665+
666+
$loaderEnd = microtime(true);
667+
668+
// load configs
669+
if (defined('PHPUNIT_CONFIG_DIR')) {
670+
self::$configDir = OC::$SERVERROOT . '/' . PHPUNIT_CONFIG_DIR . '/';
671+
} elseif (defined('PHPUNIT_RUN') && PHPUNIT_RUN && is_dir(OC::$SERVERROOT . '/tests/config/')) {
672+
self::$configDir = OC::$SERVERROOT . '/tests/config/';
673+
} elseif ($dir = getenv('NEXTCLOUD_CONFIG_DIR')) {
674+
self::$configDir = rtrim($dir, '/') . '/';
675+
} else {
676+
self::$configDir = OC::$SERVERROOT . '/config/';
677+
}
678+
self::$config = new \OC\Config(self::$configDir);
679+
680+
// Enable lazy loading if activated
681+
\OC\AppFramework\Utility\SimpleContainer::$useLazyObjects = (bool)self::$config->getValue('enable_lazy_objects', true);
674682

675683
try {
676684
self::initPaths();
677-
// setup 3rdparty autoloader
678-
$vendorAutoLoad = OC::$SERVERROOT . '/3rdparty/autoload.php';
679-
if (!file_exists($vendorAutoLoad)) {
680-
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".');
681-
}
682-
require_once $vendorAutoLoad;
683685
} catch (\RuntimeException $e) {
684686
if (!self::$CLI) {
685687
http_response_code(503);
@@ -689,15 +691,24 @@ public static function init(): void {
689691
print($e->getMessage());
690692
exit();
691693
}
692-
$loaderEnd = microtime(true);
693694

694-
// Enable lazy loading if activated
695-
\OC\AppFramework\Utility\SimpleContainer::$useLazyObjects = (bool)self::$config->getValue('enable_lazy_objects', true);
695+
//$eventLogger = Server::get(\OCP\Diagnostics\IEventLogger::class);
696+
//$eventLogger->log('autoloader', 'Autoloader', $loaderStart, $loaderEnd);
697+
//$eventLogger->start('init', 'Initialize');
698+
}
699+
700+
public static function init(): void {
701+
// First handle PHP configuration and copy auth headers to the expected
702+
// $_SERVER variable before doing anything Server object related
703+
self::setRequiredIniValues();
704+
self::handleAuthHeaders();
696705

697-
// setup the basic server
706+
// set up the basic server
698707
self::$server = new \OC\Server(\OC::$WEBROOT, self::$config);
699708
self::$server->boot();
700709

710+
$loaderStart = microtime(true);
711+
701712
try {
702713
$profiler = new BuiltInProfiler(
703714
Server::get(IConfig::class),
@@ -713,8 +724,7 @@ public static function init(): void {
713724
}
714725

715726
$eventLogger = Server::get(\OCP\Diagnostics\IEventLogger::class);
716-
$eventLogger->log('autoloader', 'Autoloader', $loaderStart, $loaderEnd);
717-
$eventLogger->start('boot', 'Initialize');
727+
$eventLogger->start('init', 'Initialize');
718728

719729
// Override php.ini and log everything if we're troubleshooting
720730
if (self::$config->getValue('loglevel') === ILogger::DEBUG) {
@@ -1285,4 +1295,4 @@ protected static function tryAppAPILogin(OCP\IRequest $request): bool {
12851295
}
12861296
}
12871297

1288-
OC::init();
1298+
OC::boot();

lib/private/Route/Router.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ public function findMatchingRoute(string $url): array {
277277
$this->loadRoutes();
278278
}
279279

280+
280281
$this->eventLogger->start('route:url:match', 'Symfony url matcher call');
281282
$matcher = new UrlMatcher($this->root, $this->context);
282283
try {

lib/private/Share/Share.php

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,12 @@ public static function registerBackend($itemType, $class, $collectionOf = null,
5757
];
5858
return true;
5959
}
60-
Server::get(LoggerInterface::class)->warning(
61-
'Sharing backend ' . $class . ' not registered, ' . self::$backendTypes[$itemType]['class']
62-
. ' is already registered for ' . $itemType,
63-
['app' => 'files_sharing']);
60+
if (self::$backendTypes[$itemType]['class'] !== $class) {
61+
Server::get(LoggerInterface::class)->warning(
62+
'Sharing backend ' . $class . ' not registered, ' . self::$backendTypes[$itemType]['class']
63+
. ' is already registered for ' . $itemType,
64+
['app' => 'files_sharing']);
65+
}
6466
}
6567
return false;
6668
}

0 commit comments

Comments
 (0)