Merge lp:~fourkitchens/pressflow/6-evented into lp:pressflow

Proposed by David Strauss
Status: Work in progress
Proposed branch: lp:~fourkitchens/pressflow/6-evented
Merge into: lp:pressflow
Diff against target: 485 lines (+340/-18)
6 files modified
DaemonRequest.php (+58/-0)
DaemonResponse.php (+139/-0)
evented-daemon.php (+120/-0)
includes/bootstrap.inc (+12/-10)
includes/common.inc (+10/-8)
themes/garland/page.tpl.php (+1/-0)
To merge this branch: bzr merge lp:~fourkitchens/pressflow/6-evented
Reviewer Review Type Date Requested Status
Pressflow Administrators Pending
Review via email: mp+29770@code.launchpad.net
To post a comment you must log in.
lp:~fourkitchens/pressflow/6-evented updated
88. By Aaron Forsander

Added very basic support for static files.

89. By Aaron Forsander

Drupal's .htaccess doesn't set q with the leading / and it trims trailing slashes anyways.

90. By Aaron Forsander

Created DaemonRequest and DaemonResponse classes to make working with requests and responses a little easier. Trying to figure out the appropriate session setup/teardown per request.

91. By Aaron Forsander

Cleaning up log output.

92. By David Strauss

Merge from 6.x trunk.

Unmerged revisions

92. By David Strauss

Merge from 6.x trunk.

91. By Aaron Forsander

Cleaning up log output.

90. By Aaron Forsander

Created DaemonRequest and DaemonResponse classes to make working with requests and responses a little easier. Trying to figure out the appropriate session setup/teardown per request.

89. By Aaron Forsander

Drupal's .htaccess doesn't set q with the leading / and it trims trailing slashes anyways.

88. By Aaron Forsander

Added very basic support for static files.

87. By David Strauss

Make the port number for the daemon a shell argument.

86. By David Strauss

Initial event-driven system.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'DaemonRequest.php'
2--- DaemonRequest.php 1970-01-01 00:00:00 +0000
3+++ DaemonRequest.php 2010-08-22 07:23:38 +0000
4@@ -0,0 +1,58 @@
5+<?php
6+
7+class DaemonRequest {
8+ public $request;
9+ public $method = '';
10+ public $uri = '';
11+ public $query_string = '';
12+ public $path = '';
13+ public $headers = array();
14+ public $body = '';
15+
16+ public function __construct($evhttp_request) {
17+ $this->request = $evhttp_request;
18+ $this->method = evhttp_request_method($this->request);
19+ $this->uri = evhttp_request_uri($this->request);
20+ $this->headers = evhttp_request_headers($this->request);
21+ $this->body = evhttp_request_body($this->request);
22+ $this->query_string = parse_url($this->uri, PHP_URL_QUERY);
23+ $this->path = parse_url($this->uri, PHP_URL_PATH);
24+ }
25+
26+ public function init() {
27+ // Reset request-specific globals
28+ foreach (array('_COOKIE', '_POST', '_GET', '_FILES', '_SESSION') as $global) {
29+ $GLOBALS[$global] = array();
30+ }
31+
32+ // Initialize SERVER data
33+ $_SERVER["REQUEST_METHOD"] = $this->method;
34+ $_SERVER["REQUEST_TIME"] = time();
35+ $_SERVER["argv"] = $_SERVER["REQUEST_URI"] = $this->uri;
36+
37+ // Initialize HTTP data
38+ foreach ($this->headers as $name => $value) {
39+ $_SERVER["HTTP_" . str_replace("-", "_", strtoupper($name))] = $value;
40+ }
41+
42+ // Initialize POST data
43+ if ($this->method === "POST") {
44+ parse_str($this->body, $_POST);
45+ }
46+
47+ // Initialize GET data
48+ $_SERVER['QUERY_STRING'] = $this->query_string;
49+ parse_str($this->query_string, $_GET);
50+ $_GET['q'] = trim($this->path, '/');
51+
52+ // Initialize COOKIE data
53+ if ($this->headers['Cookie']) {
54+ $cookies = explode(';', $this->headers['Cookie']);
55+ foreach ($cookies as $cookie) {
56+ list($name, $value) = explode('=', $cookie);
57+ $_COOKIE[$name] = $value;
58+ }
59+ }
60+ }
61+}
62+
63
64=== added file 'DaemonResponse.php'
65--- DaemonResponse.php 1970-01-01 00:00:00 +0000
66+++ DaemonResponse.php 2010-08-22 07:23:38 +0000
67@@ -0,0 +1,139 @@
68+<?php
69+class DaemonResponse {
70+ public $request;
71+ public $response;
72+ public $code;
73+ public $body;
74+ public $headers = array();
75+
76+ public static $messages = array(
77+ // Informational 1xx
78+ 100 => 'Continue',
79+ 101 => 'Switching Protocols',
80+
81+ // Success 2xx
82+ 200 => 'OK',
83+ 201 => 'Created',
84+ 202 => 'Accepted',
85+ 203 => 'Non-Authoritative Information',
86+ 204 => 'No Content',
87+ 205 => 'Reset Content',
88+ 206 => 'Partial Content',
89+ 207 => 'Multi-Status',
90+
91+ // Redirection 3xx
92+ 300 => 'Multiple Choices',
93+ 301 => 'Moved Permanently',
94+ 302 => 'Found', // 1.1
95+ 303 => 'See Other',
96+ 304 => 'Not Modified',
97+ 305 => 'Use Proxy',
98+ // 306 is deprecated but reserved
99+ 307 => 'Temporary Redirect',
100+
101+ // Client Error 4xx
102+ 400 => 'Bad Request',
103+ 401 => 'Unauthorized',
104+ 402 => 'Payment Required',
105+ 403 => 'Forbidden',
106+ 404 => 'Not Found',
107+ 405 => 'Method Not Allowed',
108+ 406 => 'Not Acceptable',
109+ 407 => 'Proxy Authentication Required',
110+ 408 => 'Request Timeout',
111+ 409 => 'Conflict',
112+ 410 => 'Gone',
113+ 411 => 'Length Required',
114+ 412 => 'Precondition Failed',
115+ 413 => 'Request Entity Too Large',
116+ 414 => 'Request-URI Too Long',
117+ 415 => 'Unsupported Media Type',
118+ 416 => 'Requested Range Not Satisfiable',
119+ 417 => 'Expectation Failed',
120+ 422 => 'Unprocessable Entity',
121+ 423 => 'Locked',
122+ 424 => 'Failed Dependency',
123+
124+ // Server Error 5xx
125+ 500 => 'Internal Server Error',
126+ 501 => 'Not Implemented',
127+ 502 => 'Bad Gateway',
128+ 503 => 'Service Unavailable',
129+ 504 => 'Gateway Timeout',
130+ 505 => 'HTTP Version Not Supported',
131+ 507 => 'Insufficient Storage',
132+ 509 => 'Bandwidth Limit Exceeded'
133+ );
134+
135+ public function __construct($request) {
136+ $this->request = $request;
137+ }
138+
139+ public function addHeader($type, $content) {
140+ $this->headers[] = array($type, $content);
141+ return $this;
142+ }
143+
144+ public function setBody($body = '') {
145+ $this->body = $body;
146+ return $this;
147+ }
148+
149+ public function getBody() {
150+ return $this->body;
151+ }
152+
153+ public function setCode($code = 500) {
154+ $this->code = $code;
155+ return $this;
156+ }
157+
158+ public function getCode() {
159+ return $this->code;
160+ }
161+
162+ public function setResponse($body = NULL, $code = NULL) {
163+ $body = $body ? $body : $this->getBody();
164+ $code = $code ? $code : $this->getCode();
165+
166+ foreach ($this->headers as $header) {
167+ evhttp_response_add_header($this->request->request, $header[0], $header[1]);
168+ }
169+
170+ $this->response = evhttp_response_set($body, $code, self::$messages[$code]);
171+ return $this;
172+ }
173+
174+ public function getResponse() {
175+ $this->setResponse();
176+ return $this->response;
177+ }
178+
179+ public function setCookie($name, $value = '', $maxage = 0, $path = '', $domain = '', $secure = false, $HTTPOnly = false) {
180+ if ($domain) {
181+ if (strtolower(substr($domain, 0, 4)) == 'www.') {
182+ $domain = substr($domain, 4);
183+ }
184+
185+ if ($domain[0] != '.') {
186+ $domain = '.'.$domain;
187+ }
188+
189+ $port = strpos($domain, ':');
190+ if ($port !== FALSE) {
191+ $domain = substr($domain, 0, $port);
192+ }
193+ }
194+
195+ $header = rawurlencode($name).'='.rawurlencode($value)
196+ .(empty($domain) ? '' : '; Domain='.$domain)
197+ .(empty($maxage) ? '' : '; Max-Age='.$maxage)
198+ .(empty($path) ? '' : '; Path='.$path)
199+ .(!$secure ? '' : '; Secure')
200+ .(!$HTTPOnly ? '' : '; HttpOnly');
201+
202+ evhttp_response_add_header($this->request->request, 'Set-Cookie', $header);
203+ return $this;
204+ }
205+}
206+
207
208=== added file 'evented-daemon.php'
209--- evented-daemon.php 1970-01-01 00:00:00 +0000
210+++ evented-daemon.php 2010-08-22 07:23:38 +0000
211@@ -0,0 +1,120 @@
212+<?php
213+
214+require_once 'DaemonRequest.php';
215+require_once 'DaemonResponse.php';
216+
217+/**
218+ * Perform generic bootstrapping for daemonized requests.
219+ */
220+function daemon_initialize() {
221+ global $conf;
222+ require_once './includes/bootstrap.inc';
223+ require_once './includes/path.inc';
224+ require_once './includes/common.inc';
225+ require_once './includes/module.inc';
226+ drupal_bootstrap(DRUPAL_BOOTSTRAP_DATABASE, TRUE);
227+ $conf = variable_init(isset($conf) ? $conf : array());
228+ require_once variable_get('session_inc', './includes/session.inc');
229+ _drupal_bootstrap_full(TRUE);
230+ daemon_log('Base daemon initialized');
231+}
232+
233+function daemon_log($msg) {
234+ echo date("c") . " [Pressflow] $msg" . PHP_EOL;
235+ return;
236+}
237+
238+/**
239+ * Handle an individual request.
240+ */
241+function daemon_request_callback(DaemonRequest $request) {
242+ $start = microtime(TRUE);
243+ global $user;
244+
245+ // Initialize our response object
246+ $response = new DaemonResponse($request);
247+
248+ // MEGAHACK! Your mother would be ashamed! There is no way around it! This needs
249+ // to be available to the rest of Drupal so it can set headers and whatnot. This
250+ // causes a segfault unless it is cleaned up at the end of _daemon_request_callback.
251+ $GLOBALS['response'] = $response;
252+
253+ // Initialize server
254+ $request->init();
255+
256+ daemon_log('Using path: ' . $_GET['q']);
257+
258+ // Start a new session
259+ drupal_session_started(FALSE);
260+ drupal_session_initialize();
261+ if (!isset($_COOKIE[session_name()])) {
262+ $user = drupal_anonymous_user();
263+ $session_id = session_id(md5(uniqid('', TRUE)));
264+ $response->setCookie(session_name(), $session_id);
265+ }
266+
267+ // Initialize Drupal
268+ bootstrap_invoke_all('boot');
269+ module_list(TRUE, FALSE);
270+ drupal_init_language();
271+ drupal_init_path();
272+ module_invoke_all('init');
273+
274+ // Hack to handle static files
275+ $static_file_types = array('js' => 'application/x-javascript', 'css' => 'text/css', 'png' => 'image/png',
276+ 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'gif' => 'image/gif', 'bmp' => 'image/bmp',
277+ 'ico' => 'image/x-icon');
278+ $ext = pathinfo($_GET['q'], PATHINFO_EXTENSION);
279+ if (isset($static_file_types[$ext]) && file_exists($_GET['q']) && strpos(realpath($_GET['q']), getcwd()) === 0) {
280+ return $response->addHeader('Content-type', $static_file_types[$ext])->setBody(file_get_contents($_GET['q']))->setCode(200);
281+ }
282+ else {
283+ $response->addHeader('Content-type', 'text/html; charset=utf-8');
284+ }
285+
286+ // Execute the appropriate menu handler.
287+ $return = menu_execute_active_handler();
288+
289+ // Menu status constants are integers; page content is a string.
290+ if (is_int($return)) {
291+ switch ($return) {
292+ case MENU_NOT_FOUND:
293+ daemon_log('Response: Not found');
294+ return $response->setBody('Not found.')->setCode(404);
295+ case MENU_ACCESS_DENIED:
296+ daemon_log('Response: Access denied');
297+ return $response->setBody('Access denied.')->setCode(403);
298+ case MENU_SITE_OFFLINE:
299+ daemon_log('Response: Site offline');
300+ return $response->setBody('Service unavailable.')->setCode(503);
301+ }
302+ }
303+ elseif (isset($return)) {
304+ // Print any value (including an empty string) except NULL or undefined:
305+ $res = theme('page', $return);
306+ }
307+
308+ drupal_page_footer();
309+ session_write_close();
310+ return $response->setBody($res)->setCode(200);
311+}
312+
313+function _daemon_request_callback($evhttp_request) {
314+ $response = daemon_request_callback(new DaemonRequest($evhttp_request));
315+ unset($GLOBALS['response']);
316+ return $response->getResponse();
317+}
318+
319+if (!extension_loaded('event')) {
320+ dl('event.' . PHP_SHLIB_SUFFIX);
321+}
322+
323+error_reporting(E_ALL & ~E_NOTICE);
324+ini_set('display_errors', 1);
325+
326+daemon_initialize();
327+event_init();
328+$httpd = evhttp_start('0.0.0.0', $argv[1]);
329+evhttp_set_gencb($httpd, '_daemon_request_callback');
330+event_dispatch();
331+
332
333=== modified file 'includes/bootstrap.inc'
334--- includes/bootstrap.inc 2010-08-11 21:05:18 +0000
335+++ includes/bootstrap.inc 2010-08-22 07:23:38 +0000
336@@ -1400,14 +1400,14 @@
337 * DRUPAL_BOOTSTRAP_PATH: set $_GET['q'] to Drupal path of request.
338 * DRUPAL_BOOTSTRAP_FULL: Drupal is fully loaded, validate and fix input data.
339 */
340-function drupal_bootstrap($phase = NULL) {
341+function drupal_bootstrap($phase = NULL, $daemonized = FALSE) {
342 static $phases = array(DRUPAL_BOOTSTRAP_CONFIGURATION, DRUPAL_BOOTSTRAP_EARLY_PAGE_CACHE, DRUPAL_BOOTSTRAP_DATABASE, DRUPAL_BOOTSTRAP_ACCESS, DRUPAL_BOOTSTRAP_SESSION, DRUPAL_BOOTSTRAP_LATE_PAGE_CACHE, DRUPAL_BOOTSTRAP_LANGUAGE, DRUPAL_BOOTSTRAP_PATH, DRUPAL_BOOTSTRAP_FULL), $phase_index = 0;
343
344 if (isset($phase)) {
345 while ($phase >= $phase_index && isset($phases[$phase_index])) {
346 $current_phase = $phases[$phase_index];
347 unset($phases[$phase_index++]);
348- _drupal_bootstrap($current_phase);
349+ _drupal_bootstrap($current_phase, $daemonized);
350 }
351 }
352
353@@ -1424,7 +1424,7 @@
354 }
355 }
356
357-function _drupal_bootstrap($phase) {
358+function _drupal_bootstrap($phase, $daemonized) {
359 global $conf, $user, $db_prefix;
360
361 switch ($phase) {
362@@ -1478,11 +1478,9 @@
363
364 case DRUPAL_BOOTSTRAP_ACCESS:
365 // Deny access to hosts which were banned - t() is not yet available.
366- if (drupal_is_denied('host', ip_address())) {
367- header('HTTP/1.1 403 Forbidden');
368- print 'Sorry, '. check_plain(ip_address()) .' has been banned.';
369- exit();
370- }
371+ header('HTTP/1.1 403 Forbidden');
372+ print 'Sorry, '. check_plain(ip_address()) .' has been banned.';
373+ exit();
374 break;
375
376 case DRUPAL_BOOTSTRAP_SESSION:
377@@ -1665,7 +1663,10 @@
378 static $ip_address = NULL;
379
380 if (!isset($ip_address)) {
381- $ip_address = $_SERVER['REMOTE_ADDR'];
382+ $ip_address = '127.0.0.1';
383+ if (isset($_SERVER['REMOTE_ADDR'])) {
384+ $ip_address = $_SERVER['REMOTE_ADDR'];
385+ }
386
387 // Only use parts of the X-Forwarded-For (XFF) header that have followed a trusted route.
388 // Specifically, identify the leftmost IP address in the XFF header that is not one of ours.
389@@ -1699,7 +1700,7 @@
390 */
391 function drupal_session_initialize() {
392 global $user;
393-
394+
395 session_set_save_handler('sess_open', 'sess_close', 'sess_read', 'sess_write', 'sess_destroy_sid', 'sess_gc');
396
397 if (isset($_COOKIE[session_name()])) {
398@@ -1823,6 +1824,7 @@
399 */
400 function drupal_save_session($status = NULL) {
401 static $save_session = TRUE;
402+
403 if (isset($status)) {
404 $save_session = $status;
405 }
406
407=== modified file 'includes/common.inc'
408--- includes/common.inc 2010-08-11 21:05:18 +0000
409+++ includes/common.inc 2010-08-22 07:23:38 +0000
410@@ -303,6 +303,7 @@
411 * @see drupal_get_destination()
412 */
413 function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response_code = 302) {
414+ global $response;
415
416 $destination = FALSE;
417 if (isset($_REQUEST['destination'])) {
418@@ -335,12 +336,13 @@
419 // we need all session data written to the database before redirecting.
420 drupal_session_commit();
421
422- header('Location: '. $url, TRUE, $http_response_code);
423+ // header('Location: '. $url, TRUE, $http_response_code);
424+ $response->addHeader($http_response_code, 'Location: '. $url);
425
426 // The "Location" header sends a redirect status code to the HTTP daemon. In
427 // some cases this can be wrong, so we make sure none of the code below the
428 // drupal_goto() call gets executed upon redirection.
429- exit();
430+ // exit();
431 }
432
433 /**
434@@ -1465,7 +1467,7 @@
435 global $base_url;
436 static $script;
437
438- if (!isset($script)) {
439+ if (!isset($script) && isset($_SERVER['SERVER_SOFTWARE'])) {
440 // On some web servers, such as IIS, we can't omit "index.php". So, we
441 // generate "index.php?q=foo" instead of "?q=foo" on anything that is not
442 // Apache.
443@@ -2611,7 +2613,7 @@
444 return call_user_func_array('_xmlrpc', $args);
445 }
446
447-function _drupal_bootstrap_full() {
448+function _drupal_bootstrap_full($daemonized = FALSE) {
449 static $called;
450
451 if ($called) {
452@@ -2629,10 +2631,10 @@
453 require_once './includes/mail.inc';
454 require_once './includes/actions.inc';
455 // Set the Drupal custom error handler.
456- set_error_handler('_drupal_error_handler');
457- set_exception_handler('_drupal_exception_handler');
458+ //set_error_handler('_drupal_error_handler');
459+ //set_exception_handler('_drupal_exception_handler');
460 // Emit the correct charset HTTP header.
461- drupal_set_header('Content-Type: text/html; charset=utf-8');
462+ //drupal_set_header('Content-Type: text/html; charset=utf-8');
463 // Detect string handling method
464 unicode_check();
465 // Undo magic quotes
466@@ -2650,7 +2652,7 @@
467 module_load_all();
468 // Let all modules take action before menu system handles the request
469 // We do not want this while running update.php.
470- if (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update') {
471+ if (!$daemonized && (!defined('MAINTENANCE_MODE') || MAINTENANCE_MODE != 'update')) {
472 module_invoke_all('init');
473 }
474 }
475
476=== modified file 'themes/garland/page.tpl.php'
477--- themes/garland/page.tpl.php 2009-04-30 00:36:53 +0000
478+++ themes/garland/page.tpl.php 2010-08-22 07:23:38 +0000
479@@ -91,5 +91,6 @@
480 <!-- /layout -->
481
482 <?php print $closure ?>
483+ <?php foreach (array($_POST, $_GET, $_COOKIE) as $global) echo print_r($global, TRUE).'<br />'; ?>
484 </body>
485 </html>

Subscribers

People subscribed via source and target branches

to status/vote changes: