feature #21039 Web server bundle (fabpot)
This PR was squashed before being merged into the 3.3-dev branch (closes #21039). Discussion ---------- Web server bundle | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | yes | Deprecations? | no | Tests pass? | yes | Fixed tickets | #21040 | License | MIT | Doc PR | n/a Moved the `server:*` commands to a new bundle. It makes them more easily discoverable and more decoupled. Discoverability is important when not using symfony/symfony. In that case, the commands are not available unless you have the symfony/process component installed. With a dedicated bundle, installing the bundle also installs the dependency, making the whole process easier. Usage is the same as the current commands for basic usage: To start a web server in the foreground: ``` bin/console server:run ``` To manage a background server: ``` bin/console server:start bin/console server:stop bin/console server:status ``` The big difference is that port is auto-determined if something is already listening on port 8000. Usage is **different** if you pass options: ``` bin/console server:start 127.0.0.1:8888 bin/console server:stop # no need to pass the address again bin/console server:status # no need to pass the address again ``` That's possible as the web server now stores its address in a pid file stored in the current directory. Commits -------f39b32735b
[WebServerBundle] switched auto-run of server:start to off by default961d1cea55
[WebServerBundle] fixed server:start when already running126f4d9ec4
[WebServerBundle] added support for port auto-detection6f689d6597
[WebServerBundle] changed the way we keep track of the web server585d4451c8
[WebServerBundle] tweaked command docsfa7ebc57de
[WebServerBundle] moved most of the logic in a new class951a1a227d
[WebServerBundle] changed wordingac1ba7700e
made the router configurable via env vars48dd2b0dbe
removed obsolete check132902c621
moved server:* command to a new bundle
This commit is contained in:
commit
be85fcc2b8
@ -73,6 +73,7 @@
|
||||
"symfony/validator": "self.version",
|
||||
"symfony/var-dumper": "self.version",
|
||||
"symfony/web-profiler-bundle": "self.version",
|
||||
"symfony/web-server-bundle": "self.version",
|
||||
"symfony/workflow": "self.version",
|
||||
"symfony/yaml": "self.version"
|
||||
},
|
||||
|
@ -4,6 +4,7 @@ CHANGELOG
|
||||
3.3.0
|
||||
-----
|
||||
|
||||
* The server:* commands and their associated router files were moved to WebServerBundle
|
||||
* Translation related services are not loaded anymore when the `framework.translator` option
|
||||
is disabled.
|
||||
* Added `GlobalVariables::getToken()`
|
||||
|
@ -1,69 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle\Command;
|
||||
|
||||
/**
|
||||
* Base methods for commands related to PHP's built-in web server.
|
||||
*
|
||||
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
|
||||
*/
|
||||
abstract class ServerCommand extends ContainerAwareCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isEnabled()
|
||||
{
|
||||
if (defined('HHVM_VERSION')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!class_exists('Symfony\Component\Process\Process')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return parent::isEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the name of the lock file for a particular PHP web server process.
|
||||
*
|
||||
* @param string $address An address/port tuple
|
||||
*
|
||||
* @return string The filename
|
||||
*/
|
||||
protected function getLockFile($address)
|
||||
{
|
||||
return sys_get_temp_dir().'/'.strtr($address, '.:', '--').'.pid';
|
||||
}
|
||||
|
||||
protected function isOtherServerProcessRunning($address)
|
||||
{
|
||||
$lockFile = $this->getLockFile($address);
|
||||
|
||||
if (file_exists($lockFile)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
list($hostname, $port) = explode(':', $address);
|
||||
|
||||
$fp = @fsockopen($hostname, $port, $errno, $errstr, 5);
|
||||
|
||||
if (false !== $fp) {
|
||||
fclose($fp);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,175 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle\Command;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Symfony\Component\Process\ProcessBuilder;
|
||||
use Symfony\Component\Process\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* Runs Symfony application using PHP built-in web server.
|
||||
*
|
||||
* @author Michał Pipa <michal.pipa.xsolve@gmail.com>
|
||||
*/
|
||||
class ServerRunCommand extends ServerCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setDefinition(array(
|
||||
new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1'),
|
||||
new InputOption('port', 'p', InputOption::VALUE_REQUIRED, 'Address port number', '8000'),
|
||||
new InputOption('docroot', 'd', InputOption::VALUE_REQUIRED, 'Document root', null),
|
||||
new InputOption('router', 'r', InputOption::VALUE_REQUIRED, 'Path to custom router script'),
|
||||
))
|
||||
->setName('server:run')
|
||||
->setDescription('Runs PHP built-in web server')
|
||||
->setHelp(<<<'EOF'
|
||||
The <info>%command.name%</info> runs PHP built-in web server:
|
||||
|
||||
<info>%command.full_name%</info>
|
||||
|
||||
To change default bind address and port use the <info>address</info> argument:
|
||||
|
||||
<info>%command.full_name% 127.0.0.1:8080</info>
|
||||
|
||||
To change default docroot directory use the <info>--docroot</info> option:
|
||||
|
||||
<info>%command.full_name% --docroot=htdocs/</info>
|
||||
|
||||
If you have custom docroot directory layout, you can specify your own
|
||||
router script using <info>--router</info> option:
|
||||
|
||||
<info>%command.full_name% --router=app/config/router.php</info>
|
||||
|
||||
Specifing a router script is required when the used environment is not "dev",
|
||||
"prod", or "test".
|
||||
|
||||
See also: http://www.php.net/manual/en/features.commandline.webserver.php
|
||||
|
||||
EOF
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$documentRoot = $input->getOption('docroot');
|
||||
|
||||
if (null === $documentRoot) {
|
||||
$documentRoot = $this->getContainer()->getParameter('kernel.root_dir').'/../web';
|
||||
}
|
||||
|
||||
if (!is_dir($documentRoot)) {
|
||||
$io->error(sprintf('The given document root directory "%s" does not exist', $documentRoot));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$env = $this->getContainer()->getParameter('kernel.environment');
|
||||
$address = $input->getArgument('address');
|
||||
|
||||
if (false === strpos($address, ':')) {
|
||||
$address = $address.':'.$input->getOption('port');
|
||||
}
|
||||
|
||||
if ($this->isOtherServerProcessRunning($address)) {
|
||||
$io->error(sprintf('A process is already listening on http://%s.', $address));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ('prod' === $env) {
|
||||
$io->error('Running PHP built-in server in production environment is NOT recommended!');
|
||||
}
|
||||
|
||||
$io->success(sprintf('Server running on http://%s', $address));
|
||||
$io->comment('Quit the server with CONTROL-C.');
|
||||
|
||||
if (null === $builder = $this->createPhpProcessBuilder($io, $address, $input->getOption('router'), $env)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$builder->setWorkingDirectory($documentRoot);
|
||||
$builder->setTimeout(null);
|
||||
$process = $builder->getProcess();
|
||||
$callback = null;
|
||||
|
||||
if (OutputInterface::VERBOSITY_NORMAL > $output->getVerbosity()) {
|
||||
$process->disableOutput();
|
||||
} else {
|
||||
try {
|
||||
$process->setTty(true);
|
||||
} catch (RuntimeException $e) {
|
||||
$callback = function ($type, $buffer) use ($output) {
|
||||
if (Process::ERR === $type && $output instanceof ConsoleOutputInterface) {
|
||||
$output = $output->getErrorOutput();
|
||||
}
|
||||
$output->write($buffer, false, OutputInterface::OUTPUT_RAW);
|
||||
};
|
||||
}
|
||||
}
|
||||
$process->run($callback);
|
||||
|
||||
if (!$process->isSuccessful()) {
|
||||
$errorMessages = array('Built-in server terminated unexpectedly.');
|
||||
|
||||
if ($process->isOutputDisabled()) {
|
||||
$errorMessages[] = 'Run the command again with -v option for more details.';
|
||||
}
|
||||
|
||||
$io->error($errorMessages);
|
||||
}
|
||||
|
||||
return $process->getExitCode();
|
||||
}
|
||||
|
||||
private function createPhpProcessBuilder(SymfonyStyle $io, $address, $router, $env)
|
||||
{
|
||||
$router = $router ?: $this
|
||||
->getContainer()
|
||||
->get('kernel')
|
||||
->locateResource(sprintf('@FrameworkBundle/Resources/config/router_%s.php', $env))
|
||||
;
|
||||
|
||||
if (!file_exists($router)) {
|
||||
$io->error(sprintf('The given router script "%s" does not exist.', $router));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$router = realpath($router);
|
||||
$finder = new PhpExecutableFinder();
|
||||
|
||||
if (false === $binary = $finder->find()) {
|
||||
$io->error('Unable to find PHP binary to run server.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
return new ProcessBuilder(array($binary, '-S', $address, $router));
|
||||
}
|
||||
}
|
@ -1,234 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle\Command;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
use Symfony\Component\Process\Process;
|
||||
|
||||
/**
|
||||
* Runs PHP's built-in web server in a background process.
|
||||
*
|
||||
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
|
||||
*/
|
||||
class ServerStartCommand extends ServerCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setDefinition(array(
|
||||
new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1'),
|
||||
new InputOption('port', 'p', InputOption::VALUE_REQUIRED, 'Address port number', '8000'),
|
||||
new InputOption('docroot', 'd', InputOption::VALUE_REQUIRED, 'Document root', null),
|
||||
new InputOption('router', 'r', InputOption::VALUE_REQUIRED, 'Path to custom router script'),
|
||||
new InputOption('force', 'f', InputOption::VALUE_NONE, 'Force web server startup'),
|
||||
))
|
||||
->setName('server:start')
|
||||
->setDescription('Starts PHP built-in web server in the background')
|
||||
->setHelp(<<<'EOF'
|
||||
The <info>%command.name%</info> runs PHP's built-in web server:
|
||||
|
||||
<info>php %command.full_name%</info>
|
||||
|
||||
To change the default bind address and the default port use the <info>address</info> argument:
|
||||
|
||||
<info>php %command.full_name% 127.0.0.1:8080</info>
|
||||
|
||||
To change the default document root directory use the <info>--docroot</info> option:
|
||||
|
||||
<info>php %command.full_name% --docroot=htdocs/</info>
|
||||
|
||||
If you have a custom document root directory layout, you can specify your own
|
||||
router script using the <info>--router</info> option:
|
||||
|
||||
<info>php %command.full_name% --router=app/config/router.php</info>
|
||||
|
||||
Specifying a router script is required when the used environment is not <comment>"dev"</comment> or
|
||||
<comment>"prod"</comment>.
|
||||
|
||||
See also: http://www.php.net/manual/en/features.commandline.webserver.php
|
||||
|
||||
EOF
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $cliOutput = $output);
|
||||
|
||||
if (!extension_loaded('pcntl')) {
|
||||
$io->error(array(
|
||||
'This command needs the pcntl extension to run.',
|
||||
'You can either install it or use the "server:run" command instead to run the built-in web server.',
|
||||
));
|
||||
|
||||
if ($io->ask('Do you want to execute <info>server:run</info> immediately? [Yn] ', true)) {
|
||||
$command = $this->getApplication()->find('server:run');
|
||||
|
||||
return $command->run($input, $cliOutput);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$documentRoot = $input->getOption('docroot');
|
||||
|
||||
if (null === $documentRoot) {
|
||||
$documentRoot = $this->getContainer()->getParameter('kernel.root_dir').'/../web';
|
||||
}
|
||||
|
||||
if (!is_dir($documentRoot)) {
|
||||
$io->error(sprintf('The given document root directory "%s" does not exist.', $documentRoot));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$env = $this->getContainer()->getParameter('kernel.environment');
|
||||
|
||||
if (false === $router = $this->determineRouterScript($input->getOption('router'), $env, $io)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$address = $input->getArgument('address');
|
||||
|
||||
if (false === strpos($address, ':')) {
|
||||
$address = $address.':'.$input->getOption('port');
|
||||
}
|
||||
|
||||
if (!$input->getOption('force') && $this->isOtherServerProcessRunning($address)) {
|
||||
$io->error(array(
|
||||
sprintf('A process is already listening on http://%s.', $address),
|
||||
'Use the --force option if the server process terminated unexpectedly to start a new web server process.',
|
||||
));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ('prod' === $env) {
|
||||
$io->error('Running PHP built-in server in production environment is NOT recommended!');
|
||||
}
|
||||
|
||||
$pid = pcntl_fork();
|
||||
|
||||
if ($pid < 0) {
|
||||
$io->error('Unable to start the server process.');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if ($pid > 0) {
|
||||
$io->success(sprintf('Web server listening on http://%s', $address));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (posix_setsid() < 0) {
|
||||
$io->error('Unable to set the child process as session leader');
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (null === $process = $this->createServerProcess($io, $address, $documentRoot, $router)) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
$process->disableOutput();
|
||||
$process->start();
|
||||
$lockFile = $this->getLockFile($address);
|
||||
touch($lockFile);
|
||||
|
||||
if (!$process->isRunning()) {
|
||||
$io->error('Unable to start the server process');
|
||||
unlink($lockFile);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
// stop the web server when the lock file is removed
|
||||
while ($process->isRunning()) {
|
||||
if (!file_exists($lockFile)) {
|
||||
$process->stop();
|
||||
}
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine the absolute file path for the router script, using the environment to choose a standard script
|
||||
* if no custom router script is specified.
|
||||
*
|
||||
* @param string|null $router File path of the custom router script, if set by the user; otherwise null
|
||||
* @param string $env The application environment
|
||||
* @param SymfonyStyle $io An SymfonyStyle instance
|
||||
*
|
||||
* @return string|bool The absolute file path of the router script, or false on failure
|
||||
*/
|
||||
private function determineRouterScript($router, $env, SymfonyStyle $io)
|
||||
{
|
||||
if (null === $router) {
|
||||
$router = $this
|
||||
->getContainer()
|
||||
->get('kernel')
|
||||
->locateResource(sprintf('@FrameworkBundle/Resources/config/router_%s.php', $env))
|
||||
;
|
||||
}
|
||||
|
||||
if (false === $path = realpath($router)) {
|
||||
$io->error(sprintf('The given router script "%s" does not exist.', $router));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a process to start PHP's built-in web server.
|
||||
*
|
||||
* @param SymfonyStyle $io A SymfonyStyle instance
|
||||
* @param string $address IP address and port to listen to
|
||||
* @param string $documentRoot The application's document root
|
||||
* @param string $router The router filename
|
||||
*
|
||||
* @return Process The process
|
||||
*/
|
||||
private function createServerProcess(SymfonyStyle $io, $address, $documentRoot, $router)
|
||||
{
|
||||
$finder = new PhpExecutableFinder();
|
||||
if (false === $binary = $finder->find()) {
|
||||
$io->error('Unable to find PHP binary to start server.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$script = implode(' ', array_map(array('Symfony\Component\Process\ProcessUtils', 'escapeArgument'), array(
|
||||
$binary,
|
||||
'-S',
|
||||
$address,
|
||||
$router,
|
||||
)));
|
||||
|
||||
return new Process('exec '.$script, $documentRoot, null, null, null);
|
||||
}
|
||||
}
|
@ -1,79 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle\Command;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
/**
|
||||
* Shows the status of a process that is running PHP's built-in web server in
|
||||
* the background.
|
||||
*
|
||||
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
|
||||
*/
|
||||
class ServerStatusCommand extends ServerCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setDefinition(array(
|
||||
new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1:8000'),
|
||||
new InputOption('port', 'p', InputOption::VALUE_REQUIRED, 'Address port number', '8000'),
|
||||
))
|
||||
->setName('server:status')
|
||||
->setDescription('Outputs the status of the built-in web server for the given address')
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$address = $input->getArgument('address');
|
||||
|
||||
if (false === strpos($address, ':')) {
|
||||
$address = $address.':'.$input->getOption('port');
|
||||
}
|
||||
|
||||
// remove an orphaned lock file
|
||||
if (file_exists($this->getLockFile($address)) && !$this->isServerRunning($address)) {
|
||||
unlink($this->getLockFile($address));
|
||||
}
|
||||
|
||||
if (file_exists($this->getLockFile($address))) {
|
||||
$io->success(sprintf('Web server still listening on http://%s', $address));
|
||||
} else {
|
||||
$io->warning(sprintf('No web server is listening on http://%s', $address));
|
||||
}
|
||||
}
|
||||
|
||||
private function isServerRunning($address)
|
||||
{
|
||||
list($hostname, $port) = explode(':', $address);
|
||||
|
||||
if (false !== $fp = @fsockopen($hostname, $port, $errno, $errstr, 1)) {
|
||||
fclose($fp);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file implements rewrite rules for PHP built-in web server.
|
||||
*
|
||||
* See: http://www.php.net/manual/en/features.commandline.webserver.php
|
||||
*
|
||||
* If you have custom directory layout, then you have to write your own router
|
||||
* and pass it as a value to 'router' option of server:run command.
|
||||
*
|
||||
* @author: Michał Pipa <michal.pipa.xsolve@gmail.com>
|
||||
* @author: Albert Jessurum <ajessu@gmail.com>
|
||||
*/
|
||||
|
||||
// Workaround https://bugs.php.net/64566
|
||||
if (ini_get('auto_prepend_file') && !in_array(realpath(ini_get('auto_prepend_file')), get_included_files(), true)) {
|
||||
require ini_get('auto_prepend_file');
|
||||
}
|
||||
|
||||
if (is_file($_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR.$_SERVER['SCRIPT_NAME'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$_SERVER = array_merge($_SERVER, $_ENV);
|
||||
$_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR.'app.php';
|
||||
|
||||
// Since we are rewriting to app.php, adjust SCRIPT_NAME and PHP_SELF accordingly
|
||||
$_SERVER['SCRIPT_NAME'] = DIRECTORY_SEPARATOR.'app.php';
|
||||
$_SERVER['PHP_SELF'] = DIRECTORY_SEPARATOR.'app.php';
|
||||
|
||||
require 'app.php';
|
||||
|
||||
error_log(sprintf('%s:%d [%d]: %s', $_SERVER['REMOTE_ADDR'], $_SERVER['REMOTE_PORT'], http_response_code(), $_SERVER['REQUEST_URI']), 4);
|
@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file implements rewrite rules for PHP built-in web server.
|
||||
*
|
||||
* See: http://www.php.net/manual/en/features.commandline.webserver.php
|
||||
*
|
||||
* If you have custom directory layout, then you have to write your own router
|
||||
* and pass it as a value to 'router' option of server:run command.
|
||||
*
|
||||
* @author: Michał Pipa <michal.pipa.xsolve@gmail.com>
|
||||
* @author: Albert Jessurum <ajessu@gmail.com>
|
||||
*/
|
||||
|
||||
if (is_file($_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR.$_SERVER['SCRIPT_NAME'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$_SERVER = array_merge($_SERVER, $_ENV);
|
||||
$_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR.'app_test.php';
|
||||
|
||||
require 'app_test.php';
|
7
src/Symfony/Bundle/WebServerBundle/CHANGELOG.md
Normal file
7
src/Symfony/Bundle/WebServerBundle/CHANGELOG.md
Normal file
@ -0,0 +1,7 @@
|
||||
CHANGELOG
|
||||
=========
|
||||
|
||||
3.3.0
|
||||
-----
|
||||
|
||||
* Added bundle
|
30
src/Symfony/Bundle/WebServerBundle/Command/ServerCommand.php
Normal file
30
src/Symfony/Bundle/WebServerBundle/Command/ServerCommand.php
Normal file
@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\WebServerBundle\Command;
|
||||
|
||||
use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;
|
||||
|
||||
/**
|
||||
* Base methods for commands related to a local web server.
|
||||
*
|
||||
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
|
||||
*/
|
||||
abstract class ServerCommand extends ContainerAwareCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function isEnabled()
|
||||
{
|
||||
return !defined('HHVM_VERSION') && parent::isEnabled();
|
||||
}
|
||||
}
|
117
src/Symfony/Bundle/WebServerBundle/Command/ServerRunCommand.php
Normal file
117
src/Symfony/Bundle/WebServerBundle/Command/ServerRunCommand.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\WebServerBundle\Command;
|
||||
|
||||
use Symfony\Bundle\WebServerBundle\WebServer;
|
||||
use Symfony\Bundle\WebServerBundle\WebServerConfig;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Output\ConsoleOutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
/**
|
||||
* Runs Symfony application using a local web server.
|
||||
*
|
||||
* @author Michał Pipa <michal.pipa.xsolve@gmail.com>
|
||||
*/
|
||||
class ServerRunCommand extends ServerCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setDefinition(array(
|
||||
new InputArgument('addressport', InputArgument::OPTIONAL, 'The address to listen to (can be address:port, address, or port)'),
|
||||
new InputOption('docroot', 'd', InputOption::VALUE_REQUIRED, 'Document root'),
|
||||
new InputOption('router', 'r', InputOption::VALUE_REQUIRED, 'Path to custom router script'),
|
||||
))
|
||||
->setName('server:run')
|
||||
->setDescription('Runs a local web server')
|
||||
->setHelp(<<<'EOF'
|
||||
The <info>%command.name%</info> runs a local web server:
|
||||
|
||||
<info>%command.full_name%</info>
|
||||
|
||||
Change the default address and port by passing them as an argument:
|
||||
|
||||
<info>%command.full_name% 127.0.0.1:8080</info>
|
||||
|
||||
Use the <info>--docroot</info> option to change the default docroot directory:
|
||||
|
||||
<info>%command.full_name% --docroot=htdocs/</info>
|
||||
|
||||
Specify your own router script via the <info>--router</info> option:
|
||||
|
||||
<info>%command.full_name% --router=app/config/router.php</info>
|
||||
|
||||
See also: http://www.php.net/manual/en/features.commandline.webserver.php
|
||||
EOF
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
if (null === $documentRoot = $input->getOption('docroot')) {
|
||||
$documentRoot = $this->getContainer()->getParameter('kernel.root_dir').'/../web';
|
||||
}
|
||||
|
||||
if (!is_dir($documentRoot)) {
|
||||
$io->error(sprintf('The document root directory "%s" does not exist.', $documentRoot));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$env = $this->getContainer()->getParameter('kernel.environment');
|
||||
if ('prod' === $env) {
|
||||
$io->error('Running this server in production environment is NOT recommended!');
|
||||
}
|
||||
|
||||
$callback = null;
|
||||
$disableOutput = false;
|
||||
if ($output->isQuiet()) {
|
||||
$disableOutput = true;
|
||||
} else {
|
||||
$callback = function ($type, $buffer) use ($output) {
|
||||
if (Process::ERR === $type && $output instanceof ConsoleOutputInterface) {
|
||||
$output = $output->getErrorOutput();
|
||||
}
|
||||
$output->write($buffer, false, OutputInterface::OUTPUT_RAW);
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
$server = new WebServer();
|
||||
$config = new WebServerConfig($documentRoot, $env, $input->getArgument('addressport'), $input->getOption('router'));
|
||||
|
||||
$io->success(sprintf('Server listening on http://%s', $config->getAddress()));
|
||||
$io->comment('Quit the server with CONTROL-C.');
|
||||
|
||||
$exitCode = $server->run($config, $disableOutput, $callback);
|
||||
} catch (\Exception $e) {
|
||||
$io->error($e->getMessage());
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
return $exitCode;
|
||||
}
|
||||
}
|
@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\WebServerBundle\Command;
|
||||
|
||||
use Symfony\Bundle\WebServerBundle\WebServer;
|
||||
use Symfony\Bundle\WebServerBundle\WebServerConfig;
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
/**
|
||||
* Runs a local web server in a background process.
|
||||
*
|
||||
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
|
||||
*/
|
||||
class ServerStartCommand extends ServerCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('server:start')
|
||||
->setDefinition(array(
|
||||
new InputArgument('addressport', InputArgument::OPTIONAL, 'The address to listen to (can be address:port, address, or port)'),
|
||||
new InputOption('docroot', 'd', InputOption::VALUE_REQUIRED, 'Document root'),
|
||||
new InputOption('router', 'r', InputOption::VALUE_REQUIRED, 'Path to custom router script'),
|
||||
new InputOption('pidfile', null, InputOption::VALUE_REQUIRED, 'PID file'),
|
||||
))
|
||||
->setDescription('Starts a local web server in the background')
|
||||
->setHelp(<<<'EOF'
|
||||
The <info>%command.name%</info> runs a local web server:
|
||||
|
||||
<info>php %command.full_name%</info>
|
||||
|
||||
Change the default address and port by passing them as an argument:
|
||||
|
||||
<info>php %command.full_name% 127.0.0.1:8080</info>
|
||||
|
||||
Use the <info>--docroot</info> option to change the default docroot directory:
|
||||
|
||||
<info>php %command.full_name% --docroot=htdocs/</info>
|
||||
|
||||
Specify your own router script via the <info>--router</info> option:
|
||||
|
||||
<info>php %command.full_name% --router=app/config/router.php</info>
|
||||
|
||||
See also: http://www.php.net/manual/en/features.commandline.webserver.php
|
||||
EOF
|
||||
)
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $cliOutput = $output);
|
||||
|
||||
if (!extension_loaded('pcntl')) {
|
||||
$io->error(array(
|
||||
'This command needs the pcntl extension to run.',
|
||||
'You can either install it or use the "server:run" command instead.',
|
||||
));
|
||||
|
||||
if ($io->ask('Do you want to execute <info>server:run</info> immediately? [yN] ', false)) {
|
||||
return $this->getApplication()->find('server:run')->run($input, $cliOutput);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (null === $documentRoot = $input->getOption('docroot')) {
|
||||
$documentRoot = $this->getContainer()->getParameter('kernel.root_dir').'/../web';
|
||||
}
|
||||
|
||||
if (!is_dir($documentRoot)) {
|
||||
$io->error(sprintf('The document root directory "%s" does not exist.', $documentRoot));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$env = $this->getContainer()->getParameter('kernel.environment');
|
||||
if ('prod' === $env) {
|
||||
$io->error('Running this server in production environment is NOT recommended!');
|
||||
}
|
||||
|
||||
try {
|
||||
$server = new WebServer();
|
||||
if ($server->isRunning($input->getOption('pidfile'))) {
|
||||
$io->error(sprintf('The web server is already running (listening on http://%s).', $server->getAddress($input->getOption('pidfile'))));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
$config = new WebServerConfig($documentRoot, $env, $input->getArgument('addressport'), $input->getOption('router'));
|
||||
|
||||
if (WebServer::STARTED === $server->start($config, $input->getOption('pidfile'))) {
|
||||
$io->success(sprintf('Server listening on http://%s', $config->getAddress()));
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$io->error($e->getMessage());
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\WebServerBundle\Command;
|
||||
|
||||
use Symfony\Bundle\WebServerBundle\WebServer;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
/**
|
||||
* Shows the status of a process that is running PHP's built-in web server in
|
||||
* the background.
|
||||
*
|
||||
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
|
||||
*/
|
||||
class ServerStatusCommand extends ServerCommand
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setName('server:status')
|
||||
->setDefinition(array(
|
||||
new InputOption('pidfile', null, InputOption::VALUE_REQUIRED, 'PID file'),
|
||||
))
|
||||
->setDescription('Outputs the status of the local web server for the given address')
|
||||
;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function execute(InputInterface $input, OutputInterface $output)
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
$server = new WebServer();
|
||||
if ($server->isRunning($input->getOption('pidfile'))) {
|
||||
$io->success(sprintf('Web server still listening on http://%s', $server->getAddress($input->getOption('pidfile'))));
|
||||
} else {
|
||||
$io->warning('No web server is listening.');
|
||||
}
|
||||
}
|
||||
}
|
@ -9,16 +9,16 @@
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle\Command;
|
||||
namespace Symfony\Bundle\WebServerBundle\Command;
|
||||
|
||||
use Symfony\Component\Console\Input\InputArgument;
|
||||
use Symfony\Bundle\WebServerBundle\WebServer;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use Symfony\Component\Console\Input\InputOption;
|
||||
use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
/**
|
||||
* Stops a background process running PHP's built-in web server.
|
||||
* Stops a background process running a local web server.
|
||||
*
|
||||
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
|
||||
*/
|
||||
@ -30,21 +30,19 @@ class ServerStopCommand extends ServerCommand
|
||||
protected function configure()
|
||||
{
|
||||
$this
|
||||
->setDefinition(array(
|
||||
new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1'),
|
||||
new InputOption('port', 'p', InputOption::VALUE_REQUIRED, 'Address port number', '8000'),
|
||||
))
|
||||
->setName('server:stop')
|
||||
->setDescription('Stops PHP\'s built-in web server that was started with the server:start command')
|
||||
->setDefinition(array(
|
||||
new InputOption('pidfile', null, InputOption::VALUE_REQUIRED, 'PID file'),
|
||||
))
|
||||
->setDescription('Stops the local web server that was started with the server:start command')
|
||||
->setHelp(<<<'EOF'
|
||||
The <info>%command.name%</info> stops PHP's built-in web server:
|
||||
The <info>%command.name%</info> stops the local web server:
|
||||
|
||||
<info>php %command.full_name%</info>
|
||||
|
||||
To change the default bind address and the default port use the <info>address</info> argument:
|
||||
|
||||
<info>php %command.full_name% 127.0.0.1:8080</info>
|
||||
|
||||
EOF
|
||||
)
|
||||
;
|
||||
@ -57,20 +55,14 @@ EOF
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$address = $input->getArgument('address');
|
||||
if (false === strpos($address, ':')) {
|
||||
$address = $address.':'.$input->getOption('port');
|
||||
}
|
||||
|
||||
$lockFile = $this->getLockFile($address);
|
||||
|
||||
if (!file_exists($lockFile)) {
|
||||
$io->error(sprintf('No web server is listening on http://%s', $address));
|
||||
try {
|
||||
$server = new WebServer();
|
||||
$server->stop($input->getOption('pidfile'));
|
||||
$io->success('Stopped the web server.');
|
||||
} catch (\Exception $e) {
|
||||
$io->error($e->getMessage());
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
unlink($lockFile);
|
||||
$io->success(sprintf('Stopped the web server listening on http://%s', $address));
|
||||
}
|
||||
}
|
19
src/Symfony/Bundle/WebServerBundle/LICENSE
Normal file
19
src/Symfony/Bundle/WebServerBundle/LICENSE
Normal file
@ -0,0 +1,19 @@
|
||||
Copyright (c) 2004-2017 Fabien Potencier
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is furnished
|
||||
to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
10
src/Symfony/Bundle/WebServerBundle/README.md
Normal file
10
src/Symfony/Bundle/WebServerBundle/README.md
Normal file
@ -0,0 +1,10 @@
|
||||
WebServerBundle
|
||||
===============
|
||||
|
||||
Resources
|
||||
---------
|
||||
|
||||
* [Contributing](https://symfony.com/doc/current/contributing/index.html)
|
||||
* [Report issues](https://github.com/symfony/symfony/issues) and
|
||||
[send Pull Requests](https://github.com/symfony/symfony/pulls)
|
||||
in the [main Symfony repository](https://github.com/symfony/symfony)
|
@ -30,13 +30,15 @@ if (is_file($_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR.$_SERVER['SCRIPT_NAME'
|
||||
return false;
|
||||
}
|
||||
|
||||
$script = getenv('APP_FRONT_CONTROLLER') ?: 'index.php';
|
||||
|
||||
$_SERVER = array_merge($_SERVER, $_ENV);
|
||||
$_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR.'app_dev.php';
|
||||
$_SERVER['SCRIPT_FILENAME'] = $_SERVER['DOCUMENT_ROOT'].DIRECTORY_SEPARATOR.$script;
|
||||
|
||||
// Since we are rewriting to app_dev.php, adjust SCRIPT_NAME and PHP_SELF accordingly
|
||||
$_SERVER['SCRIPT_NAME'] = DIRECTORY_SEPARATOR.'app_dev.php';
|
||||
$_SERVER['PHP_SELF'] = DIRECTORY_SEPARATOR.'app_dev.php';
|
||||
$_SERVER['SCRIPT_NAME'] = DIRECTORY_SEPARATOR.$script;
|
||||
$_SERVER['PHP_SELF'] = DIRECTORY_SEPARATOR.$script;
|
||||
|
||||
require 'app_dev.php';
|
||||
require $script;
|
||||
|
||||
error_log(sprintf('%s:%d [%d]: %s', $_SERVER['REMOTE_ADDR'], $_SERVER['REMOTE_PORT'], http_response_code(), $_SERVER['REQUEST_URI']), 4);
|
165
src/Symfony/Bundle/WebServerBundle/WebServer.php
Normal file
165
src/Symfony/Bundle/WebServerBundle/WebServer.php
Normal file
@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\WebServerBundle;
|
||||
|
||||
use Symfony\Component\Process\PhpExecutableFinder;
|
||||
use Symfony\Component\Process\Process;
|
||||
use Symfony\Component\Process\ProcessBuilder;
|
||||
use Symfony\Component\Process\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* Manages a local HTTP web server.
|
||||
*
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class WebServer
|
||||
{
|
||||
const STARTED = 0;
|
||||
const STOPPED = 1;
|
||||
|
||||
public function run(WebServerConfig $config, $disableOutput = true, callable $callback = null)
|
||||
{
|
||||
if ($this->isRunning()) {
|
||||
throw new \RuntimeException(sprintf('A process is already listening on http://%s.', $config->getAddress()));
|
||||
}
|
||||
|
||||
$process = $this->createServerProcess($config);
|
||||
if ($disableOutput) {
|
||||
$process->disableOutput();
|
||||
$callback = null;
|
||||
} else {
|
||||
try {
|
||||
$process->setTty(true);
|
||||
$callback = null;
|
||||
} catch (RuntimeException $e) {
|
||||
}
|
||||
}
|
||||
|
||||
$process->run($callback);
|
||||
|
||||
if (!$process->isSuccessful()) {
|
||||
$error = 'Server terminated unexpectedly.';
|
||||
if ($process->isOutputDisabled()) {
|
||||
$error .= ' Run the command again with -v option for more details.';
|
||||
}
|
||||
|
||||
throw new \RuntimeException($error);
|
||||
}
|
||||
}
|
||||
|
||||
public function start(WebServerConfig $config, $pidFile = null)
|
||||
{
|
||||
if ($this->isRunning()) {
|
||||
throw new \RuntimeException(sprintf('A process is already listening on http://%s.', $config->getAddress()));
|
||||
}
|
||||
|
||||
$pid = pcntl_fork();
|
||||
|
||||
if ($pid < 0) {
|
||||
throw new \RuntimeException('Unable to start the server process.');
|
||||
}
|
||||
|
||||
if ($pid > 0) {
|
||||
return self::STARTED;
|
||||
}
|
||||
|
||||
if (posix_setsid() < 0) {
|
||||
throw new \RuntimeException('Unable to set the child process as session leader.');
|
||||
}
|
||||
|
||||
$process = $this->createServerProcess($config);
|
||||
$process->disableOutput();
|
||||
$process->start();
|
||||
|
||||
if (!$process->isRunning()) {
|
||||
throw new \RuntimeException('Unable to start the server process.');
|
||||
}
|
||||
|
||||
$pidFile = $pidFile ?: $this->getDefaultPidFile();
|
||||
file_put_contents($pidFile, $config->getAddress());
|
||||
|
||||
// stop the web server when the lock file is removed
|
||||
while ($process->isRunning()) {
|
||||
if (!file_exists($pidFile)) {
|
||||
$process->stop();
|
||||
}
|
||||
|
||||
sleep(1);
|
||||
}
|
||||
|
||||
return self::STOPPED;
|
||||
}
|
||||
|
||||
public function stop($pidFile = null)
|
||||
{
|
||||
$pidFile = $pidFile ?: $this->getDefaultPidFile();
|
||||
if (!file_exists($pidFile)) {
|
||||
throw new \RuntimeException('No web server is listening.');
|
||||
}
|
||||
|
||||
unlink($pidFile);
|
||||
}
|
||||
|
||||
public function getAddress($pidFile = null)
|
||||
{
|
||||
$pidFile = $pidFile ?: $this->getDefaultPidFile();
|
||||
if (!file_exists($pidFile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return file_get_contents($pidFile);
|
||||
}
|
||||
|
||||
public function isRunning($pidFile = null)
|
||||
{
|
||||
$pidFile = $pidFile ?: $this->getDefaultPidFile();
|
||||
if (!file_exists($pidFile)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$address = file_get_contents($pidFile);
|
||||
$pos = strrpos($address, ':');
|
||||
$hostname = substr($address, 0, $pos);
|
||||
$port = substr($address, $pos + 1);
|
||||
if (false !== $fp = @fsockopen($hostname, $port, $errno, $errstr, 1)) {
|
||||
fclose($fp);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
unlink($pidFile);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Process The process
|
||||
*/
|
||||
private function createServerProcess(WebServerConfig $config)
|
||||
{
|
||||
$finder = new PhpExecutableFinder();
|
||||
if (false === $binary = $finder->find()) {
|
||||
throw new \RuntimeException('Unable to find the PHP binary.');
|
||||
}
|
||||
|
||||
$builder = new ProcessBuilder(array($binary, '-S', $config->getAddress(), $config->getRouter()));
|
||||
$builder->setWorkingDirectory($config->getDocumentRoot());
|
||||
$builder->setTimeout(null);
|
||||
|
||||
return $builder->getProcess();
|
||||
}
|
||||
|
||||
private function getDefaultPidFile()
|
||||
{
|
||||
return getcwd().'/.web-server-pid';
|
||||
}
|
||||
}
|
18
src/Symfony/Bundle/WebServerBundle/WebServerBundle.php
Normal file
18
src/Symfony/Bundle/WebServerBundle/WebServerBundle.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\WebServerBundle;
|
||||
|
||||
use Symfony\Component\HttpKernel\Bundle\Bundle;
|
||||
|
||||
class WebServerBundle extends Bundle
|
||||
{
|
||||
}
|
117
src/Symfony/Bundle/WebServerBundle/WebServerConfig.php
Normal file
117
src/Symfony/Bundle/WebServerBundle/WebServerConfig.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\WebServerBundle;
|
||||
|
||||
/**
|
||||
* @author Fabien Potencier <fabien@symfony.com>
|
||||
*/
|
||||
class WebServerConfig
|
||||
{
|
||||
private $hostname;
|
||||
private $port;
|
||||
private $documentRoot;
|
||||
private $env;
|
||||
private $router;
|
||||
|
||||
public function __construct($documentRoot, $env, $address = null, $router = null)
|
||||
{
|
||||
if (!is_dir($documentRoot)) {
|
||||
throw new \InvalidArgumentException(sprintf('The document root directory "%s" does not exist.', $documentRoot));
|
||||
}
|
||||
|
||||
if (null === $file = $this->guessFrontController($documentRoot, $env)) {
|
||||
throw new \InvalidArgumentException(sprintf('Unable to guess the front controller under "%s".', $documentRoot));
|
||||
}
|
||||
|
||||
putenv('APP_FRONT_CONTROLLER='.$file);
|
||||
|
||||
$this->documentRoot = $documentRoot;
|
||||
$this->env = $env;
|
||||
$this->router = $router ?: __DIR__.'/Resources/router.php';
|
||||
|
||||
if (null === $address) {
|
||||
$this->hostname = '127.0.0.1';
|
||||
$this->port = $this->findBestPort();
|
||||
} elseif (false !== $pos = strrpos($address, ':')) {
|
||||
$this->hostname = substr($address, 0, $pos);
|
||||
$this->port = substr($address, $pos + 1);
|
||||
} elseif (ctype_digit($address)) {
|
||||
$this->hostname = '127.0.0.1';
|
||||
$this->port = $address;
|
||||
} else {
|
||||
$this->hostname = $address;
|
||||
$this->port = $this->findBestPort();
|
||||
}
|
||||
|
||||
if (!ctype_digit($this->port)) {
|
||||
throw new \InvalidArgumentException(sprintf('Port "%s" is not valid.', $this->port));
|
||||
}
|
||||
}
|
||||
|
||||
public function getDocumentRoot()
|
||||
{
|
||||
return $this->documentRoot;
|
||||
}
|
||||
|
||||
public function getEnv()
|
||||
{
|
||||
return $this->env;
|
||||
}
|
||||
|
||||
public function getRouter()
|
||||
{
|
||||
return $this->router;
|
||||
}
|
||||
|
||||
public function getHostname()
|
||||
{
|
||||
return $this->hostname;
|
||||
}
|
||||
|
||||
public function getPort()
|
||||
{
|
||||
return $this->port;
|
||||
}
|
||||
|
||||
public function getAddress()
|
||||
{
|
||||
return $this->hostname.':'.$this->port;
|
||||
}
|
||||
|
||||
private function guessFrontController($documentRoot, $env)
|
||||
{
|
||||
foreach (array('app', 'index') as $prefix) {
|
||||
$file = sprintf('%s_%s.php', $prefix, $env);
|
||||
if (file_exists($documentRoot.'/'.$file)) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
$file = sprintf('%s.php', $prefix);
|
||||
if (file_exists($documentRoot.'/'.$file)) {
|
||||
return $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function findBestPort()
|
||||
{
|
||||
$port = 8000;
|
||||
while (false !== $fp = @fsockopen('127.0.0.1', $port, $errno, $errstr, 1)) {
|
||||
fclose($fp);
|
||||
if ($port++ >= 8100) {
|
||||
throw new \RuntimeException('Unable to find a port available to run the web server.');
|
||||
}
|
||||
}
|
||||
|
||||
return $port;
|
||||
}
|
||||
}
|
35
src/Symfony/Bundle/WebServerBundle/composer.json
Normal file
35
src/Symfony/Bundle/WebServerBundle/composer.json
Normal file
@ -0,0 +1,35 @@
|
||||
{
|
||||
"name": "symfony/web-server-bundle",
|
||||
"type": "web-server-bundle",
|
||||
"description": "Symfony WebServerBundle",
|
||||
"keywords": [],
|
||||
"homepage": "https://symfony.com",
|
||||
"license": "MIT",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Fabien Potencier",
|
||||
"email": "fabien@symfony.com"
|
||||
},
|
||||
{
|
||||
"name": "Symfony Community",
|
||||
"homepage": "https://symfony.com/contributors"
|
||||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=5.5.9",
|
||||
"symfony/console": "~2.8.8|~3.0.8|~3.1.2|~3.2",
|
||||
"symfony/process": "~2.8|~3.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": { "Symfony\\Bundle\\WebServerBundle\\": "" },
|
||||
"exclude-from-classmap": [
|
||||
"/Tests/"
|
||||
]
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "3.3-dev"
|
||||
}
|
||||
}
|
||||
}
|
29
src/Symfony/Bundle/WebServerBundle/phpunit.xml.dist
Normal file
29
src/Symfony/Bundle/WebServerBundle/phpunit.xml.dist
Normal file
@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
|
||||
backupGlobals="false"
|
||||
colors="true"
|
||||
bootstrap="vendor/autoload.php"
|
||||
>
|
||||
<php>
|
||||
<ini name="error_reporting" value="-1" />
|
||||
</php>
|
||||
|
||||
<testsuites>
|
||||
<testsuite name="Symfony WebServerBundle Test Suite">
|
||||
<directory>./Tests/</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
|
||||
<filter>
|
||||
<whitelist>
|
||||
<directory>./</directory>
|
||||
<exclude>
|
||||
<directory>./Resources</directory>
|
||||
<directory>./Tests</directory>
|
||||
<directory>./vendor</directory>
|
||||
</exclude>
|
||||
</whitelist>
|
||||
</filter>
|
||||
</phpunit>
|
Reference in New Issue
Block a user