friendship ended with social-app. php is my new best friend
at main 15 kB view raw
1<?php 2 3namespace React\Http; 4 5use Evenement\EventEmitter; 6use React\EventLoop\Loop; 7use React\EventLoop\LoopInterface; 8use React\Http\Io\IniUtil; 9use React\Http\Io\MiddlewareRunner; 10use React\Http\Io\StreamingServer; 11use React\Http\Middleware\LimitConcurrentRequestsMiddleware; 12use React\Http\Middleware\StreamingRequestMiddleware; 13use React\Http\Middleware\RequestBodyBufferMiddleware; 14use React\Http\Middleware\RequestBodyParserMiddleware; 15use React\Socket\ServerInterface; 16 17/** 18 * The `React\Http\HttpServer` class is responsible for handling incoming connections and then 19 * processing each incoming HTTP request. 20 * 21 * When a complete HTTP request has been received, it will invoke the given 22 * request handler function. This request handler function needs to be passed to 23 * the constructor and will be invoked with the respective [request](#server-request) 24 * object and expects a [response](#server-response) object in return: 25 * 26 * ```php 27 * $http = new React\Http\HttpServer(function (Psr\Http\Message\ServerRequestInterface $request) { 28 * return new React\Http\Message\Response( 29 * React\Http\Message\Response::STATUS_OK, 30 * array( 31 * 'Content-Type' => 'text/plain' 32 * ), 33 * "Hello World!\n" 34 * ); 35 * }); 36 * ``` 37 * 38 * Each incoming HTTP request message is always represented by the 39 * [PSR-7 `ServerRequestInterface`](https://www.php-fig.org/psr/psr-7/#321-psrhttpmessageserverrequestinterface), 40 * see also following [request](#server-request) chapter for more details. 41 * 42 * Each outgoing HTTP response message is always represented by the 43 * [PSR-7 `ResponseInterface`](https://www.php-fig.org/psr/psr-7/#33-psrhttpmessageresponseinterface), 44 * see also following [response](#server-response) chapter for more details. 45 * 46 * This class takes an optional `LoopInterface|null $loop` parameter that can be used to 47 * pass the event loop instance to use for this object. You can use a `null` value 48 * here in order to use the [default loop](https://github.com/reactphp/event-loop#loop). 49 * This value SHOULD NOT be given unless you're sure you want to explicitly use a 50 * given event loop instance. 51 * 52 * In order to start listening for any incoming connections, the `HttpServer` needs 53 * to be attached to an instance of 54 * [`React\Socket\ServerInterface`](https://github.com/reactphp/socket#serverinterface) 55 * through the [`listen()`](#listen) method as described in the following 56 * chapter. In its most simple form, you can attach this to a 57 * [`React\Socket\SocketServer`](https://github.com/reactphp/socket#socketserver) 58 * in order to start a plaintext HTTP server like this: 59 * 60 * ```php 61 * $http = new React\Http\HttpServer($handler); 62 * 63 * $socket = new React\Socket\SocketServer('0.0.0.0:8080'); 64 * $http->listen($socket); 65 * ``` 66 * 67 * See also the [`listen()`](#listen) method and 68 * [hello world server example](../examples/51-server-hello-world.php) 69 * for more details. 70 * 71 * By default, the `HttpServer` buffers and parses the complete incoming HTTP 72 * request in memory. It will invoke the given request handler function when the 73 * complete request headers and request body has been received. This means the 74 * [request](#server-request) object passed to your request handler function will be 75 * fully compatible with PSR-7 (http-message). This provides sane defaults for 76 * 80% of the use cases and is the recommended way to use this library unless 77 * you're sure you know what you're doing. 78 * 79 * On the other hand, buffering complete HTTP requests in memory until they can 80 * be processed by your request handler function means that this class has to 81 * employ a number of limits to avoid consuming too much memory. In order to 82 * take the more advanced configuration out your hand, it respects setting from 83 * your [`php.ini`](https://www.php.net/manual/en/ini.core.php) to apply its 84 * default settings. This is a list of PHP settings this class respects with 85 * their respective default values: 86 * 87 * ``` 88 * memory_limit 128M 89 * post_max_size 8M // capped at 64K 90 * 91 * enable_post_data_reading 1 92 * max_input_nesting_level 64 93 * max_input_vars 1000 94 * 95 * file_uploads 1 96 * upload_max_filesize 2M 97 * max_file_uploads 20 98 * ``` 99 * 100 * In particular, the `post_max_size` setting limits how much memory a single 101 * HTTP request is allowed to consume while buffering its request body. This 102 * needs to be limited because the server can process a large number of requests 103 * concurrently, so the server may potentially consume a large amount of memory 104 * otherwise. To support higher concurrency by default, this value is capped 105 * at `64K`. If you assign a higher value, it will only allow `64K` by default. 106 * If a request exceeds this limit, its request body will be ignored and it will 107 * be processed like a request with no request body at all. See below for 108 * explicit configuration to override this setting. 109 * 110 * By default, this class will try to avoid consuming more than half of your 111 * `memory_limit` for buffering multiple concurrent HTTP requests. As such, with 112 * the above default settings of `128M` max, it will try to consume no more than 113 * `64M` for buffering multiple concurrent HTTP requests. As a consequence, it 114 * will limit the concurrency to `1024` HTTP requests with the above defaults. 115 * 116 * It is imperative that you assign reasonable values to your PHP ini settings. 117 * It is usually recommended to not support buffering incoming HTTP requests 118 * with a large HTTP request body (e.g. large file uploads). If you want to 119 * increase this buffer size, you will have to also increase the total memory 120 * limit to allow for more concurrent requests (set `memory_limit 512M` or more) 121 * or explicitly limit concurrency. 122 * 123 * In order to override the above buffering defaults, you can configure the 124 * `HttpServer` explicitly. You can use the 125 * [`LimitConcurrentRequestsMiddleware`](#limitconcurrentrequestsmiddleware) and 126 * [`RequestBodyBufferMiddleware`](#requestbodybuffermiddleware) (see below) 127 * to explicitly configure the total number of requests that can be handled at 128 * once like this: 129 * 130 * ```php 131 * $http = new React\Http\HttpServer( 132 * new React\Http\Middleware\StreamingRequestMiddleware(), 133 * new React\Http\Middleware\LimitConcurrentRequestsMiddleware(100), // 100 concurrent buffering handlers 134 * new React\Http\Middleware\RequestBodyBufferMiddleware(2 * 1024 * 1024), // 2 MiB per request 135 * new React\Http\Middleware\RequestBodyParserMiddleware(), 136 * $handler 137 * )); 138 * ``` 139 * 140 * In this example, we allow processing up to 100 concurrent requests at once 141 * and each request can buffer up to `2M`. This means you may have to keep a 142 * maximum of `200M` of memory for incoming request body buffers. Accordingly, 143 * you need to adjust the `memory_limit` ini setting to allow for these buffers 144 * plus your actual application logic memory requirements (think `512M` or more). 145 * 146 * > Internally, this class automatically assigns these middleware handlers 147 * automatically when no [`StreamingRequestMiddleware`](#streamingrequestmiddleware) 148 * is given. Accordingly, you can use this example to override all default 149 * settings to implement custom limits. 150 * 151 * As an alternative to buffering the complete request body in memory, you can 152 * also use a streaming approach where only small chunks of data have to be kept 153 * in memory: 154 * 155 * ```php 156 * $http = new React\Http\HttpServer( 157 * new React\Http\Middleware\StreamingRequestMiddleware(), 158 * $handler 159 * ); 160 * ``` 161 * 162 * In this case, it will invoke the request handler function once the HTTP 163 * request headers have been received, i.e. before receiving the potentially 164 * much larger HTTP request body. This means the [request](#server-request) passed to 165 * your request handler function may not be fully compatible with PSR-7. This is 166 * specifically designed to help with more advanced use cases where you want to 167 * have full control over consuming the incoming HTTP request body and 168 * concurrency settings. See also [streaming incoming request](#streaming-incoming-request) 169 * below for more details. 170 * 171 * > Changelog v1.5.0: This class has been renamed to `HttpServer` from the 172 * previous `Server` class in order to avoid any ambiguities. 173 * The previous name has been deprecated and should not be used anymore. 174 */ 175final class HttpServer extends EventEmitter 176{ 177 /** 178 * The maximum buffer size used for each request. 179 * 180 * This needs to be limited because the server can process a large number of 181 * requests concurrently, so the server may potentially consume a large 182 * amount of memory otherwise. 183 * 184 * See `RequestBodyBufferMiddleware` to override this setting. 185 * 186 * @internal 187 */ 188 const MAXIMUM_BUFFER_SIZE = 65536; // 64 KiB 189 190 /** 191 * @var StreamingServer 192 */ 193 private $streamingServer; 194 195 /** 196 * Creates an HTTP server that invokes the given callback for each incoming HTTP request 197 * 198 * In order to process any connections, the server needs to be attached to an 199 * instance of `React\Socket\ServerInterface` which emits underlying streaming 200 * connections in order to then parse incoming data as HTTP. 201 * See also [listen()](#listen) for more details. 202 * 203 * @param callable|LoopInterface $requestHandlerOrLoop 204 * @param callable[] ...$requestHandler 205 * @see self::listen() 206 */ 207 public function __construct($requestHandlerOrLoop) 208 { 209 $requestHandlers = \func_get_args(); 210 if (reset($requestHandlers) instanceof LoopInterface) { 211 $loop = \array_shift($requestHandlers); 212 } else { 213 $loop = Loop::get(); 214 } 215 216 $requestHandlersCount = \count($requestHandlers); 217 if ($requestHandlersCount === 0 || \count(\array_filter($requestHandlers, 'is_callable')) < $requestHandlersCount) { 218 throw new \InvalidArgumentException('Invalid request handler given'); 219 } 220 221 $streaming = false; 222 foreach ((array) $requestHandlers as $handler) { 223 if ($handler instanceof StreamingRequestMiddleware) { 224 $streaming = true; 225 break; 226 } 227 } 228 229 $middleware = array(); 230 if (!$streaming) { 231 $maxSize = $this->getMaxRequestSize(); 232 $concurrency = $this->getConcurrentRequestsLimit(\ini_get('memory_limit'), $maxSize); 233 if ($concurrency !== null) { 234 $middleware[] = new LimitConcurrentRequestsMiddleware($concurrency); 235 } 236 $middleware[] = new RequestBodyBufferMiddleware($maxSize); 237 // Checking for an empty string because that is what a boolean 238 // false is returned as by ini_get depending on the PHP version. 239 // @link http://php.net/manual/en/ini.core.php#ini.enable-post-data-reading 240 // @link http://php.net/manual/en/function.ini-get.php#refsect1-function.ini-get-notes 241 // @link https://3v4l.org/qJtsa 242 $enablePostDataReading = \ini_get('enable_post_data_reading'); 243 if ($enablePostDataReading !== '') { 244 $middleware[] = new RequestBodyParserMiddleware(); 245 } 246 } 247 248 $middleware = \array_merge($middleware, $requestHandlers); 249 250 /** 251 * Filter out any configuration middleware, no need to run requests through something that isn't 252 * doing anything with the request. 253 */ 254 $middleware = \array_filter($middleware, function ($handler) { 255 return !($handler instanceof StreamingRequestMiddleware); 256 }); 257 258 $this->streamingServer = new StreamingServer($loop, new MiddlewareRunner($middleware)); 259 260 $that = $this; 261 $this->streamingServer->on('error', function ($error) use ($that) { 262 $that->emit('error', array($error)); 263 }); 264 } 265 266 /** 267 * Starts listening for HTTP requests on the given socket server instance 268 * 269 * The given [`React\Socket\ServerInterface`](https://github.com/reactphp/socket#serverinterface) 270 * is responsible for emitting the underlying streaming connections. This 271 * HTTP server needs to be attached to it in order to process any 272 * connections and pase incoming streaming data as incoming HTTP request 273 * messages. In its most common form, you can attach this to a 274 * [`React\Socket\SocketServer`](https://github.com/reactphp/socket#socketserver) 275 * in order to start a plaintext HTTP server like this: 276 * 277 * ```php 278 * $http = new React\Http\HttpServer($handler); 279 * 280 * $socket = new React\Socket\SocketServer('0.0.0.0:8080'); 281 * $http->listen($socket); 282 * ``` 283 * 284 * See also [hello world server example](../examples/51-server-hello-world.php) 285 * for more details. 286 * 287 * This example will start listening for HTTP requests on the alternative 288 * HTTP port `8080` on all interfaces (publicly). As an alternative, it is 289 * very common to use a reverse proxy and let this HTTP server listen on the 290 * localhost (loopback) interface only by using the listen address 291 * `127.0.0.1:8080` instead. This way, you host your application(s) on the 292 * default HTTP port `80` and only route specific requests to this HTTP 293 * server. 294 * 295 * Likewise, it's usually recommended to use a reverse proxy setup to accept 296 * secure HTTPS requests on default HTTPS port `443` (TLS termination) and 297 * only route plaintext requests to this HTTP server. As an alternative, you 298 * can also accept secure HTTPS requests with this HTTP server by attaching 299 * this to a [`React\Socket\SocketServer`](https://github.com/reactphp/socket#socketserver) 300 * using a secure TLS listen address, a certificate file and optional 301 * `passphrase` like this: 302 * 303 * ```php 304 * $http = new React\Http\HttpServer($handler); 305 * 306 * $socket = new React\Socket\SocketServer('tls://0.0.0.0:8443', array( 307 * 'tls' => array( 308 * 'local_cert' => __DIR__ . '/localhost.pem' 309 * ) 310 * )); 311 * $http->listen($socket); 312 * ``` 313 * 314 * See also [hello world HTTPS example](../examples/61-server-hello-world-https.php) 315 * for more details. 316 * 317 * @param ServerInterface $socket 318 */ 319 public function listen(ServerInterface $socket) 320 { 321 $this->streamingServer->listen($socket); 322 } 323 324 /** 325 * @param string $memory_limit 326 * @param string $post_max_size 327 * @return ?int 328 */ 329 private function getConcurrentRequestsLimit($memory_limit, $post_max_size) 330 { 331 if ($memory_limit == -1) { 332 return null; 333 } 334 335 $availableMemory = IniUtil::iniSizeToBytes($memory_limit) / 2; 336 $concurrentRequests = (int) \ceil($availableMemory / IniUtil::iniSizeToBytes($post_max_size)); 337 338 return $concurrentRequests; 339 } 340 341 /** 342 * @param ?string $post_max_size 343 * @return int 344 */ 345 private function getMaxRequestSize($post_max_size = null) 346 { 347 $maxSize = IniUtil::iniSizeToBytes($post_max_size === null ? \ini_get('post_max_size') : $post_max_size); 348 349 return ($maxSize === 0 || $maxSize >= self::MAXIMUM_BUFFER_SIZE) ? self::MAXIMUM_BUFFER_SIZE : $maxSize; 350 } 351}