friendship ended with social-app. php is my new best friend
at main 15 kB view raw
1<?php 2 3namespace React\Http\Message; 4 5use Fig\Http\Message\StatusCodeInterface; 6use Psr\Http\Message\ResponseInterface; 7use Psr\Http\Message\StreamInterface; 8use React\Http\Io\AbstractMessage; 9use React\Http\Io\BufferedBody; 10use React\Http\Io\HttpBodyStream; 11use React\Stream\ReadableStreamInterface; 12 13/** 14 * Represents an outgoing server response message. 15 * 16 * ```php 17 * $response = new React\Http\Message\Response( 18 * React\Http\Message\Response::STATUS_OK, 19 * array( 20 * 'Content-Type' => 'text/html' 21 * ), 22 * "<html>Hello world!</html>\n" 23 * ); 24 * ``` 25 * 26 * This class implements the 27 * [PSR-7 `ResponseInterface`](https://www.php-fig.org/psr/psr-7/#33-psrhttpmessageresponseinterface) 28 * which in turn extends the 29 * [PSR-7 `MessageInterface`](https://www.php-fig.org/psr/psr-7/#31-psrhttpmessagemessageinterface). 30 * 31 * On top of this, this class implements the 32 * [PSR-7 Message Util `StatusCodeInterface`](https://github.com/php-fig/http-message-util/blob/master/src/StatusCodeInterface.php) 33 * which means that most common HTTP status codes are available as class 34 * constants with the `STATUS_*` prefix. For instance, the `200 OK` and 35 * `404 Not Found` status codes can used as `Response::STATUS_OK` and 36 * `Response::STATUS_NOT_FOUND` respectively. 37 * 38 * > Internally, this implementation builds on top a base class which is 39 * considered an implementation detail that may change in the future. 40 * 41 * @see \Psr\Http\Message\ResponseInterface 42 */ 43final class Response extends AbstractMessage implements ResponseInterface, StatusCodeInterface 44{ 45 /** 46 * Create an HTML response 47 * 48 * ```php 49 * $html = <<<HTML 50 * <!doctype html> 51 * <html> 52 * <body>Hello wörld!</body> 53 * </html> 54 * 55 * HTML; 56 * 57 * $response = React\Http\Message\Response::html($html); 58 * ``` 59 * 60 * This is a convenient shortcut method that returns the equivalent of this: 61 * 62 * ``` 63 * $response = new React\Http\Message\Response( 64 * React\Http\Message\Response::STATUS_OK, 65 * [ 66 * 'Content-Type' => 'text/html; charset=utf-8' 67 * ], 68 * $html 69 * ); 70 * ``` 71 * 72 * This method always returns a response with a `200 OK` status code and 73 * the appropriate `Content-Type` response header for the given HTTP source 74 * string encoded in UTF-8 (Unicode). It's generally recommended to end the 75 * given plaintext string with a trailing newline. 76 * 77 * If you want to use a different status code or custom HTTP response 78 * headers, you can manipulate the returned response object using the 79 * provided PSR-7 methods or directly instantiate a custom HTTP response 80 * object using the `Response` constructor: 81 * 82 * ```php 83 * $response = React\Http\Message\Response::html( 84 * "<h1>Error</h1>\n<p>Invalid user name given.</p>\n" 85 * )->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST); 86 * ``` 87 * 88 * @param string $html 89 * @return self 90 */ 91 public static function html($html) 92 { 93 return new self(self::STATUS_OK, array('Content-Type' => 'text/html; charset=utf-8'), $html); 94 } 95 96 /** 97 * Create a JSON response 98 * 99 * ```php 100 * $response = React\Http\Message\Response::json(['name' => 'Alice']); 101 * ``` 102 * 103 * This is a convenient shortcut method that returns the equivalent of this: 104 * 105 * ``` 106 * $response = new React\Http\Message\Response( 107 * React\Http\Message\Response::STATUS_OK, 108 * [ 109 * 'Content-Type' => 'application/json' 110 * ], 111 * json_encode( 112 * ['name' => 'Alice'], 113 * JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRESERVE_ZERO_FRACTION 114 * ) . "\n" 115 * ); 116 * ``` 117 * 118 * This method always returns a response with a `200 OK` status code and 119 * the appropriate `Content-Type` response header for the given structured 120 * data encoded as a JSON text. 121 * 122 * The given structured data will be encoded as a JSON text. Any `string` 123 * values in the data must be encoded in UTF-8 (Unicode). If the encoding 124 * fails, this method will throw an `InvalidArgumentException`. 125 * 126 * By default, the given structured data will be encoded with the flags as 127 * shown above. This includes pretty printing (PHP 5.4+) and preserving 128 * zero fractions for `float` values (PHP 5.6.6+) to ease debugging. It is 129 * assumed any additional data overhead is usually compensated by using HTTP 130 * response compression. 131 * 132 * If you want to use a different status code or custom HTTP response 133 * headers, you can manipulate the returned response object using the 134 * provided PSR-7 methods or directly instantiate a custom HTTP response 135 * object using the `Response` constructor: 136 * 137 * ```php 138 * $response = React\Http\Message\Response::json( 139 * ['error' => 'Invalid user name given'] 140 * )->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST); 141 * ``` 142 * 143 * @param mixed $data 144 * @return self 145 * @throws \InvalidArgumentException when encoding fails 146 */ 147 public static function json($data) 148 { 149 $json = @\json_encode( 150 $data, 151 (\defined('JSON_PRETTY_PRINT') ? \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES | \JSON_UNESCAPED_UNICODE : 0) | (\defined('JSON_PRESERVE_ZERO_FRACTION') ? \JSON_PRESERVE_ZERO_FRACTION : 0) 152 ); 153 154 // throw on error, now `false` but used to be `(string) "null"` before PHP 5.5 155 if ($json === false || (\PHP_VERSION_ID < 50500 && \json_last_error() !== \JSON_ERROR_NONE)) { 156 throw new \InvalidArgumentException( 157 'Unable to encode given data as JSON' . (\function_exists('json_last_error_msg') ? ': ' . \json_last_error_msg() : ''), 158 \json_last_error() 159 ); 160 } 161 162 return new self(self::STATUS_OK, array('Content-Type' => 'application/json'), $json . "\n"); 163 } 164 165 /** 166 * Create a plaintext response 167 * 168 * ```php 169 * $response = React\Http\Message\Response::plaintext("Hello wörld!\n"); 170 * ``` 171 * 172 * This is a convenient shortcut method that returns the equivalent of this: 173 * 174 * ``` 175 * $response = new React\Http\Message\Response( 176 * React\Http\Message\Response::STATUS_OK, 177 * [ 178 * 'Content-Type' => 'text/plain; charset=utf-8' 179 * ], 180 * "Hello wörld!\n" 181 * ); 182 * ``` 183 * 184 * This method always returns a response with a `200 OK` status code and 185 * the appropriate `Content-Type` response header for the given plaintext 186 * string encoded in UTF-8 (Unicode). It's generally recommended to end the 187 * given plaintext string with a trailing newline. 188 * 189 * If you want to use a different status code or custom HTTP response 190 * headers, you can manipulate the returned response object using the 191 * provided PSR-7 methods or directly instantiate a custom HTTP response 192 * object using the `Response` constructor: 193 * 194 * ```php 195 * $response = React\Http\Message\Response::plaintext( 196 * "Error: Invalid user name given.\n" 197 * )->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST); 198 * ``` 199 * 200 * @param string $text 201 * @return self 202 */ 203 public static function plaintext($text) 204 { 205 return new self(self::STATUS_OK, array('Content-Type' => 'text/plain; charset=utf-8'), $text); 206 } 207 208 /** 209 * Create an XML response 210 * 211 * ```php 212 * $xml = <<<XML 213 * <?xml version="1.0" encoding="utf-8"?> 214 * <body> 215 * <greeting>Hello wörld!</greeting> 216 * </body> 217 * 218 * XML; 219 * 220 * $response = React\Http\Message\Response::xml($xml); 221 * ``` 222 * 223 * This is a convenient shortcut method that returns the equivalent of this: 224 * 225 * ``` 226 * $response = new React\Http\Message\Response( 227 * React\Http\Message\Response::STATUS_OK, 228 * [ 229 * 'Content-Type' => 'application/xml' 230 * ], 231 * $xml 232 * ); 233 * ``` 234 * 235 * This method always returns a response with a `200 OK` status code and 236 * the appropriate `Content-Type` response header for the given XML source 237 * string. It's generally recommended to use UTF-8 (Unicode) and specify 238 * this as part of the leading XML declaration and to end the given XML 239 * source string with a trailing newline. 240 * 241 * If you want to use a different status code or custom HTTP response 242 * headers, you can manipulate the returned response object using the 243 * provided PSR-7 methods or directly instantiate a custom HTTP response 244 * object using the `Response` constructor: 245 * 246 * ```php 247 * $response = React\Http\Message\Response::xml( 248 * "<error><message>Invalid user name given.</message></error>\n" 249 * )->withStatus(React\Http\Message\Response::STATUS_BAD_REQUEST); 250 * ``` 251 * 252 * @param string $xml 253 * @return self 254 */ 255 public static function xml($xml) 256 { 257 return new self(self::STATUS_OK, array('Content-Type' => 'application/xml'), $xml); 258 } 259 260 /** 261 * @var bool 262 * @see self::$phrasesMap 263 */ 264 private static $phrasesInitialized = false; 265 266 /** 267 * Map of standard HTTP status codes to standard reason phrases. 268 * 269 * This map will be fully populated with all standard reason phrases on 270 * first access. By default, it only contains a subset of HTTP status codes 271 * that have a custom mapping to reason phrases (such as those with dashes 272 * and all caps words). See `self::STATUS_*` for all possible status code 273 * constants. 274 * 275 * @var array<int,string> 276 * @see self::STATUS_* 277 * @see self::getReasonPhraseForStatusCode() 278 */ 279 private static $phrasesMap = array( 280 200 => 'OK', 281 203 => 'Non-Authoritative Information', 282 207 => 'Multi-Status', 283 226 => 'IM Used', 284 414 => 'URI Too Large', 285 418 => 'I\'m a teapot', 286 505 => 'HTTP Version Not Supported' 287 ); 288 289 /** @var int */ 290 private $statusCode; 291 292 /** @var string */ 293 private $reasonPhrase; 294 295 /** 296 * @param int $status HTTP status code (e.g. 200/404), see `self::STATUS_*` constants 297 * @param array<string,string|string[]> $headers additional response headers 298 * @param string|ReadableStreamInterface|StreamInterface $body response body 299 * @param string $version HTTP protocol version (e.g. 1.1/1.0) 300 * @param ?string $reason custom HTTP response phrase 301 * @throws \InvalidArgumentException for an invalid body 302 */ 303 public function __construct( 304 $status = self::STATUS_OK, 305 array $headers = array(), 306 $body = '', 307 $version = '1.1', 308 $reason = null 309 ) { 310 if (\is_string($body)) { 311 $body = new BufferedBody($body); 312 } elseif ($body instanceof ReadableStreamInterface && !$body instanceof StreamInterface) { 313 $body = new HttpBodyStream($body, null); 314 } elseif (!$body instanceof StreamInterface) { 315 throw new \InvalidArgumentException('Invalid response body given'); 316 } 317 318 parent::__construct($version, $headers, $body); 319 320 $this->statusCode = (int) $status; 321 $this->reasonPhrase = ($reason !== '' && $reason !== null) ? (string) $reason : self::getReasonPhraseForStatusCode($status); 322 } 323 324 public function getStatusCode() 325 { 326 return $this->statusCode; 327 } 328 329 public function withStatus($code, $reasonPhrase = '') 330 { 331 if ((string) $reasonPhrase === '') { 332 $reasonPhrase = self::getReasonPhraseForStatusCode($code); 333 } 334 335 if ($this->statusCode === (int) $code && $this->reasonPhrase === (string) $reasonPhrase) { 336 return $this; 337 } 338 339 $response = clone $this; 340 $response->statusCode = (int) $code; 341 $response->reasonPhrase = (string) $reasonPhrase; 342 343 return $response; 344 } 345 346 public function getReasonPhrase() 347 { 348 return $this->reasonPhrase; 349 } 350 351 /** 352 * @param int $code 353 * @return string default reason phrase for given status code or empty string if unknown 354 */ 355 private static function getReasonPhraseForStatusCode($code) 356 { 357 if (!self::$phrasesInitialized) { 358 self::$phrasesInitialized = true; 359 360 // map all `self::STATUS_` constants from status code to reason phrase 361 // e.g. `self::STATUS_NOT_FOUND = 404` will be mapped to `404 Not Found` 362 $ref = new \ReflectionClass(__CLASS__); 363 foreach ($ref->getConstants() as $name => $value) { 364 if (!isset(self::$phrasesMap[$value]) && \strpos($name, 'STATUS_') === 0) { 365 self::$phrasesMap[$value] = \ucwords(\strtolower(\str_replace('_', ' ', \substr($name, 7)))); 366 } 367 } 368 } 369 370 return isset(self::$phrasesMap[$code]) ? self::$phrasesMap[$code] : ''; 371 } 372 373 /** 374 * [Internal] Parse incoming HTTP protocol message 375 * 376 * @internal 377 * @param string $message 378 * @return self 379 * @throws \InvalidArgumentException if given $message is not a valid HTTP response message 380 */ 381 public static function parseMessage($message) 382 { 383 $start = array(); 384 if (!\preg_match('#^HTTP/(?<version>\d\.\d) (?<status>\d{3})(?: (?<reason>[^\r\n]*+))?[\r]?+\n#m', $message, $start)) { 385 throw new \InvalidArgumentException('Unable to parse invalid status-line'); 386 } 387 388 // only support HTTP/1.1 and HTTP/1.0 requests 389 if ($start['version'] !== '1.1' && $start['version'] !== '1.0') { 390 throw new \InvalidArgumentException('Received response with invalid protocol version'); 391 } 392 393 // check number of valid header fields matches number of lines + status line 394 $matches = array(); 395 $n = \preg_match_all(self::REGEX_HEADERS, $message, $matches, \PREG_SET_ORDER); 396 if (\substr_count($message, "\n") !== $n + 1) { 397 throw new \InvalidArgumentException('Unable to parse invalid response header fields'); 398 } 399 400 // format all header fields into associative array 401 $headers = array(); 402 foreach ($matches as $match) { 403 $headers[$match[1]][] = $match[2]; 404 } 405 406 return new self( 407 (int) $start['status'], 408 $headers, 409 '', 410 $start['version'], 411 isset($start['reason']) ? $start['reason'] : '' 412 ); 413 } 414}