Http.php 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  1. <?php
  2. /**
  3. * This file is part of workerman.
  4. *
  5. * Licensed under The MIT License
  6. * For full copyright and license information, please see the MIT-LICENSE.txt
  7. * Redistributions of files must retain the above copyright notice.
  8. *
  9. * @author walkor<walkor@workerman.net>
  10. * @copyright walkor<walkor@workerman.net>
  11. * @link http://www.workerman.net/
  12. * @license http://www.opensource.org/licenses/mit-license.php MIT License
  13. */
  14. namespace Workerman\Protocols;
  15. use Workerman\Connection\TcpConnection;
  16. use Workerman\Protocols\Http\Request;
  17. use Workerman\Protocols\Http\Response;
  18. /**
  19. * Class Http.
  20. * @package Workerman\Protocols
  21. */
  22. class Http
  23. {
  24. /**
  25. * Request class name.
  26. *
  27. * @var string
  28. */
  29. protected static $_requestClass = Request::class;
  30. /**
  31. * Upload tmp dir.
  32. *
  33. * @var string
  34. */
  35. protected static $_uploadTmpDir = '';
  36. /**
  37. * Cache.
  38. *
  39. * @var bool.
  40. */
  41. protected static $_enableCache = true;
  42. /**
  43. * Get or set the request class name.
  44. *
  45. * @param string|null $class_name
  46. * @return string
  47. */
  48. public static function requestClass($class_name = null)
  49. {
  50. if ($class_name) {
  51. static::$_requestClass = $class_name;
  52. }
  53. return static::$_requestClass;
  54. }
  55. /**
  56. * Enable or disable Cache.
  57. *
  58. * @param mixed $value
  59. */
  60. public static function enableCache($value)
  61. {
  62. static::$_enableCache = (bool)$value;
  63. }
  64. /**
  65. * Check the integrity of the package.
  66. *
  67. * @param string $recv_buffer
  68. * @param TcpConnection $connection
  69. * @return int
  70. */
  71. public static function input(string $recv_buffer, TcpConnection $connection)
  72. {
  73. static $input = [];
  74. if (!isset($recv_buffer[512]) && isset($input[$recv_buffer])) {
  75. return $input[$recv_buffer];
  76. }
  77. $crlf_pos = \strpos($recv_buffer, "\r\n\r\n");
  78. if (false === $crlf_pos) {
  79. // Judge whether the package length exceeds the limit.
  80. if (\strlen($recv_buffer) >= 16384) {
  81. $connection->close("HTTP/1.1 413 Request Entity Too Large\r\n\r\n", true);
  82. return 0;
  83. }
  84. return 0;
  85. }
  86. $length = $crlf_pos + 4;
  87. $firstLine = \explode(" ", \strstr($recv_buffer, "\r\n", true), 3);
  88. if (!\in_array($firstLine[0], ['GET', 'POST', 'OPTIONS', 'HEAD', 'DELETE', 'PUT', 'PATCH'])) {
  89. $connection->close("HTTP/1.1 400 Bad Request\r\n\r\n", true);
  90. return 0;
  91. }
  92. $header = \substr($recv_buffer, 0, $crlf_pos);
  93. $hostHeaderPosition = \strpos($header, "\r\nHost: ");
  94. if (false === $hostHeaderPosition && $firstLine[2] !== "HTTP/1.1") {
  95. $connection->close("HTTP/1.1 400 Bad Request\r\n\r\n", true);
  96. return 0;
  97. }
  98. if ($pos = \strpos($header, "\r\nContent-Length: ")) {
  99. $length = $length + (int)\substr($header, $pos + 18, 10);
  100. $has_content_length = true;
  101. } else if (\preg_match("/\r\ncontent-length: ?(\d+)/i", $header, $match)) {
  102. $length = $length + $match[1];
  103. $has_content_length = true;
  104. } else {
  105. $has_content_length = false;
  106. if (false !== stripos($header, "\r\nTransfer-Encoding:")) {
  107. $connection->close("HTTP/1.1 400 Bad Request\r\n\r\n", true);
  108. return 0;
  109. }
  110. }
  111. if ($has_content_length) {
  112. if ($length > $connection->maxPackageSize) {
  113. $connection->close("HTTP/1.1 413 Request Entity Too Large\r\n\r\n", true);
  114. return 0;
  115. }
  116. }
  117. if (!isset($recv_buffer[512])) {
  118. $input[$recv_buffer] = $length;
  119. if (\count($input) > 512) {
  120. unset($input[key($input)]);
  121. }
  122. }
  123. return $length;
  124. }
  125. /**
  126. * Http decode.
  127. *
  128. * @param string $recv_buffer
  129. * @param TcpConnection $connection
  130. * @return \Workerman\Protocols\Http\Request
  131. */
  132. public static function decode($recv_buffer, TcpConnection $connection)
  133. {
  134. static $requests = [];
  135. $cacheable = static::$_enableCache && !isset($recv_buffer[512]);
  136. if (true === $cacheable && isset($requests[$recv_buffer])) {
  137. $request = clone $requests[$recv_buffer];
  138. $request->connection = $connection;
  139. $connection->__request = $request;
  140. $request->properties = [];
  141. return $request;
  142. }
  143. $request = new static::$_requestClass($recv_buffer);
  144. $request->connection = $connection;
  145. $connection->__request = $request;
  146. if (true === $cacheable) {
  147. $requests[$recv_buffer] = $request;
  148. if (\count($requests) > 512) {
  149. unset($requests[\key($requests)]);
  150. }
  151. }
  152. return $request;
  153. }
  154. /**
  155. * Http encode.
  156. *
  157. * @param string|Response $response
  158. * @param TcpConnection $connection
  159. * @return string
  160. */
  161. public static function encode($response, TcpConnection $connection)
  162. {
  163. if (isset($connection->__request)) {
  164. $connection->__request->session = null;
  165. $connection->__request->connection = null;
  166. $connection->__request = null;
  167. }
  168. if (!\is_object($response)) {
  169. $ext_header = '';
  170. if (isset($connection->__header)) {
  171. foreach ($connection->__header as $name => $value) {
  172. if (\is_array($value)) {
  173. foreach ($value as $item) {
  174. $ext_header = "$name: $item\r\n";
  175. }
  176. } else {
  177. $ext_header = "$name: $value\r\n";
  178. }
  179. }
  180. unset($connection->__header);
  181. }
  182. $body_len = \strlen((string)$response);
  183. return "HTTP/1.1 200 OK\r\nServer: workerman\r\n{$ext_header}Connection: keep-alive\r\nContent-Type: text/html;charset=utf-8\r\nContent-Length: $body_len\r\n\r\n$response";
  184. }
  185. if (isset($connection->__header)) {
  186. $response->withHeaders($connection->__header);
  187. unset($connection->__header);
  188. }
  189. if (isset($response->file)) {
  190. $file = $response->file['file'];
  191. $offset = $response->file['offset'];
  192. $length = $response->file['length'];
  193. \clearstatcache();
  194. $file_size = (int)\filesize($file);
  195. $body_len = $length > 0 ? $length : $file_size - $offset;
  196. $response->withHeaders([
  197. 'Content-Length' => $body_len,
  198. 'Accept-Ranges' => 'bytes',
  199. ]);
  200. if ($offset || $length) {
  201. $offset_end = $offset + $body_len - 1;
  202. $response->header('Content-Range', "bytes $offset-$offset_end/$file_size");
  203. }
  204. if ($body_len < 2 * 1024 * 1024) {
  205. $connection->send((string)$response . file_get_contents($file, false, null, $offset, $body_len), true);
  206. return '';
  207. }
  208. $handler = \fopen($file, 'r');
  209. if (false === $handler) {
  210. $connection->close(new Response(403, null, '403 Forbidden'));
  211. return '';
  212. }
  213. $connection->send((string)$response, true);
  214. static::sendStream($connection, $handler, $offset, $length);
  215. return '';
  216. }
  217. return (string)$response;
  218. }
  219. /**
  220. * Send remainder of a stream to client.
  221. *
  222. * @param TcpConnection $connection
  223. * @param resource $handler
  224. * @param int $offset
  225. * @param int $length
  226. */
  227. protected static function sendStream(TcpConnection $connection, $handler, $offset = 0, $length = 0)
  228. {
  229. $connection->bufferFull = false;
  230. if ($offset !== 0) {
  231. \fseek($handler, $offset);
  232. }
  233. $offset_end = $offset + $length;
  234. // Read file content from disk piece by piece and send to client.
  235. $do_write = function () use ($connection, $handler, $length, $offset_end) {
  236. // Send buffer not full.
  237. while ($connection->bufferFull === false) {
  238. // Read from disk.
  239. $size = 1024 * 1024;
  240. if ($length !== 0) {
  241. $tell = \ftell($handler);
  242. $remain_size = $offset_end - $tell;
  243. if ($remain_size <= 0) {
  244. fclose($handler);
  245. $connection->onBufferDrain = null;
  246. return;
  247. }
  248. $size = $remain_size > $size ? $size : $remain_size;
  249. }
  250. $buffer = \fread($handler, $size);
  251. // Read eof.
  252. if ($buffer === '' || $buffer === false) {
  253. fclose($handler);
  254. $connection->onBufferDrain = null;
  255. return;
  256. }
  257. $connection->send($buffer, true);
  258. }
  259. };
  260. // Send buffer full.
  261. $connection->onBufferFull = function ($connection) {
  262. $connection->bufferFull = true;
  263. };
  264. // Send buffer drain.
  265. $connection->onBufferDrain = function ($connection) use ($do_write) {
  266. $connection->bufferFull = false;
  267. $do_write();
  268. };
  269. $do_write();
  270. }
  271. /**
  272. * Set or get uploadTmpDir.
  273. *
  274. * @return bool|string
  275. */
  276. public static function uploadTmpDir($dir = null)
  277. {
  278. if (null !== $dir) {
  279. static::$_uploadTmpDir = $dir;
  280. }
  281. if (static::$_uploadTmpDir === '') {
  282. if ($upload_tmp_dir = \ini_get('upload_tmp_dir')) {
  283. static::$_uploadTmpDir = $upload_tmp_dir;
  284. } else if ($upload_tmp_dir = \sys_get_temp_dir()) {
  285. static::$_uploadTmpDir = $upload_tmp_dir;
  286. }
  287. }
  288. return static::$_uploadTmpDir;
  289. }
  290. }