| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484 |
- <?php
- /**
- * This file is part of workerman.
- *
- * Licensed under The MIT License
- * For full copyright and license information, please see the MIT-LICENSE.txt
- * Redistributions of files must retain the above copyright notice.
- *
- * @author walkor<walkor@workerman.net>
- * @copyright walkor<walkor@workerman.net>
- * @link http://www.workerman.net/
- * @license http://www.opensource.org/licenses/mit-license.php MIT License
- */
- namespace Workerman\Protocols\Http;
- /**
- * Class Response
- * @package Workerman\Protocols\Http
- */
- class Response
- {
- /**
- * Header data.
- *
- * @var array
- */
- protected $_header = null;
- /**
- * Http status.
- *
- * @var int
- */
- protected $_status = null;
- /**
- * Http reason.
- *
- * @var string
- */
- protected $_reason = null;
- /**
- * Http version.
- *
- * @var string
- */
- protected $_version = '1.1';
- /**
- * Http body.
- *
- * @var string
- */
- protected $_body = null;
- /**
- * Send file info
- *
- * @var array
- */
- public $file = null;
- /**
- * Mine type map.
- * @var array
- */
- protected static $_mimeTypeMap = null;
- /**
- * Phrases.
- *
- * @var array<int,string>
- *
- * @link https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
- */
- const PHRASES = [
- 100 => 'Continue',
- 101 => 'Switching Protocols',
- 102 => 'Processing', // WebDAV; RFC 2518
- 103 => 'Early Hints', // RFC 8297
- 200 => 'OK',
- 201 => 'Created',
- 202 => 'Accepted',
- 203 => 'Non-Authoritative Information', // since HTTP/1.1
- 204 => 'No Content',
- 205 => 'Reset Content',
- 206 => 'Partial Content', // RFC 7233
- 207 => 'Multi-Status', // WebDAV; RFC 4918
- 208 => 'Already Reported', // WebDAV; RFC 5842
- 226 => 'IM Used', // RFC 3229
- 300 => 'Multiple Choices',
- 301 => 'Moved Permanently',
- 302 => 'Found', // Previously "Moved temporarily"
- 303 => 'See Other', // since HTTP/1.1
- 304 => 'Not Modified', // RFC 7232
- 305 => 'Use Proxy', // since HTTP/1.1
- 306 => 'Switch Proxy',
- 307 => 'Temporary Redirect', // since HTTP/1.1
- 308 => 'Permanent Redirect', // RFC 7538
- 400 => 'Bad Request',
- 401 => 'Unauthorized', // RFC 7235
- 402 => 'Payment Required',
- 403 => 'Forbidden',
- 404 => 'Not Found',
- 405 => 'Method Not Allowed',
- 406 => 'Not Acceptable',
- 407 => 'Proxy Authentication Required', // RFC 7235
- 408 => 'Request Timeout',
- 409 => 'Conflict',
- 410 => 'Gone',
- 411 => 'Length Required',
- 412 => 'Precondition Failed', // RFC 7232
- 413 => 'Payload Too Large', // RFC 7231
- 414 => 'URI Too Long', // RFC 7231
- 415 => 'Unsupported Media Type', // RFC 7231
- 416 => 'Range Not Satisfiable', // RFC 7233
- 417 => 'Expectation Failed',
- 418 => 'I\'m a teapot', // RFC 2324, RFC 7168
- 421 => 'Misdirected Request', // RFC 7540
- 422 => 'Unprocessable Entity', // WebDAV; RFC 4918
- 423 => 'Locked', // WebDAV; RFC 4918
- 424 => 'Failed Dependency', // WebDAV; RFC 4918
- 425 => 'Too Early', // RFC 8470
- 426 => 'Upgrade Required',
- 428 => 'Precondition Required', // RFC 6585
- 429 => 'Too Many Requests', // RFC 6585
- 431 => 'Request Header Fields Too Large', // RFC 6585
- 451 => 'Unavailable For Legal Reasons', // RFC 7725
-
- 500 => 'Internal Server Error',
- 501 => 'Not Implemented',
- 502 => 'Bad Gateway',
- 503 => 'Service Unavailable',
- 504 => 'Gateway Timeout',
- 505 => 'HTTP Version Not Supported',
- 506 => 'Variant Also Negotiates', // RFC 2295
- 507 => 'Insufficient Storage', // WebDAV; RFC 4918
- 508 => 'Loop Detected', // WebDAV; RFC 5842
- 510 => 'Not Extended', // RFC 2774
- 511 => 'Network Authentication Required', // RFC 6585
- ];
- /**
- * Init.
- *
- * @return void
- */
- public static function init()
- {
- static::initMimeTypeMap();
- }
- /**
- * Response constructor.
- *
- * @param int $status
- * @param array $headers
- * @param string $body
- */
- public function __construct(
- $status = 200,
- $headers = [],
- $body = ''
- )
- {
- $this->_status = $status;
- $this->_header = $headers;
- $this->_body = (string)$body;
- }
- /**
- * Set header.
- *
- * @param string $name
- * @param string $value
- * @return $this
- */
- public function header($name, $value)
- {
- $this->_header[$name] = $value;
- return $this;
- }
- /**
- * Set header.
- *
- * @param string $name
- * @param string $value
- * @return Response
- */
- public function withHeader($name, $value)
- {
- return $this->header($name, $value);
- }
- /**
- * Set headers.
- *
- * @param array $headers
- * @return $this
- */
- public function withHeaders($headers)
- {
- $this->_header = \array_merge_recursive($this->_header, $headers);
- return $this;
- }
- /**
- * Remove header.
- *
- * @param string $name
- * @return $this
- */
- public function withoutHeader($name)
- {
- unset($this->_header[$name]);
- return $this;
- }
- /**
- * Get header.
- *
- * @param string $name
- * @return null|array|string
- */
- public function getHeader($name)
- {
- return $this->_header[$name] ?? null;
- }
- /**
- * Get headers.
- *
- * @return array
- */
- public function getHeaders()
- {
- return $this->_header;
- }
- /**
- * Set status.
- *
- * @param int $code
- * @param string|null $reason_phrase
- * @return $this
- */
- public function withStatus($code, $reason_phrase = null)
- {
- $this->_status = $code;
- $this->_reason = $reason_phrase;
- return $this;
- }
- /**
- * Get status code.
- *
- * @return int
- */
- public function getStatusCode()
- {
- return $this->_status;
- }
- /**
- * Get reason phrase.
- *
- * @return string
- */
- public function getReasonPhrase()
- {
- return $this->_reason;
- }
- /**
- * Set protocol version.
- *
- * @param int $version
- * @return $this
- */
- public function withProtocolVersion($version)
- {
- $this->_version = $version;
- return $this;
- }
- /**
- * Set http body.
- *
- * @param string $body
- * @return $this
- */
- public function withBody($body)
- {
- $this->_body = (string)$body;
- return $this;
- }
- /**
- * Get http raw body.
- *
- * @return string
- */
- public function rawBody()
- {
- return $this->_body;
- }
- /**
- * Send file.
- *
- * @param string $file
- * @param int $offset
- * @param int $length
- * @return $this
- */
- public function withFile($file, $offset = 0, $length = 0)
- {
- if (!\is_file($file)) {
- return $this->withStatus(404)->withBody('<h3>404 Not Found</h3>');
- }
- $this->file = ['file' => $file, 'offset' => $offset, 'length' => $length];
- return $this;
- }
- /**
- * Set cookie.
- *
- * @param $name
- * @param string $value
- * @param int $max_age
- * @param string $path
- * @param string $domain
- * @param bool $secure
- * @param bool $http_only
- * @param bool $same_site
- * @return $this
- */
- public function cookie($name, $value = '', $max_age = null, $path = '', $domain = '', $secure = false, $http_only = false, $same_site = false)
- {
- $this->_header['Set-Cookie'][] = $name . '=' . \rawurlencode($value)
- . (empty($domain) ? '' : '; Domain=' . $domain)
- . ($max_age === null ? '' : '; Max-Age=' . $max_age)
- . (empty($path) ? '' : '; Path=' . $path)
- . (!$secure ? '' : '; Secure')
- . (!$http_only ? '' : '; HttpOnly')
- . (empty($same_site) ? '' : '; SameSite=' . $same_site);
- return $this;
- }
- /**
- * Create header for file.
- *
- * @param array $file_info
- * @return string
- */
- protected function createHeadForFile($file_info)
- {
- $file = $file_info['file'];
- $reason = $this->_reason ?: self::PHRASES[$this->_status];
- $head = "HTTP/{$this->_version} {$this->_status} $reason\r\n";
- $headers = $this->_header;
- if (!isset($headers['Server'])) {
- $head .= "Server: workerman\r\n";
- }
- foreach ($headers as $name => $value) {
- if (\is_array($value)) {
- foreach ($value as $item) {
- $head .= "$name: $item\r\n";
- }
- continue;
- }
- $head .= "$name: $value\r\n";
- }
- if (!isset($headers['Connection'])) {
- $head .= "Connection: keep-alive\r\n";
- }
- $file_info = \pathinfo($file);
- $extension = $file_info['extension'] ?? '';
- $base_name = $file_info['basename'] ?? 'unknown';
- if (!isset($headers['Content-Type'])) {
- if (isset(self::$_mimeTypeMap[$extension])) {
- $head .= "Content-Type: " . self::$_mimeTypeMap[$extension] . "\r\n";
- } else {
- $head .= "Content-Type: application/octet-stream\r\n";
- }
- }
- if (!isset($headers['Content-Disposition']) && !isset(self::$_mimeTypeMap[$extension])) {
- $head .= "Content-Disposition: attachment; filename=\"$base_name\"\r\n";
- }
- if (!isset($headers['Last-Modified'])) {
- if ($mtime = \filemtime($file)) {
- $head .= 'Last-Modified: ' . \gmdate('D, d M Y H:i:s', $mtime) . ' GMT' . "\r\n";
- }
- }
- return "{$head}\r\n";
- }
- /**
- * __toString.
- *
- * @return string
- */
- public function __toString()
- {
- if (isset($this->file)) {
- return $this->createHeadForFile($this->file);
- }
- $reason = $this->_reason ?: self::PHRASES[$this->_status] ?? '';
- $body_len = \strlen($this->_body);
- if (empty($this->_header)) {
- return "HTTP/{$this->_version} {$this->_status} $reason\r\nServer: workerman\r\nContent-Type: text/html;charset=utf-8\r\nContent-Length: $body_len\r\nConnection: keep-alive\r\n\r\n{$this->_body}";
- }
- $head = "HTTP/{$this->_version} {$this->_status} $reason\r\n";
- $headers = $this->_header;
- if (!isset($headers['Server'])) {
- $head .= "Server: workerman\r\n";
- }
- foreach ($headers as $name => $value) {
- if (\is_array($value)) {
- foreach ($value as $item) {
- $head .= "$name: $item\r\n";
- }
- continue;
- }
- $head .= "$name: $value\r\n";
- }
- if (!isset($headers['Connection'])) {
- $head .= "Connection: keep-alive\r\n";
- }
- if (!isset($headers['Content-Type'])) {
- $head .= "Content-Type: text/html;charset=utf-8\r\n";
- } else if ($headers['Content-Type'] === 'text/event-stream') {
- return $head . $this->_body;
- }
- if (!isset($headers['Transfer-Encoding'])) {
- $head .= "Content-Length: $body_len\r\n\r\n";
- } else {
- return "$head\r\n" . dechex($body_len) . "\r\n{$this->_body}\r\n";
- }
- // The whole http package
- return $head . $this->_body;
- }
- /**
- * Init mime map.
- *
- * @return void
- */
- public static function initMimeTypeMap()
- {
- $mime_file = __DIR__ . '/mime.types';
- $items = \file($mime_file, \FILE_IGNORE_NEW_LINES | \FILE_SKIP_EMPTY_LINES);
- foreach ($items as $content) {
- if (\preg_match("/\s*(\S+)\s+(\S.+)/", $content, $match)) {
- $mime_type = $match[1];
- $extension_var = $match[2];
- $extension_array = \explode(' ', \substr($extension_var, 0, -1));
- foreach ($extension_array as $file_extension) {
- static::$_mimeTypeMap[$file_extension] = $mime_type;
- }
- }
- }
- }
- }
- Response::init();
|