-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #20 from xp-forge/feature/development-server
Support development webserver
- Loading branch information
Showing
8 changed files
with
318 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<?php namespace xp\web; | ||
|
||
module xp-forge/web { | ||
|
||
/** Provide entry point for web-main.php */ | ||
public function initialize() { | ||
class_alias(WebRunner::class, 'xp\scriptlet\Runner'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
<?php namespace xp\web; | ||
|
||
use util\cmd\Console; | ||
use lang\Runtime; | ||
use lang\RuntimeOptions; | ||
use lang\CommandLine; | ||
use lang\ClassLoader; | ||
use lang\FileSystemClassLoader; | ||
use lang\archive\ArchiveClassLoader; | ||
use peer\Socket; | ||
use io\IOException; | ||
|
||
class Develop { | ||
private $host, $port; | ||
|
||
/** | ||
* Creates a new instance | ||
* | ||
* @param string $host | ||
* @param int $port | ||
*/ | ||
public function __construct($host, $port) { | ||
$this->host= $host; | ||
$this->port= $port; | ||
} | ||
|
||
/** | ||
* Serve requests | ||
* | ||
* @param string $source | ||
* @param string $profile | ||
* @param io.Path $webroot | ||
* @param io.Path $docroot | ||
* @param string[] $config | ||
*/ | ||
public function serve($source, $profile, $webroot, $docroot, $config) { | ||
|
||
// PHP doesn't start with a nonexistant document root | ||
if (!$docroot->exists()) { | ||
$docroot= getcwd(); | ||
} | ||
|
||
// Inherit all currently loaded paths acceptable to bootstrapping | ||
$include= '.'.PATH_SEPARATOR.PATH_SEPARATOR.'.'; | ||
foreach (ClassLoader::getLoaders() as $delegate) { | ||
if ($delegate instanceof FileSystemClassLoader || $delegate instanceof ArchiveClassLoader) { | ||
$include.= PATH_SEPARATOR.$delegate->path; | ||
} | ||
} | ||
|
||
// Start `php -S`, the development webserver | ||
$runtime= Runtime::getInstance(); | ||
$arguments= ['-S', $this->host.':'.$this->port, '-t', $docroot]; | ||
$cmd= CommandLine::forName(PHP_OS)->compose($runtime->getExecutable()->getFileName(), array_merge( | ||
$arguments, | ||
$runtime->startupOptions()->withSetting('user_dir', $docroot)->withSetting('include_path', $include)->asArguments(), | ||
[$runtime->bootStrapScript('web')] | ||
)); | ||
|
||
// Export environment | ||
putenv('DOCUMENT_ROOT='.$docroot); | ||
putenv('SERVER_PROFILE='.$profile); | ||
putenv('WEB_SOURCE='.$source); | ||
putenv('WEB_CONFIG='.implode('PATH_SEPARATOR', $config)); | ||
putenv('WEB_ROOT='.$webroot); | ||
|
||
Console::writeLine("\e[33m@", nameof($this), "(HTTP @ `php ", implode(' ', $arguments), "`)\e[0m"); | ||
Console::writeLine("\e[1mServing ", $source, $config, "\e[0m"); | ||
Console::writeLine("\e[36m", str_repeat('═', 72), "\e[0m"); | ||
Console::writeLine(); | ||
|
||
if (!($proc= proc_open($cmd, [STDIN, STDOUT, STDERR], $pipes, null, null, ['bypass_shell' => true]))) { | ||
throw new IOException('Cannot execute `'.$runtime->getExecutable()->getFileName().'`'); | ||
} | ||
|
||
Console::writeLine("\e[33;1m>\e[0m Server started: \e[35;4mhttp://$this->host:$this->port\e[0m (", date('r'), ')'); | ||
Console::writeLine(' PID ', getmypid(), ' : ', proc_get_status($proc)['pid'], '; press Enter to exit'); | ||
Console::writeLine(); | ||
|
||
// Inside `xp -supervise`, connect to signalling socket | ||
if ($port= getenv('XP_SIGNAL')) { | ||
$s= new Socket('127.0.0.1', $port); | ||
$s->connect(); | ||
$s->canRead(null) && $s->read(); | ||
$s->close(); | ||
} else { | ||
Console::read(); | ||
Console::write('> Shut down '); | ||
} | ||
|
||
// Wait for shutdown | ||
proc_terminate($proc, 2); | ||
do { | ||
Console::write('.'); | ||
$status= proc_get_status($proc); | ||
} while ($status['running']); | ||
|
||
proc_close($proc); | ||
Console::writeLine(); | ||
Console::writeLine("\e[33;1m>\e[0m Server stopped. (", date('r'), ')'); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,100 @@ | ||
<?php namespace xp\web; | ||
|
||
/** | ||
* Wrapper for PHP's Server API ("SAPI"). | ||
* | ||
* @see http://php.net/reserved.variables.server | ||
* @see http://php.net/wrappers.php | ||
* @see http://php.net/php-sapi-name | ||
*/ | ||
class SAPI extends \web\io\Output implements \web\io\Input { | ||
private $in= null; | ||
private $out; | ||
|
||
/** @return string */ | ||
public function method() { return $_SERVER['REQUEST_METHOD']; } | ||
|
||
/** @return string */ | ||
public function scheme() { return 'http'; } | ||
|
||
/** @return string */ | ||
public function uri() { return $_SERVER['REQUEST_URI']; } | ||
|
||
/** @return [:string] */ | ||
public function headers() { return getallheaders(); } | ||
|
||
/** @return string */ | ||
public function readLine() { | ||
if (null === $this->in) { | ||
$this->in= fopen('php://input', 'rb'); | ||
} | ||
return fgets($this->in, 8192); | ||
} | ||
|
||
/** | ||
* Read from request | ||
* | ||
* @param int $length | ||
* @return string | ||
*/ | ||
public function read($length= -1) { | ||
if (null === $this->in) { | ||
$this->in= fopen('php://input', 'rb'); | ||
} | ||
|
||
if (-1 === $length) { | ||
$r= ''; | ||
while (!feof($this->in)) { | ||
$r.= fread($this->in, 8192); | ||
} | ||
return $r; | ||
} else { | ||
return fread($this->in, $length); | ||
} | ||
} | ||
|
||
/** | ||
* Start response | ||
* | ||
* @param int $status | ||
* @param string $message | ||
* @param [:string] $headers | ||
* @return void | ||
*/ | ||
public function begin($status, $message, $headers) { | ||
if ('cgi' === PHP_SAPI || 'cgi-fcgi' === PHP_SAPI) { | ||
header('Status: '.$status.' '.$message); | ||
} else { | ||
header('HTTP/1.1 '.$status.' '.$message); | ||
} | ||
|
||
foreach ($headers as $name => $value) { | ||
header($name.': '.$value); | ||
} | ||
$this->out= ''; | ||
} | ||
|
||
/** | ||
* Write to response | ||
* | ||
* @param int $bytes | ||
* @return void | ||
*/ | ||
public function write($bytes) { | ||
$this->out.= $bytes; | ||
} | ||
|
||
/** @return void */ | ||
public function flush() { | ||
echo $this->out; | ||
$this->out= ''; | ||
} | ||
|
||
/** @return void */ | ||
public function finish() { | ||
if ($this->in) { | ||
fclose($this->in); | ||
} | ||
echo $this->out; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
<?php namespace xp\web; | ||
|
||
use web\Environment; | ||
use web\Request; | ||
use web\Response; | ||
use web\Error; | ||
use web\Status; | ||
use web\InternalServerError; | ||
use lang\ClassLoader; | ||
|
||
/** | ||
* Entry point for web-main.php | ||
*/ | ||
class WebRunner { | ||
|
||
/** | ||
* Logs a request | ||
* | ||
* @param web.Request $response | ||
* @param web.Response $response | ||
* @param string $message | ||
* @return void | ||
*/ | ||
private static function log($request, $response, $message= null) { | ||
$query= $request->uri()->query(); | ||
fprintf(STDERR, | ||
" \e[33m[%s %d %.3fkB]\e[0m %d %s %s %s\n", | ||
date('Y-m-d H:i:s'), | ||
getmypid(), | ||
memory_get_usage() / 1024, | ||
$response->status(), | ||
$request->method(), | ||
$request->uri()->path().($query ? '?'.$query : ''), | ||
$message | ||
); | ||
} | ||
|
||
/** | ||
* Sends an error | ||
* | ||
* @param web.Request $response | ||
* @param web.Response $response | ||
* @param web.Error $error | ||
* @param string $profile | ||
* @return void | ||
*/ | ||
private static function error($request, $response, $error, $profile) { | ||
$loader= ClassLoader::getDefault(); | ||
$message= Status::message($error->status()); | ||
|
||
$response->answer($error->status(), $message); | ||
foreach (['web/error-'.$profile.'.html', 'web/error.html'] as $variant) { | ||
if (!$loader->providesResource($variant)) continue; | ||
$response->send(sprintf( | ||
$loader->getResource($variant), | ||
$error->status(), | ||
htmlspecialchars($message), | ||
htmlspecialchars($error->getMessage()), | ||
htmlspecialchars($error->toString()) | ||
)); | ||
break; | ||
} | ||
self::log($request, $response, $error->toString()); | ||
} | ||
|
||
/** @param string[] $args */ | ||
public static function main($args) { | ||
$env= new Environment($args[2], $args[0], $args[1], explode('PATH_SEPARATOR', getenv('WEB_CONFIG'))); | ||
$application= (new Source(getenv('WEB_SOURCE'), $env))->application(); | ||
|
||
$sapi= new SAPI(); | ||
$request= new Request($sapi); | ||
$response= new Response($sapi); | ||
$response->header('Date', gmdate('D, d M Y H:i:s T')); | ||
$response->header('Host', $request->header('Host')); | ||
|
||
try { | ||
$application->service($request, $response); | ||
self::log($request, $response); | ||
} catch (Error $e) { | ||
self::error($request, $response, $e); | ||
} catch (\Throwable $e) { // PHP7 | ||
self::error($request, $response, new InternalServerError($e), $args[2]); | ||
} catch (\Exception $e) { // PHP5 | ||
self::error($request, $response, new InternalServerError($e), $args[2]); | ||
} finally { | ||
$response->flushed() || $response->flush(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters