8000 [FrameworkBundle] Additional helper commands to control PHP's built-in web server by xabbuh · Pull Request #11311 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[FrameworkBundle] Additional helper commands to control PHP's built-in web server #11311

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 22, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Command/ServerCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?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 (version_compare(phpversion(), '5.4.0', '<') || defined('HHVM_VERSION')) {
return false;
}

if (!extension_loaded('pcntl')) {
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';
}
}
155 changes: 155 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Command/ServerStartCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
<?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\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:8000'),
new InputOption('docroot', 'd', InputOption::VALUE_REQUIRED, 'Document root', 'web/'),
new InputOption('router', 'r', InputOption::VALUE_REQUIRED, 'Path to custom router script'),
))
->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>%command.full_name%</info>

To change the default bind address and the default port use the <info>address</info> argument:

<info>%command.full_name% 127.0.0.1:8080</info>

To change the default document root directory use the <info>--docroot</info> option:

<info>%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>%command.full_name% --router=app/config/router.php</info>

Specifying a router script is required when the used environment is not "dev" or
"prod".

See also: http://www.php.net/manual/en/features.commandline.webserver.php

EOF
)
;
}

/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$env = $this->getContainer()->getParameter('kernel.environment');

if ('prod' === $env) {
$output->writeln('<error>Running PHP built-in server in production environment is NOT recommended!</error>');
}

$pid = pcntl_fork();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this command must be disabled when pcntl is not available

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed. I added a check. I also added this check to the server:stop and server:status commands since they don't make any sense when the server:start command is not available.


if ($pid < 0) {
$output->writeln('<error>Unable to start the server process</error>');

return 1;
}

if ($pid > 0) {
$output->writeln('<info>Server started successfully</info>');

return;
}

if (posix_setsid() < 0) {
$output->writeln('<error>Unable to set the child process as session leader</error>');

return 1;
}

$process = $this->createServerProcess(
$input->getArgument('address'),
$input->getOption('docroot'),
$input->getOption('router'),
$env,
null
);
$process->disableOutput();
$process->start();
$lockFile = $this->getLockFile($input->getArgument('address'));
touch($lockFile);

if (!$process->isRunning()) {
$output->writeln('<error>Unable to start the server process</error>');
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);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the process is killed, let's remove the file

if (file_exists($lockFile)) {
    unlink($lockfile);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, it's one line above

< 8000 /details-toggle>
}

/**
* Creates a process to start PHP's built-in web server.
*
* @param string $address IP address and port to listen to
* @param string $documentRoot The application's document root
* @param string $router The router filename
* @param string $env The application environment
* @param int $timeout Process timeout
*
* @return Process The process
*/
private function createServerProcess($address, $documentRoot, $router, $env, $timeout = null)
{
$router = $router ?: $this
->getContainer()
->get('kernel')
->locateResource(sprintf('@FrameworkBundle/Resources/config/router_%s.php', $env))
;
$script = implode(' ', array_map(array('Symfony\Component\Process\ProcessUtils', 'escapeArgument'), array(
PHP_BINARY,
'-S',
$address,
$router,
)));

return new Process('exec '.$script, $documentRoot, null, null, $timeout);
}
}
71 changes: 71 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Command/ServerStatusCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?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\Output\OutputInterface;

/**
* 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'),
))
->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)
{
$address = $input->getArgument('address');

// remove an orphaned lock file
if (file_exists($this->getLockFile($address)) && !$this->isServerRunning($address)) {
unlink($this->getLockFile($address));
}

if (file_exists($this->getLockFile($address))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The server process could have crashed. You could check if the server sends HTTP responses.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. That could be done relatively easy.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lock file is now removed if the server process terminated before.

$output->writeln(sprintf('<info>Web server still listening on %s</info>', $address));
} else {
$output->writeln(sprintf('<error>No web server is listening on %s</error>', $address));
}
}

private function isServerRunning($address)
{
list($hostname, $port) = explode(':', $address);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this would trigger an error in case the address is not in the right format (for example randomstring would trigger an Undefined index notice

To avoid that, you can use:

explode(':', $address) + array(8000);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or better, ensure the passed argument matches a regular expression

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, there wouldn't be any server running with this address. So, there is no lock file and isServerRunning() would never be called (see line 48).


if (false !== $fp = @fsockopen($hostname, $port, $errno, $errstr, 1)) {
fclose($fp);

return true;
}

return false;
}
}
67 changes: 67 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Command/ServerStopCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?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\Output\OutputInterface;

/**
* Stops a background process running PHP's built-in web server.
*
* @author Christian Flothmann <christian.flothmann@xabbuh.de>
*/
class ServerStopCommand extends ServerCommand
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this
->setDefinition(array(
new InputArgument('address', InputArgument::OPTIONAL, 'Address:port', '127.0.0.1:8000'),
))
->setName('server:stop')
->setDescription('Stops PHP\'s built-in web server that was started with the server:start command')
->setHelp(<<<EOF
The <info>%command.name%</info> stops PHP's built-in web server:

<info>%command.full_name%</info>

To change the default bind address and the default port use the <info>address</info> argument:

<info>%command.full_name% 127.0.0.1:8080</info>

EOF
)
;
}

/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$address = $input->getArgument('address');
$lockFile = $this->getLockFile($address);

if (!file_exists($lockFile)) {
$output->writeln(sprintf('<error>No web server is listening on %s</error>', $address));

return 1;
}

unlink($lockFile);
$output->writeln(sprintf('<info>Stopped the web server listening on %s</info>', $address));
}
}
0