91 * Switch to control if directory listing is enabled |
91 * Switch to control if directory listing is enabled |
92 * @var bool |
92 * @var bool |
93 */ |
93 */ |
94 |
94 |
95 var $allow_dir_list = false; |
95 var $allow_dir_list = false; |
|
96 |
|
97 /** |
|
98 * Switch to control forking support. |
|
99 * @var bool |
|
100 */ |
|
101 |
|
102 var $allow_fork = true; |
|
103 |
|
104 /** |
|
105 * Keep-alive support uses this to track what the client requested. |
|
106 * Only used if $allow_fork is set to true. |
|
107 * @var bool |
|
108 */ |
|
109 |
|
110 var $in_keepalive = false; |
96 |
111 |
97 /** |
112 /** |
98 * Constructor. |
113 * Constructor. |
99 * @param string IPv4 address to bind to |
114 * @param string IPv4 address to bind to |
100 * @param int Port number |
115 * @param int Port number |
128 * Destructor. |
143 * Destructor. |
129 */ |
144 */ |
130 |
145 |
131 function __destruct() |
146 function __destruct() |
132 { |
147 { |
133 status('WebServer: destroying socket'); |
148 if ( !defined('HTTPD_WS_CHILD') ) |
134 @socket_close($this->sock); |
149 { |
|
150 status('WebServer: destroying socket'); |
|
151 @socket_shutdown($this->sock, 2); |
|
152 @socket_close($this->sock); |
|
153 } |
135 } |
154 } |
136 |
155 |
137 /** |
156 /** |
138 * Main server loop |
157 * Main server loop |
139 */ |
158 */ |
140 |
159 |
141 function serve() |
160 function serve() |
142 { |
161 { |
143 while ( true ) |
162 while ( true ) |
144 { |
163 { |
|
164 // if this is a child process, we're finished - close up shop |
|
165 if ( defined('HTTPD_WS_CHILD') && !$this->in_keepalive ) |
|
166 { |
|
167 exit(0); |
|
168 } |
|
169 |
145 // wait for connection... |
170 // wait for connection... |
146 // trick from http://us.php.net/manual/en/function.socket-accept.php |
171 // trick from http://us.php.net/manual/en/function.socket-accept.php |
147 $remote = false; |
172 if ( !defined('HTTPD_WS_CHILD') ) |
148 switch(@socket_select($r = array($this->sock), $w = array($this->sock), $e = array($this->sock), 5)) { |
173 { |
149 case 2: |
174 $remote = false; |
150 break; |
175 $timeout = 5; |
151 case 1: |
176 switch(@socket_select($r = array($this->sock), $w = array($this->sock), $e = array($this->sock), $timeout)) { |
152 $remote = @socket_accept($this->sock); |
177 case 2: |
153 break; |
178 break; |
154 case 0: |
179 case 1: |
155 break; |
180 $remote = @socket_accept($this->sock); |
|
181 break; |
|
182 case 0: |
|
183 break; |
|
184 } |
156 } |
185 } |
157 |
186 |
158 if ( !$remote ) |
187 if ( !$remote ) |
159 { |
188 { |
|
189 $this->in_keepalive = false; |
160 continue; |
190 continue; |
161 } |
191 } |
|
192 |
|
193 // fork off if possible |
|
194 if ( function_exists('pcntl_fork') && $this->allow_fork && !$this->in_keepalive ) |
|
195 { |
|
196 $pid = pcntl_fork(); |
|
197 if ( $pid == -1 ) |
|
198 { |
|
199 // do nothing; continue responding to request in single-threaded mode |
|
200 } |
|
201 else if ( $pid ) |
|
202 { |
|
203 // we are the parent, continue listening |
|
204 $remote = false; |
|
205 continue; |
|
206 } |
|
207 else |
|
208 { |
|
209 // this is the child |
|
210 define('HTTPD_WS_CHILD', 1); |
|
211 $this->sock = false; |
|
212 } |
|
213 } |
|
214 |
|
215 $this->in_keepalive = false; |
162 |
216 |
163 // read request |
217 // read request |
164 $last_line = ''; |
218 $last_line = ''; |
165 $client_headers = ''; |
219 $client_headers = ''; |
166 while ( $line = @socket_read($remote, 1024, PHP_NORMAL_READ) ) |
220 while ( $line = @socket_read($remote, 1024, PHP_NORMAL_READ) ) |
196 continue; |
250 continue; |
197 $key = 'HTTP_' . strtoupper(str_replace('-', '_', $match[1])); |
251 $key = 'HTTP_' . strtoupper(str_replace('-', '_', $match[1])); |
198 $_SERVER[$key] = $match[2]; |
252 $_SERVER[$key] = $match[2]; |
199 } |
253 } |
200 |
254 |
|
255 // enable keep-alive if requested |
|
256 if ( isset($_SERVER['HTTP_CONNECTION']) && defined('HTTPD_WS_CHILD') ) |
|
257 { |
|
258 $this->in_keepalive = ( $_SERVER['HTTP_CONNECTION'] === 'keep-alive' ); |
|
259 } |
|
260 |
201 if ( isset($_SERVER['HTTP_AUTHORIZATION']) ) |
261 if ( isset($_SERVER['HTTP_AUTHORIZATION']) ) |
202 { |
262 { |
203 $data = $_SERVER['HTTP_AUTHORIZATION']; |
263 $data = $_SERVER['HTTP_AUTHORIZATION']; |
204 $data = substr(strstr($data, ' '), 1); |
264 $data = substr(strstr($data, ' '), 1); |
205 $data = base64_decode($data); |
265 $data = base64_decode($data); |
283 continue; |
343 continue; |
284 } |
344 } |
285 |
345 |
286 $this->send_standard_response($remote, $handler, $uri, $params); |
346 $this->send_standard_response($remote, $handler, $uri, $params); |
287 |
347 |
288 @socket_close($remote); |
348 if ( !$this->in_keepalive ) |
|
349 { |
|
350 // if ( defined('HTTPD_WS_CHILD') ) |
|
351 // status('Closing connection'); |
|
352 @socket_close($remote); |
|
353 exit(0); |
|
354 } |
|
355 else |
|
356 { |
|
357 // if ( defined('HTTPD_WS_CHILD') ) |
|
358 // status('Continuing connection'); |
|
359 @socket_write($remote, "\r\n\r\n"); |
|
360 } |
289 } |
361 } |
290 } |
362 } |
291 |
363 |
292 /** |
364 /** |
293 * Sends the client appropriate response headers. |
365 * Sends the client appropriate response headers. |
300 function send_client_headers($socket, $http_code = 200, $contenttype = 'text/html', $headers = '') |
372 function send_client_headers($socket, $http_code = 200, $contenttype = 'text/html', $headers = '') |
301 { |
373 { |
302 global $http_responses; |
374 global $http_responses; |
303 $reason_code = ( isset($http_responses[$http_code]) ) ? $http_responses[$http_code] : 'Unknown'; |
375 $reason_code = ( isset($http_responses[$http_code]) ) ? $http_responses[$http_code] : 'Unknown'; |
304 |
376 |
|
377 $_SERVER['HTTP_USER_AGENT'] = ( isset($_SERVER['HTTP_USER_AGENT']) ) ? $_SERVER['HTTP_USER_AGENT'] : '(no user agent)'; |
305 status("{$_SERVER['REMOTE_ADDR']} {$_SERVER['REQUEST_METHOD']} {$_SERVER['REQUEST_URI']} $http_code {$_SERVER['HTTP_USER_AGENT']}"); |
378 status("{$_SERVER['REMOTE_ADDR']} {$_SERVER['REQUEST_METHOD']} {$_SERVER['REQUEST_URI']} $http_code {$_SERVER['HTTP_USER_AGENT']}"); |
306 |
379 |
307 $headers = str_replace("\r\n", "\n", $headers); |
380 $headers = str_replace("\r\n", "\n", $headers); |
308 $headers = str_replace("\n", "\r\n", $headers); |
381 $headers = str_replace("\n", "\r\n", $headers); |
309 $headers = preg_replace("#[\r\n]+$#", '', $headers); |
382 $headers = preg_replace("#[\r\n]+$#", '', $headers); |
|
383 $connection = ( $this->in_keepalive ) ? 'keep-alive' : 'close'; |
310 |
384 |
311 @socket_write($socket, "HTTP/1.1 $http_code $reason_code\r\n"); |
385 @socket_write($socket, "HTTP/1.1 $http_code $reason_code\r\n"); |
312 @socket_write($socket, "Server: $this->server_string"); |
386 @socket_write($socket, "Server: $this->server_string"); |
313 @socket_write($socket, "Connection: close\r\n"); |
387 @socket_write($socket, "Connection: $connection\r\n"); |
314 @socket_write($socket, "Content-Type: $contenttype\r\n"); |
388 @socket_write($socket, "Content-Type: $contenttype\r\n"); |
315 if ( !empty($headers) ) |
389 if ( !empty($headers) ) |
316 { |
390 { |
317 @socket_write($socket, "$headers\r\n"); |
391 @socket_write($socket, "$headers\r\n"); |
318 } |
392 } |
516 if ( $output == '__break__' ) |
590 if ( $output == '__break__' ) |
517 { |
591 { |
518 return true; |
592 return true; |
519 } |
593 } |
520 |
594 |
|
595 $this->header("Content-length: " . strlen($output)); |
521 $headers = implode("\r\n", $this->response_headers); |
596 $headers = implode("\r\n", $this->response_headers); |
522 |
597 |
523 // write headers |
598 // write headers |
524 $this->send_client_headers($socket, $this->response_code, $this->content_type, $headers); |
599 $this->send_client_headers($socket, $this->response_code, $this->content_type, $headers); |
525 |
600 |