276 lines
5.2 KiB
PHP
276 lines
5.2 KiB
PHP
<?php
|
|
|
|
namespace alveolata\server;
|
|
|
|
require_once(DIR_ALVEOLATA . '/log/functions.php');
|
|
|
|
|
|
/**
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
*/
|
|
class struct_subject/*<type_input,type_output>*/
|
|
{
|
|
|
|
/**
|
|
* @var int
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
*/
|
|
public $port;
|
|
|
|
|
|
/**
|
|
* @var function<type_input,type_output>
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
*/
|
|
public $handler;
|
|
|
|
|
|
/**
|
|
* whether a global handler for the operating systems interrupt signal (SIGINT) shall be installed for stopping the
|
|
* server gracfully
|
|
*
|
|
* @var boolean
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
*/
|
|
public $install_killhandler;
|
|
|
|
|
|
/**
|
|
* procedure to be executed on start
|
|
*
|
|
* @var function<tuple<>,void>
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
*/
|
|
public $on_starting;
|
|
|
|
|
|
/**
|
|
* procedure to be executed on stop
|
|
*
|
|
* @var function<tuple<>,void>
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
*/
|
|
public $on_stopping;
|
|
|
|
|
|
/**
|
|
* procedure to be executed on start
|
|
*
|
|
* @var function<tuple<>,void>
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
*/
|
|
public $on_started;
|
|
|
|
|
|
/**
|
|
* procedure to be executed on stop
|
|
*
|
|
* @var function<tuple<>,void>
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
*/
|
|
public $on_stopped;
|
|
|
|
|
|
/**
|
|
* @var any
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
*/
|
|
public $socket;
|
|
|
|
|
|
/**
|
|
* whether the server is supposed to shutdown
|
|
*
|
|
* @var boolean
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
*/
|
|
public $run;
|
|
|
|
|
|
/**
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
*/
|
|
public function __construct(
|
|
int $port,
|
|
\Closure $handler,
|
|
bool $install_killhandler,
|
|
\Closure $on_starting,
|
|
\Closure $on_stopping,
|
|
\Closure $on_started,
|
|
\Closure $on_stopped
|
|
)
|
|
{
|
|
$this->port = $port;
|
|
$this->handler = $handler;
|
|
$this->install_killhandler = $install_killhandler;
|
|
$this->on_starting = $on_starting;
|
|
$this->on_stopping = $on_stopping;
|
|
$this->on_started = $on_started;
|
|
$this->on_stopped = $on_stopped;
|
|
$this->socket = null;
|
|
$this->run = false;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
* @param int $port
|
|
* @param function<type_input,type_output> $handler
|
|
* @param boolean $install_killhandler
|
|
* @param function<tuple<>,void> [$on_starting]
|
|
* @param function<tuple<>,void> [$on_stopping]
|
|
* @param function<tuple<>,void> [$on_started]
|
|
* @param function<tuple<>,void> [$on_stopped]
|
|
* @return struct_subject<type_input,type_output>
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
*/
|
|
function make(
|
|
int $port,
|
|
\Closure $handler,
|
|
bool $install_killhandler = false,
|
|
\Closure $on_starting = null,
|
|
\Closure $on_stopping = null,
|
|
\Closure $on_started = null,
|
|
\Closure $on_stopped = null
|
|
) : struct_subject
|
|
{
|
|
if ($on_starting === null) {
|
|
$on_starting = function () {
|
|
\alveolata\log\info('starting server …');
|
|
};
|
|
}
|
|
if ($on_stopping === null) {
|
|
$on_stopping = function () {
|
|
\alveolata\log\info('stopping server …');
|
|
};
|
|
}
|
|
if ($on_started === null) {
|
|
$on_started = function () {
|
|
\alveolata\log\info('server started');
|
|
};
|
|
}
|
|
if ($on_stopped === null) {
|
|
$on_stopped = function () {
|
|
\alveolata\log\info('server stopped');
|
|
};
|
|
}
|
|
return (
|
|
new struct_subject(
|
|
$port,
|
|
$handler,
|
|
$install_killhandler,
|
|
$on_starting,
|
|
$on_stopping,
|
|
$on_started,
|
|
$on_stopped
|
|
)
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* @param struct_subject<type_input,type_output> $subject
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
*/
|
|
function killhandler(
|
|
struct_subject/*<type_input,type_output>*/ $subject
|
|
) : void
|
|
{
|
|
\alveolata\log\warning(
|
|
'killhandler for server won\'t work'
|
|
);
|
|
/*
|
|
\pcntl_signal(
|
|
SIGINT,
|
|
function ($signo) use (&$subject) {stop($subject);}
|
|
);
|
|
*/
|
|
}
|
|
|
|
|
|
/**
|
|
* @param struct_subject<type_input,type_output> $subject
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
*/
|
|
function stop/*<type_input,type_output>*/(
|
|
struct_subject/*<type_input,type_output>*/ $subject
|
|
) : void
|
|
{
|
|
($subject->on_stopping)();
|
|
if (! ($subject->socket === null)) {
|
|
\socket_shutdown($subject->socket);
|
|
}
|
|
$subject->run = false;
|
|
}
|
|
|
|
|
|
/**
|
|
* @param struct_subject<type_input, type_output> $subject
|
|
* @author Christian Fraß <frass@greenscale.de>
|
|
*/
|
|
function start/*<type_input, type_output>*/(
|
|
struct_subject/*<type_input, type_output>*/ $subject
|
|
) : void
|
|
{
|
|
$conf = [
|
|
'host' => 'localhost',
|
|
'max_connections' => 7,
|
|
'buffer_length' => 2048,
|
|
];
|
|
($subject->on_starting)();
|
|
if ($subject->install_killhandler) {
|
|
killhandler($subject);
|
|
}
|
|
$subject->run = true;
|
|
$socket = \socket_create(
|
|
AF_INET,
|
|
SOCK_STREAM,
|
|
SOL_TCP
|
|
);
|
|
// \socket_set_nonblock($socket);
|
|
\socket_bind(
|
|
$socket,
|
|
$conf['host'],
|
|
$subject->port
|
|
);
|
|
\socket_listen(
|
|
$socket,
|
|
$conf['max_connections']
|
|
);
|
|
($subject->on_started)();
|
|
while ($subject->run) {
|
|
$subject->socket = $request = \socket_accept(
|
|
$socket
|
|
);
|
|
/*if ($request === false) {
|
|
usleep(125000);
|
|
}
|
|
else {*/
|
|
$input = \socket_read(
|
|
$request,
|
|
$conf['buffer_length']
|
|
);
|
|
$output = ($subject->handler)($input);
|
|
$bytes = \socket_write(
|
|
$request,
|
|
$output,
|
|
strlen($output)
|
|
);
|
|
$successful = ($bytes !== false);
|
|
if (! $successful) {
|
|
\error_log('could not write to socket; shutting down server');
|
|
break;
|
|
}
|
|
/*}*/
|
|
}
|
|
\socket_close(
|
|
$request
|
|
);
|
|
\socket_close(
|
|
$socket
|
|
);
|
|
($subject->on_stopped)();
|
|
}
|
|
|
|
?>
|