dvaknheo / workermanhttpd
workerman http server andplugin for duckphp
Requires
- php: >=7.2.0
- workerman/workerman: ^4.0.4
Requires (Dev)
- dvaknheo/duckphp: >=1.2.10
- dvaknheo/libcoverage: >=1.0.5
- phpunit/php-code-coverage: 8.0.2
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
*** v1.0.7 (development) ***
What is WorkermanHttpd
WorkermanHttpd lets the same PHP code run on Workerman and php-fpm with almost no
changes. It is a thin wrapper around Workerman\Worker, built around two ideas:
- echo directly — output is captured with an output buffer and becomes the response body
- use superglobals directly —
$_GET,$_POST,$_REQUEST,$_COOKIE,$_FILES,$_SESSION,$_SERVER
A few system functions have to be routed through WorkermanHttpd static wrappers
(same parameters as the native functions):
| function | use instead |
|---|---|
header() |
WorkermanHttpd::header() |
setcookie() |
WorkermanHttpd::setcookie() |
exit() |
WorkermanHttpd::exit() |
session_start() |
WorkermanHttpd::session_start() |
session_id() |
WorkermanHttpd::session_id() |
session_destroy() |
WorkermanHttpd::session_destroy() |
session_set_save_handler() |
WorkermanHttpd::session_set_save_handler() |
register_shutdown_function() |
WorkermanHttpd::register_shutdown_function() |
Call WorkermanHttpd::system_wrapper_get_providers() to list them.
Install
composer require dvaknheo/workermanhttpd
Requires PHP >= 7.2 and workerman/workerman ^4.0.4.
Basic usage
<?php require(__DIR__.'/vendor/autoload.php'); function hello() { \WorkermanHttpd\WorkermanHttpd::header('X-Test: '.DATE(DATE_ATOM)); echo "<h1>hello ,have a good start.</h1>\n"; return true; // true = handled; false = 404 } \WorkermanHttpd\WorkermanHttpd::RunQuickly([ 'host' => '127.0.0.1', 'port' => 8080, 'http_handler' => 'hello', ]);
Browse http://127.0.0.1:8080/.
Using it with DuckPhp
This library implements DuckPhp's official HTTP-server plug-in slot, so point
--http_server at it:
php bin/cli.php run --http_server=WorkermanHttpd/HttpServerForDuckPhp --port=8080
DuckPhp\Component\Command::command_run() resolves http_server to a class name, swaps the
HttpServer singleton for it, and then calls init($options)->run() on your class. This
library therefore ships:
WorkermanHttpd\WorkermanHttpd— the main class; optionhttp_app_classnames your DuckPhp application class;WorkermanHttpd\HttpServerForDuckPhp— the same class plus a declaration ofDuckPhp\HttpServer\HttpServerInterface.
Note:
HttpServerForDuckPhprequires DuckPhp at compile time (implementsis resolved then), which is exactly why it lives in its own file — users without DuckPhp installed never autoload it.
You can also start the server yourself, without DuckPhp's CLI:
\WorkermanHttpd\WorkermanHttpd::RunQuickly([ 'port' => 8080, 'http_app_class' => \YourProject\System\App::class, 'path' => __DIR__, ]);
Options
$options = [ // listen 'host' => '127.0.0.1', 'port' => 8080, // Workerman 'worker_name' => 'WorkermanHttpd', 'worker_count' => -1, // -1 = CPU cores x 2 'worker_properties' => [], // assigned straight onto Workerman\Worker 'pid_file' => null, // becomes Worker::$pidFile; used by getPid()/close() 'stdout_file' => null, // becomes Worker::$stdoutFile 'command' => 'start', // start / stop / reload / restart 'background' => false, // daemon mode (not supported on Windows) 'gracefull' => false, // -g graceful restart // request / paths 'path' => '', // project root; defaults to the startup cwd 'doc_root' => null, // $_SERVER['DOCUMENT_ROOT']; auto-detects path/public 'request_class' => '', // defaults to WorkermanHttpd\Request 'http_enable_cache' => false, // Workerman's request-object cache; must stay off in a worker // DuckPhp 'http_app_class' => null, // FQCN of the DuckPhp application class 'http_app_options' => [], // extra options merged into the application // Components renewed for each request (clone + re-init). See below. // Set to false to disable the per-request container entirely. 'http_app_renew_classes' => [ 'DuckPhp\\Core\\Route', 'DuckPhp\\Core\\View', 'DuckPhp\\Component\\Lang', ], // http_handler mode 'http_handler' => null, 'http_exception_handler' => null, 'http_404_handler' => null, // file modes 'http_handler_basepath' => '', // prefix for the two paths below 'http_handler_root' => null, // document root: resolve a file per URL path 'http_handler_file' => null, // single entry file: every URL goes here 'with_http_handler_root' => false, // let a false http_handler fall through 'with_http_handler_file' => false, // let a root miss fall through to the entry file 'enable_resource_file' => true, // send non-PHP files found under the root ];
File modes
// Equivalent to nginx's `root`: run the PHP file a URL resolves to, send static files. \WorkermanHttpd\WorkermanHttpd::RunQuickly([ 'port' => 8080, 'http_handler_root' => __DIR__.'/public', ]); // Single entry file: every URL is handled by one file (REQUEST_URI/PATH_INFO preserved). \WorkermanHttpd\WorkermanHttpd::RunQuickly([ 'port' => 8080, 'http_handler_file' => __DIR__.'/public/index.php', ]);
http_handler_root supports / → index.php, an exact file, a directory's
index.php, xxx.php/extra (the remainder becomes PATH_INFO), and walking up the
path looking for the first index.php. Static files are streamed by Workerman
(Response::withFile()), so large resources are not read into memory.
Security: the path is split on / and any . or .. segment is a 404; then a
realpath() containment check (which also resolves symlinks) confirms the final file
is still inside the document root. Both are needed.
Per-request isolation in a resident worker
A Workerman worker is a resident process, while DuckPhp keeps every component
singleton in a process-level static (PhaseContainer::$instance). Left alone,
state a request writes into a component outlives that request — the measured symptom
is that a single request calling Route::_()->addRouteHook(...) permanently
swallows every later request of that worker.
So the server installs WorkermanHttpd\PerRequestPhaseContainer by default: the
container as it stands right after application init is kept untouched as a template,
each request gets a copy of it, and the classes listed in http_app_renew_classes are
replaced by a clone of the template instance, re-init()ed. Cloning (rather than
constructing) is the important part: Route receives its route hooks from the
RouteHook components during application init, and a freshly built Route would have
none of them.
Everything not listed is shared by reference — database connections, loggers, the
application object — because requests within a worker are sequential; only
per-request state needs renewing. If your own extension keeps per-request state, add
its class name to http_app_renew_classes.
Classes
Everything except the main class and SingletonExTrait is stateless.
WorkermanHttpd
The main class. Uses SingletonExTrait (a replaceable singleton).
Static: RunQuickly($options), _($object = null), G($object = null), Request(),
Response(), Worker(), OnWorkerStart($worker), OnMessage($connection, $request),
OnMasterReload(), plus the system wrappers listed above.
Instance: init(array $options, ?object $context = null), run(), getPid(), close().
HttpServerForDuckPhp
A WorkermanHttpd subclass that additionally declares
DuckPhp\HttpServer\HttpServerInterface (RunQuickly / run / getPid / close), and
understands the -H/-P/-t/-b shorthand switches. This is what
--http_server=WorkermanHttpd/HttpServerForDuckPhp loads.
Request / Response
Extend Workerman\Protocols\Http\Request / Response and use SingletonExTrait.
Replace them with Request::G(MyRequest::G()).
ExitException
The interruption exception. Do not throw it directly — call WorkermanHttpd::exit().
When __EXIT_EXCEPTION is already defined (DuckPhp defines it as
DuckPhp\Core\ExitException), exit() throws that class, which is what makes DuckPhp's
exception manager swallow it silently.
SingletonExTrait
A replaceable-singleton trait with the same effect as DuckPhp's; it honours the
__SINGLETONEX_REPALACER macro.
Testing
The repository ships an end-to-end acceptance suite: it really starts a Workerman server, drives it over real HTTP, then kills it.
composer install php dev/e2e.php # everything php dev/e2e.php --list # list cases php dev/e2e.php --filter=duckphp --dump
It needs a local DuckPhp checkout (default ../DNMVCS, override with DUCKPHP_PATH).
dev/server.php is the test server entry point and dev/project/ is a minimal DuckPhp demo
project.
Notes
- This library does not depend on DuckPhp; DuckPhp appears only in
require-devand inHttpServerForDuckPhp. - In a resident worker
$_SERVERis rebuilt from a startup snapshot for every request, so nothing leaks from one request into the next. - Workerman does not support multiple processes or
--backgroundon Windows; useworker_count = 1there.