Skip to content

Commit

Permalink
Feature #1190984. Owen Barton, znerol. Lightweight built in http serv…
Browse files Browse the repository at this point in the history
…er for development, using the httpserver library from adunar and the Envaya project.
  • Loading branch information
grugnog committed Jun 26, 2011
1 parent 3d0e46e commit 5994241
Show file tree
Hide file tree
Showing 8 changed files with 350 additions and 77 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
lib
26 changes: 4 additions & 22 deletions commands/pm/package_handler/wget.inc
Original file line number Diff line number Diff line change
Expand Up @@ -46,57 +46,39 @@ function package_handler_download_project(&$request, $release) {
}
}

// Chdir to the download location.
$olddir = getcwd();
drush_op('chdir', $request['base_project_path']);

// Dev snapshots can be cached for 4 hours and releases for 1 year.
$max_age = strpos($release['download_link'], '-dev') !== FALSE ? 3600*4 : 86400*365;
$path = drush_download_file($release['download_link'], $max_age);
$filename = basename($release['download_link']);
$path = drush_download_file($release['download_link'], $request['base_project_path'] . $filename, $max_age);
if ($path || drush_get_context('DRUSH_SIMULATE')) {
drush_log("Downloading " . $filename . " was successful.");
}
else {
drush_op('chdir', $olddir);
return drush_set_error('DRUSH_PM_DOWNLOAD_FAILED', 'Unable to download ' . $filename . ' to ' . $request['base_project_path'] . ' from '. $release['download_link']);
}

// Check Md5 hash.
if (drush_op('md5_file', $path) != $release['mdhash'] && !drush_get_context('DRUSH_SIMULATE')) {
drush_set_error('DRUSH_PM_FILE_CORRUPT', "File $filename is corrupt (wrong md5 checksum).");
drush_op('chdir', $olddir);
return FALSE;
}
else {
drush_log("Md5 checksum of $filename verified.");
}

$tarpath = basename($path, '.tar.gz');
$tarpath = basename($tarpath, '.tgz');
$tarpath .= '.tar';
// Decompress and untar in two steps as tar -xzf does not work on windows.
drush_shell_exec("gzip --decompress --stdout %s > %s", $path, $tarpath);
drush_shell_exec("tar -xf %s", $tarpath);
// Extract the tarball.
$file_list = drush_tarball_extract($path, $request['base_project_path']);

// Move untarred directory to project_dir, if distinct.
if (($request['project_type'] == 'core') || (($request['project_type'] == 'profile') && (drush_get_option('variant', 'core') == 'core'))) {
// Obtain the dodgy project_dir for drupal core.
// We use a separate tar -tf instead of -xvf above because
// the output is not the same in Mac.
drush_shell_exec("tar -tf %s", $tarpath);
$output = drush_shell_exec_output();
$project_dir = drush_trim_path($output[0]);
$project_dir = drush_trim_path($file_list[0]);
if ($request['project_dir'] != $project_dir) {
$path = $request['base_project_path'];
drush_move_dir($path . '/'. $project_dir, $path . '/' . $request['project_dir']);
}
}

// Cleanup. Remove the tar file and set previous working directory.
drush_op('unlink', $tarpath);
drush_op('chdir', $olddir);

return TRUE;
}

Expand Down
9 changes: 5 additions & 4 deletions commands/pm/pm.drush.inc
Original file line number Diff line number Diff line change
Expand Up @@ -1325,9 +1325,9 @@ function _drush_pm_releasenotes($requests, $print_status = TRUE, $tmpfile = NULL
@$dom = DOMDocument::loadHTML($data->data);
}
else {
$path = _drush_download_file($release_page_url);
@$dom = DOMDocument::loadHTMLFile($path);
@unlink($path);
$filename = drush_download_file($release_page_url);
@$dom = DOMDocument::loadHTMLFile($filename);
@unlink($filename);
if ($dom === FALSE) {
drush_log(dt("Error while requesting the release notes page for !project project.", array('!project' => $key)), 'error');
continue;
Expand Down Expand Up @@ -1712,7 +1712,8 @@ function _drush_pm_get_release_history_xml($request) {
// bootstrapped.
$url = drush_get_option('source', 'http://updates.drupal.org/release-history') . '/' . $request['name'] . '/' . $request['drupal_version'];
drush_log('Downloading release history from ' . $url);
if ($path = drush_download_file($url, drush_get_option('cache-duration-releasexml', 24*3600))) {
// Some hosts have allow_url_fopen disabled.
if ($path = drush_download_file($url, NULL, drush_get_option('cache-duration-releasexml', 24*3600))) {
$xml = simplexml_load_file($path);
}
if (!$xml) {
Expand Down
60 changes: 60 additions & 0 deletions commands/runserver/runserver-drupal.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

/**
* @file
* Classes extending the httpserver library that provide Drupal specific
* behaviours.
*/

/**
* Extends the HTTPServer class, handling request routing and environment.
*/
class DrupalServer extends HTTPServer {
public $http_host;

/**
* This is the equivalent of .htaccess, passing requests to files if they
* exist, and all other requests to index.php. We also set a number
* of CGI environment variables here.
*/
function route_request($request) {
$cgi_env = array();

// Handle static files and php scripts accessed directly
$uri = $request->uri;
$doc_root = DRUPAL_ROOT;
$path = $doc_root . $uri;
if (is_file(realpath($path))) {
if (preg_match('#\.php$#', $uri)) {
// SCRIPT_NAME is equal to uri if it does exist on disk
$cgi_env['SCRIPT_NAME'] = $uri;
return $this->get_php_response($request, $path, $cgi_env);
}
return $this->get_static_response($request, $path);
}

// We pass in the effective base_url to our auto_prepend_script via the cgi
// environment. This allows Drupal to generate working URLs to this http
// server, whilst finding the correct multisite from the HTTP_HOST header.
$cgi_env['RUNSERVER_BASE_URL'] = 'http://localhost:' . $this->port;

// We pass in an array of $conf overrides using the same approach.
// By default we set drupal_http_request_fails to FALSE, as the httpserver
// is unable to process simultanious requests on some systems.
// This is available as an option for developers to pass in their own
// favorite $conf overrides (e.g. disabling css aggregation).
$conf_inject = drush_get_option('conf-inject', array('drupal_http_request_fails' => FALSE));
$cgi_env['RUNSERVER_CONF'] = urlencode(serialize($conf_inject));

// Rewrite clean-urls
$cgi_env['QUERY_STRING'] = 'q=' . ltrim($uri, '/');
if ($request->query_string != "") {
$cgi_env['QUERY_STRING'] .= '&' . $request->query_string;
}

$cgi_env['SCRIPT_NAME'] = '/index.php';
$cgi_env['HTTP_HOST'] = $cgi_env['SERVER_NAME'] = $this->http_host;

return $this->get_php_response($request, $doc_root . '/index.php', $cgi_env);
}
}
29 changes: 29 additions & 0 deletions commands/runserver/runserver-prepend.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<?php
/**
* @file
* This file is included using the php "auto_prepend_file" option whenever
* Drupal is used with runserver.
* We use this to inject some specific changes so Drupal works correctly in
* this environment.
*/

// We set the base_url so that Drupal generates correct URLs for runserver
// (e.g. http://localhost:8888/...), but can still select and serve a specific
// site in a multisite configuration (e.g. http://mysite.com/...).
$base_url = $_SERVER['RUNSERVER_BASE_URL'];

/**
* Configuration added here will apply to all sites.
* This can be a useful place to apply generic development settings.
* We hijack system_boot (which core system module does not implement) as
* a convenient place to inject values into the $conf array.
*/
if (!function_exists('system_boot')) {
// Check function_exists as a safety net in case it is added in future.
function system_boot() {
global $conf;
$conf_inject = unserialize(urldecode($_SERVER['RUNSERVER_CONF']));
// Merge in the injected conf, overriding existing items.
$conf = array_merge($conf, $conf_inject);
}
}
120 changes: 120 additions & 0 deletions commands/runserver/runserver.drush.inc
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php

/**
* @file
* Built in http server commands.
*
* This uses an excellent php http server library developed by Jesse Young
* with support from the Envaya project.
* See https://github.com/youngj/httpserver/ and http://envaya.org/.
*/

/**
* Supported version of httpserver. This is displayed in the manual install help.
*/
define('DRUSH_HTTPSERVER_VERSION', '354b9142bf0cfd73063e28604d11288304531cd6');

/**
* Directory name for httpserver. This is displayed in the manual install help.
*/
define('DRUSH_HTTPSERVER_DIR_BASE', 'youngj-httpserver-');

/**
* Base URL for automatic download of supported version of httpserver.
*/
define('DRUSH_HTTPSERVER_BASE_URL', 'https://github.com/youngj/httpserver/tarball/');

/**
* Implementation of hook_drush_help().
*/
function runserver_drush_help($section) {
switch ($section) {
case 'meta:runserver:title':
return dt("Runserver commands");
case 'drush:runserver':
return dt("Runs a lightweight built in http server for development.
- Don't use this for production, it is neither scalable nor secure for this use.
- If you run multiple servers simultaniously, you will need to assign each a unique port.
- Use Ctrl-C or equivalent to stop the server when complete.");
}
}

/**
* Implementation of hook_drush_command().
*/
function runserver_drush_command() {
$items = array();

$items['runserver'] = array(
'description' => 'Runs a lightweight built in http server for development.',
'bootstrap' => DRUSH_BOOTSTRAP_DRUPAL_SITE,
'arguments' => array(
'addr:port' => 'Host IP address and port number to bind to (default 127.0.0.1:8888). The IP is optional, in which case just pass in the numeric port.',
),
'options' => array(
'php-cgi' => 'Name of the php-cgi binary. If it is not on your current $PATH you should include the full path. You can include command line parameters to pass into php-cgi.',
'conf-inject' => 'Key-value array of variables to override in the $conf array for the running site. By default disables drupal_http_request_fails to avoid errors on Windows (which supports only one connection at a time). Note that as this is a key-value array, it can only be specified in a drushrc or alias file, and not on the command line.',
),
'aliases' => array('rs'),
);
return $items;
}

/**
* Validate callback for runserver command.
*/
function drush_core_runserver_validate() {
if (version_compare(PHP_VERSION, '5.3.0') < 0) {
return drush_set_error('RUNSERVER_PHP_VERSION', dt('The runserver command requires php 5.3, which could not be found.'));
}
if (!drush_shell_exec('which ' . drush_get_option('php-cgi', 'php-cgi'))) {
return drush_set_error('RUNSERVER_PHP_CGI', dt('The runserver command requires the php-cgi binary, which could not be found.'));
}
}

/**
* Callback for runserver command.
*/
function drush_core_runserver($addrport = '8888') {
// Fetch httpserver to our /lib directory, if needed.
$lib = drush_get_option('lib', DRUSH_BASE_PATH . '/lib');
$httpserverfile = $lib . '/' . DRUSH_HTTPSERVER_DIR_BASE . substr(DRUSH_HTTPSERVER_VERSION, 0, 7) . '/httpserver.php';
if (!drush_file_not_empty($httpserverfile)) {
// Download and extract httpserver, and confirm success.
drush_lib_fetch(DRUSH_HTTPSERVER_BASE_URL . DRUSH_HTTPSERVER_VERSION);
if (!drush_file_not_empty($httpserverfile)) {
// Something went wrong - the library is still not present.
return drush_set_error('RUNSERVER_HTTPSERVER_LIB_NOT_FOUND', dt("The runserver command needs a copy of the httpserver library in order to function, and the attempt to download this file automatically failed. To continue you will need to download the package from !url, extract it into the !lib directory, such that httpserver.php exists at !httpserverfile.", array('!version' => DRUSH_HTTPSERVER_VERSION, '!url' => DRUSH_HTTPSERVER_BASE_URL . DRUSH_HTTPSERVER_VERSION, '!httpserverfile' => $httpserverfile, '!lib' => $lib)));
}
}

// Include the library and our class that extends it.
require_once $httpserverfile;
require_once 'runserver-drupal.inc';

// Determine configuration.
if (is_numeric($addrport)) {
$addr = '127.0.0.1';
$port = $addrport;
}
else {
$addrport = explode(':', $addrport);
if (count($addrport) !== 2 && is_numeric($addrport[1])) {
return drush_set_error('RUNSERVER_INVALID_ADDRPORT', dt('Invalid address/port argument - should be either numeric (port only), or in the "host:port" format..'));
}
$addr = $addrport[0];
$port = $addrport[1];
}

// We delete any registered files here, since they are not caught by Ctrl-C.
_drush_delete_registered_files();

// Create a new server instance and start it running.
$server = new DrupalServer(array(
'addr' => $addr,
'port' => $port,
'serverid' => 'Drush runserver',
'php_cgi' => drush_get_option('php-cgi', 'php-cgi') . ' --define auto_prepend_file="' . DRUSH_BASE_PATH . '/commands/runserver/runserver-prepend.php"',
));
$server->run_forever();
}
Loading

0 comments on commit 5994241

Please sign in to comment.