Request.php 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329
  1. <?php
  2. /**
  3. * A parallel HTTP client written in pure PHP
  4. *
  5. * @author hightman <hightman@twomice.net>
  6. * @link http://hightman.cn
  7. * @copyright Copyright (c) 2015 Twomice Studio.
  8. */
  9. namespace hightman\http;
  10. /**
  11. * Http request
  12. *
  13. * @author hightman
  14. * @since 1.0
  15. */
  16. class Request
  17. {
  18. use HeaderTrait;
  19. private $_url, $_urlParams, $_rawUrl, $_body;
  20. private $_method = 'GET';
  21. private $_maxRedirect = 5;
  22. private $_postFields = [];
  23. private $_postFiles = [];
  24. private static $_dns = [];
  25. private static $_mimes = [
  26. 'gif' => 'image/gif', 'png' => 'image/png', 'bmp' => 'image/bmp',
  27. 'jpeg' => 'image/jpeg', 'pjpg' => 'image/pjpg', 'jpg' => 'image/jpeg',
  28. 'tif' => 'image/tiff', 'htm' => 'text/html', 'css' => 'text/css',
  29. 'html' => 'text/html', 'txt' => 'text/plain', 'gz' => 'application/x-gzip',
  30. 'tgz' => 'application/x-gzip', 'tar' => 'application/x-tar',
  31. 'zip' => 'application/zip', 'hqx' => 'application/mac-binhex40',
  32. 'doc' => 'application/msword', 'pdf' => 'application/pdf',
  33. 'ps' => 'application/postcript', 'rtf' => 'application/rtf',
  34. 'dvi' => 'application/x-dvi', 'latex' => 'application/x-latex',
  35. 'swf' => 'application/x-shockwave-flash', 'tex' => 'application/x-tex',
  36. 'mid' => 'audio/midi', 'au' => 'audio/basic', 'mp3' => 'audio/mpeg',
  37. 'ram' => 'audio/x-pn-realaudio', 'ra' => 'audio/x-realaudio',
  38. 'rm' => 'audio/x-pn-realaudio', 'wav' => 'audio/x-wav', 'wma' => 'audio/x-ms-media',
  39. 'wmv' => 'video/x-ms-media', 'mpg' => 'video/mpeg', 'mpga' => 'video/mpeg',
  40. 'wrl' => 'model/vrml', 'mov' => 'video/quicktime', 'avi' => 'video/x-msvideo',
  41. ];
  42. /**
  43. * Constructor
  44. * @param string $url the request URL.
  45. * @param string $method the request method.
  46. */
  47. public function __construct($url = null, $method = null)
  48. {
  49. if ($url !== null) {
  50. $this->setUrl($url);
  51. }
  52. if ($method !== null) {
  53. $this->setMethod($method);
  54. }
  55. }
  56. /**
  57. * Convert to string
  58. * @return string url
  59. */
  60. public function __toString()
  61. {
  62. return $this->getUrl();
  63. }
  64. /**
  65. * Get max redirects
  66. * @return integer the max redirects.
  67. */
  68. public function getMaxRedirect()
  69. {
  70. return $this->_maxRedirect;
  71. }
  72. /**
  73. * Set max redirects
  74. * @param integer $num max redirects to be set.
  75. */
  76. public function setMaxRedirect($num)
  77. {
  78. $this->_maxRedirect = intval($num);
  79. }
  80. /**
  81. * @return string raw url
  82. */
  83. public function getRawUrl()
  84. {
  85. return $this->_rawUrl;
  86. }
  87. /**
  88. * Get request URL
  89. * @return string request url after handling
  90. */
  91. public function getUrl()
  92. {
  93. return $this->_url;
  94. }
  95. /**
  96. * Set request URL
  97. * Relative url will be converted to full url by adding host and protocol.
  98. * @param string $url raw url
  99. */
  100. public function setUrl($url)
  101. {
  102. $this->_rawUrl = $url;
  103. if (strncasecmp($url, 'http://', 7) && strncasecmp($url, 'https://', 8) && isset($_SERVER['HTTP_HOST'])) {
  104. if (substr($url, 0, 1) != '/') {
  105. $url = substr($_SERVER['SCRIPT_NAME'], 0, strrpos($_SERVER['SCRIPT_NAME'], '/') + 1) . $url;
  106. }
  107. $url = 'http://' . $_SERVER['HTTP_HOST'] . $url;
  108. }
  109. $this->_url = str_replace('&amp;', '&', $url);
  110. $this->_urlParams = null;
  111. }
  112. /**
  113. * Get url parameters
  114. * @return array the parameters parsed from URL, or false on error
  115. */
  116. public function getUrlParams()
  117. {
  118. if ($this->_urlParams === null) {
  119. $pa = @parse_url($this->getUrl());
  120. $pa['scheme'] = isset($pa['scheme']) ? strtolower($pa['scheme']) : 'http';
  121. if ($pa['scheme'] !== 'http' && $pa['scheme'] !== 'https') {
  122. return false;
  123. }
  124. if (!isset($pa['host'])) {
  125. return false;
  126. }
  127. if (!isset($pa['path'])) {
  128. $pa['path'] = '/';
  129. }
  130. // basic auth
  131. if (isset($pa['user']) && isset($pa['pass'])) {
  132. $this->applyBasicAuth($pa['user'], $pa['pass']);
  133. }
  134. // convert host to IP address
  135. $port = isset($pa['port']) ? intval($pa['port']) : ($pa['scheme'] === 'https' ? 443 : 80);
  136. $pa['ip'] = $this->hasHeader('x-server-ip') ?
  137. $this->getHeader('x-server-ip') : self::getIp($pa['host']);
  138. $pa['conn'] = ($pa['scheme'] === 'https' ? 'ssl' : 'tcp') . '://' . $pa['ip'] . ':' . $port;
  139. // host header
  140. if (!$this->hasHeader('host')) {
  141. $this->setHeader('host', strtolower($pa['host']));
  142. } else {
  143. $pa['host'] = $this->getHeader('host');
  144. }
  145. $this->_urlParams = $pa;
  146. }
  147. return $this->_urlParams;
  148. }
  149. /**
  150. * Get url parameter by key
  151. * @param string $key parameter name
  152. * @return string the parameter value or null if non-exists.
  153. */
  154. public function getUrlParam($key)
  155. {
  156. $pa = $this->getUrlParams();
  157. return isset($pa[$key]) ? $pa[$key] : null;
  158. }
  159. /**
  160. * Get http request method
  161. * @return string the request method
  162. */
  163. public function getMethod()
  164. {
  165. return $this->_method;
  166. }
  167. /**
  168. * Set http request method
  169. * @param string $method request method
  170. */
  171. public function setMethod($method)
  172. {
  173. $this->_method = strtoupper($method);
  174. }
  175. /**
  176. * Get http request body
  177. * Appending post fields and files.
  178. * @return string request body
  179. */
  180. public function getBody()
  181. {
  182. $body = '';
  183. if ($this->_method === 'POST' || $this->_method === 'PUT') {
  184. if ($this->_body === null) {
  185. $this->_body = $this->getPostBody();
  186. }
  187. $this->setHeader('content-length', strlen($this->_body));
  188. $body = $this->_body . Client::CRLF;
  189. }
  190. return $body;
  191. }
  192. /**
  193. * Set http request body
  194. * @param string $body content string.
  195. */
  196. public function setBody($body)
  197. {
  198. $this->_body = $body;
  199. $this->setHeader('content-length', $body === null ? null : strlen($body));
  200. }
  201. /**
  202. * Set http request body as Json
  203. * @param mixed $data json data
  204. */
  205. public function setJsonBody($data)
  206. {
  207. $body = json_encode($data, JSON_UNESCAPED_UNICODE);
  208. $this->setHeader('content-type', 'application/json');
  209. $this->setBody($body);
  210. }
  211. /**
  212. * Add field for the request use POST
  213. * @param string $key field name.
  214. * @param mixed $value field value, array supported.
  215. */
  216. public function addPostField($key, $value)
  217. {
  218. $this->setMethod('POST');
  219. $this->setBody(null);
  220. if (!is_array($value)) {
  221. $this->_postFields[$key] = strval($value);
  222. } else {
  223. $value = $this->formatArrayField($value);
  224. foreach ($value as $k => $v) {
  225. $k = $key . '[' . $k . ']';
  226. $this->_postFields[$k] = $v;
  227. }
  228. }
  229. }
  230. /**
  231. * Add file to be uploaded for request use POST
  232. * @param string $key field name.
  233. * @param string $file file path to be uploaded.
  234. * @param string $content file content, default to null and read from file.
  235. */
  236. public function addPostFile($key, $file, $content = null)
  237. {
  238. $this->setMethod('POST');
  239. $this->setBody(null);
  240. if ($content === null && is_file($file)) {
  241. $content = @file_get_contents($file);
  242. }
  243. $this->_postFiles[$key] = [basename($file), $content];
  244. }
  245. /**
  246. * Combine request body from post fields & files
  247. * @return string request body content
  248. */
  249. protected function getPostBody()
  250. {
  251. $data = '';
  252. if (count($this->_postFiles) > 0) {
  253. $boundary = md5($this->_rawUrl . microtime());
  254. foreach ($this->_postFields as $k => $v) {
  255. $data .= '--' . $boundary . Client::CRLF . 'Content-Disposition: form-data; name="' . $k . '"'
  256. . Client::CRLF . Client::CRLF . $v . Client::CRLF;
  257. }
  258. foreach ($this->_postFiles as $k => $v) {
  259. $ext = strtolower(substr($v[0], strrpos($v[0], '.') + 1));
  260. $type = isset(self::$_mimes[$ext]) ? self::$_mimes[$ext] : 'application/octet-stream';
  261. $data .= '--' . $boundary . Client::CRLF . 'Content-Disposition: form-data; name="' . $k . '"; filename="' . $v[0] . '"'
  262. . Client::CRLF . 'Content-Type: ' . $type . Client::CRLF . 'Content-Transfer-Encoding: binary'
  263. . Client::CRLF . Client::CRLF . $v[1] . Client::CRLF;
  264. }
  265. $data .= '--' . $boundary . '--' . Client::CRLF;
  266. $this->setHeader('content-type', 'multipart/form-data; boundary=' . $boundary);
  267. } else {
  268. if (count($this->_postFields) > 0) {
  269. foreach ($this->_postFields as $k => $v) {
  270. $data .= '&' . rawurlencode($k) . '=' . rawurlencode($v);
  271. }
  272. $data = substr($data, 1);
  273. $this->setHeader('content-type', 'application/x-www-form-urlencoded');
  274. }
  275. }
  276. return $data;
  277. }
  278. // get ip address
  279. protected static function getIp($host)
  280. {
  281. if (!isset(self::$_dns[$host])) {
  282. self::$_dns[$host] = gethostbyname($host);
  283. }
  284. return self::$_dns[$host];
  285. }
  286. // format array field (convert N-DIM(n>=2) array => 2-DIM array)
  287. private function formatArrayField($arr, $pk = null)
  288. {
  289. $ret = [];
  290. foreach ($arr as $k => $v) {
  291. if ($pk !== null) {
  292. $k = $pk . $k;
  293. }
  294. if (is_array($v)) {
  295. $ret = array_merge($ret, $this->formatArrayField($v, $k . ']['));
  296. } else {
  297. $ret[$k] = $v;
  298. }
  299. }
  300. return $ret;
  301. }
  302. // apply basic auth
  303. private function applyBasicAuth($user, $pass)
  304. {
  305. $this->setHeader('authorization', 'Basic ' . base64_encode($user . ':' . $pass));
  306. }
  307. }