Http.php 10 KB

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