Files
webserver/qbixserver.php
T

226 lines
7.1 KiB
PHP
Raw Normal View History

#!/usr/bin/env php
<?php
/**
* Qbix Server — pure PHP web server.
*
* Standalone: php server.php --root=./web --port=8080
* With Qbix: php server.php --app=/path/to/myapp --port=8080
*
* Options:
* --root=DIR Document root (default: ./web)
* --app=DIR Qbix app directory (loads full Q framework)
* --host=IP Bind address (default: 0.0.0.0)
* --port=PORT Listen port (default: 8080)
* --workers=N Pre-fork PHP workers (default: 0 = in-process)
* --config=FILE JSON config file to load
* --pid=PATH Write PID file
* --debug Enable verbose logging
* --version Print version and exit
* --help Print usage and exit
*/
define('QBIX_SERVER_VERSION', '1.0.0');
// ── Parse CLI args ──────────────────────────────────
$opts = array(
'root' => null,
'app' => null,
'host' => '0.0.0.0',
'port' => 8080,
'workers' => 0,
'config' => null,
'pid' => null,
'debug' => false,
);
foreach ($argv as $i => $arg) {
if ($i === 0) continue;
if ($arg === '--help' || $arg === '-h') {
echo "Qbix Server v" . QBIX_SERVER_VERSION . "\n\n";
echo "Usage: php server.php [options]\n\n";
echo "Options:\n";
echo " --root=DIR Document root (default: ./web)\n";
echo " --app=DIR Qbix app directory (uses full Q framework)\n";
echo " --host=IP Bind address (default: 0.0.0.0)\n";
echo " --port=PORT Listen port (default: 8080)\n";
echo " --workers=N Pre-fork workers (default: 0 = in-process)\n";
echo " --config=FILE JSON config file\n";
echo " --pid=PATH PID file path\n";
echo " --debug Verbose logging\n";
echo " --version Print version\n";
exit(0);
}
if ($arg === '--version' || $arg === '-v') {
echo "Qbix Server v" . QBIX_SERVER_VERSION . "\n";
exit(0);
}
if ($arg === '--debug') {
$opts['debug'] = true;
continue;
}
if (preg_match('/^--(\w+)=(.+)$/', $arg, $m)) {
$opts[$m[1]] = $m[2];
}
}
// ── Determine mode: standalone vs Qbix app ──────────
$qbixMode = false;
if ($opts['app']) {
// Qbix app mode — load the full framework
$appDir = realpath($opts['app']);
if (!$appDir || !is_dir($appDir)) {
fwrite(STDERR, "Error: app directory not found: {$opts['app']}\n");
exit(1);
}
// Look for Q.inc.php
$qInc = $appDir . '/scripts/Q.inc.php';
if (!file_exists($qInc)) {
// Try Platform path
$qInc = $appDir . '/../Platform/scripts/Q.inc.php';
}
if (file_exists($qInc)) {
define('APP_DIR', $appDir);
require_once $qInc;
$qbixMode = true;
} else {
fwrite(STDERR, "Warning: Q.inc.php not found, running in standalone mode\n");
}
$webDir = $appDir . '/web';
} else {
$webDir = $opts['root'] ?: (getcwd() . '/web');
}
if (!$qbixMode) {
// Standalone mode — load minimal Q shim
require_once __DIR__ . '/src/Q.php';
}
$webDir = realpath($webDir);
if (!$webDir || !is_dir($webDir)) {
fwrite(STDERR, "Error: document root not found: " . ($opts['root'] ?: './web') . "\n");
fwrite(STDERR, "Create a web/ directory or use --root=DIR\n");
exit(1);
}
// ── Load config ─────────────────────────────────────
// Default server config
$defaultConfig = array(
'Q' => array(
'webserver' => array(
'keepAlive' => array('max' => 100, 'timeout' => 15),
'timeout' => array('read' => 30),
'maxConnections' => 1024,
'fileCache' => array(
'maxSize' => 67108864,
'maxFile' => 1048576,
'checkInterval' => 1,
),
'rateLimit' => array(
'enabled' => false,
'requests' => 100,
'window' => 60,
'burstRequests' => 20,
'burstWindow' => 1,
),
),
'web' => array(
'cache' => array(
'enabled' => true,
'defaultTtl' => 0,
'components' => array('enabled' => false),
),
),
),
);
// Apply defaults
foreach ($defaultConfig as $k1 => $v1) {
if (is_array($v1)) {
foreach ($v1 as $k2 => $v2) {
if (is_array($v2)) {
foreach ($v2 as $k3 => $v3) {
if (is_array($v3)) {
foreach ($v3 as $k4 => $v4) {
if (Q_Config::get($k1, $k2, $k3, $k4, null) === null) {
Q_Config::set($k1, $k2, $k3, $k4, $v4);
}
}
} else {
if (Q_Config::get($k1, $k2, $k3, null) === null) {
Q_Config::set($k1, $k2, $k3, $v3);
}
}
}
}
}
}
}
// User config file
if ($opts['config']) {
Q_Config::load($opts['config']);
}
// Config from app directory
$appConfig = dirname($webDir) . '/config/server.json';
if (file_exists($appConfig)) {
Q_Config::load($appConfig);
}
// ── PID file ────────────────────────────────────────
if ($opts['pid']) {
file_put_contents($opts['pid'], getmypid());
register_shutdown_function(function () use ($opts) {
@unlink($opts['pid']);
});
}
// ── Request logging ─────────────────────────────────
$colors = array(
2 => "\033[32m", // green for 2xx
3 => "\033[33m", // yellow for 3xx
4 => "\033[31m", // red for 4xx
5 => "\033[31m", // red for 5xx
);
$reset = "\033[0m";
Q_WebServer::$onRequest = function ($method, $uri, $status, $ms) use ($colors, $reset, $opts) {
$color = $colors[(int)($status / 100)] ?? '';
$time = date('H:i:s');
echo "$time {$color}{$status}{$reset} $method $uri ({$ms}ms)\n";
};
// ── Start server ────────────────────────────────────
echo "\n";
echo " ┌──────────────────────────────────────┐\n";
echo " │ Qbix Server v" . QBIX_SERVER_VERSION . str_repeat(' ', 22 - strlen(QBIX_SERVER_VERSION)) . "│\n";
echo " ├──────────────────────────────────────┤\n";
echo " │ http://{$opts['host']}:{$opts['port']}" . str_repeat(' ', max(0, 24 - strlen("{$opts['host']}:{$opts['port']}"))) . "│\n";
echo " │ Root: " . basename($webDir) . str_repeat(' ', max(0, 29 - strlen(basename($webDir)))) . "│\n";
echo " │ Mode: " . ($qbixMode ? 'Qbix Platform' : 'Standalone') . str_repeat(' ', $qbixMode ? 16 : 19) . "│\n";
echo " │ PHP: " . ($opts['workers'] ? $opts['workers'] . ' workers' : 'in-process') . str_repeat(' ', max(0, 30 - strlen($opts['workers'] ? $opts['workers'] . ' workers' : 'in-process'))) . "│\n";
echo " ├──────────────────────────────────────┤\n";
echo " │ Dashboard: /Q/dashboard │\n";
echo " │ Health: /Q/health │\n";
echo " │ Ctrl+C to stop │\n";
echo " └──────────────────────────────────────┘\n";
echo "\n";
Q_WebServer::start(
$webDir,
$opts['host'],
(int) $opts['port'],
(int) $opts['workers']
);
Q_WebServer::run();