Socket.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  1. <?php
  2. /**
  3. * Copyright © Magento, Inc. All rights reserved.
  4. * See COPYING.txt for license details.
  5. */
  6. /**
  7. * Class to work with HTTP protocol using sockets
  8. *
  9. * @author Magento Core Team <core@magentocommerce.com>
  10. */
  11. namespace Magento\Framework\HTTP\Client;
  12. /**
  13. * Socket client
  14. *
  15. * @SuppressWarnings(PHPMD.UnusedPrivateField)
  16. */
  17. class Socket implements \Magento\Framework\HTTP\ClientInterface
  18. {
  19. /**
  20. * Hostname
  21. * @var string
  22. */
  23. private $_host = 'localhost';
  24. /**
  25. * Port
  26. * @var int
  27. */
  28. private $_port = 80;
  29. /**
  30. * Stream resource
  31. * @var object
  32. */
  33. private $_sock = null;
  34. /**
  35. * Request headers
  36. * @var array
  37. */
  38. private $_headers = [];
  39. /**
  40. * Fields for POST method - hash
  41. * @var array
  42. */
  43. private $_postFields = [];
  44. /**
  45. * Request cookies
  46. * @var array
  47. */
  48. private $_cookies = [];
  49. /**
  50. * Response headers
  51. * @var array
  52. */
  53. private $_responseHeaders = [];
  54. /**
  55. * Response body
  56. * @var string
  57. */
  58. private $_responseBody = '';
  59. /**
  60. * Response status
  61. * @var int
  62. */
  63. private $_responseStatus = 0;
  64. /**
  65. * Request timeout
  66. * @var int
  67. */
  68. private $_timeout = 300;
  69. /**
  70. * TODO
  71. * @var int
  72. */
  73. private $_redirectCount = 0;
  74. /**
  75. * Set request timeout, msec
  76. *
  77. * @param int $value
  78. * @return void
  79. */
  80. public function setTimeout($value)
  81. {
  82. $this->_timeout = (int)$value;
  83. }
  84. /**
  85. * Constructor
  86. *
  87. * @param string $host
  88. * @param int $port
  89. */
  90. public function __construct($host = null, $port = 80)
  91. {
  92. if ($host) {
  93. $this->connect($host, (int)$port);
  94. }
  95. }
  96. /**
  97. * Set connection params
  98. *
  99. * @param string $host
  100. * @param int $port
  101. * @return void
  102. */
  103. public function connect($host, $port = 80)
  104. {
  105. $this->_host = $host;
  106. $this->_port = (int)$port;
  107. }
  108. /**
  109. * Disconnect
  110. *
  111. * @return void
  112. */
  113. public function disconnect()
  114. {
  115. @fclose($this->_sock);
  116. }
  117. /**
  118. * Set headers from hash
  119. *
  120. * @param array $headers
  121. * @return void
  122. */
  123. public function setHeaders($headers)
  124. {
  125. $this->_headers = $headers;
  126. }
  127. /**
  128. * Add header
  129. *
  130. * @param string $name name, ex. "Location"
  131. * @param string $value value ex. "http://google.com"
  132. * @return void
  133. */
  134. public function addHeader($name, $value)
  135. {
  136. $this->_headers[$name] = $value;
  137. }
  138. /**
  139. * Remove specified header
  140. *
  141. * @param string $name
  142. * @return void
  143. */
  144. public function removeHeader($name)
  145. {
  146. unset($this->_headers[$name]);
  147. }
  148. /**
  149. * Authorization: Basic header
  150. *
  151. * Login credentials support
  152. *
  153. * @param string $login username
  154. * @param string $pass password
  155. * @return void
  156. */
  157. public function setCredentials($login, $pass)
  158. {
  159. $val = base64_encode("{$login}:{$pass}");
  160. $this->addHeader("Authorization", "Basic {$val}");
  161. }
  162. /**
  163. * Add cookie
  164. *
  165. * @param string $name
  166. * @param string $value
  167. * @return void
  168. */
  169. public function addCookie($name, $value)
  170. {
  171. $this->_cookies[$name] = $value;
  172. }
  173. /**
  174. * Remove cookie
  175. *
  176. * @param string $name
  177. * @return void
  178. */
  179. public function removeCookie($name)
  180. {
  181. unset($this->_cookies[$name]);
  182. }
  183. /**
  184. * Set cookies array
  185. *
  186. * @param array $cookies
  187. * @return void
  188. */
  189. public function setCookies($cookies)
  190. {
  191. $this->_cookies = $cookies;
  192. }
  193. /**
  194. * Clear cookies
  195. *
  196. * @return void
  197. */
  198. public function removeCookies()
  199. {
  200. $this->setCookies([]);
  201. }
  202. /**
  203. * Make GET request
  204. *
  205. * @param string $uri full uri path
  206. * @return void
  207. */
  208. public function get($uri)
  209. {
  210. $this->makeRequest("GET", $this->parseUrl($uri));
  211. }
  212. /**
  213. * Set host, port from full url and return relative url
  214. *
  215. * @param string $uri ex. http://google.com/index.php?a=b
  216. * @return string ex. /index.php?a=b
  217. * @throws \InvalidArgumentException
  218. */
  219. protected function parseUrl($uri)
  220. {
  221. $parts = parse_url($uri);
  222. if (!empty($parts['user']) && !empty($parts['pass'])) {
  223. $this->setCredentials($parts['user'], $parts['pass']);
  224. }
  225. if (!empty($parts['port'])) {
  226. $this->_port = (int)$parts['port'];
  227. }
  228. if (!empty($parts['host'])) {
  229. $this->_host = $parts['host'];
  230. } else {
  231. throw new \InvalidArgumentException("Uri doesn't contain host part");
  232. }
  233. if (!empty($parts['path'])) {
  234. $requestUri = $parts['path'];
  235. } else {
  236. throw new \InvalidArgumentException("Uri doesn't contain path part");
  237. }
  238. if (!empty($parts['query'])) {
  239. $requestUri .= "?" . $parts['query'];
  240. }
  241. return $requestUri;
  242. }
  243. /**
  244. * Make POST request
  245. *
  246. * @param string $uri
  247. * @param array|string $params use string in case of JSON or XML POST request
  248. * @return void
  249. */
  250. public function post($uri, $params)
  251. {
  252. $this->makeRequest("POST", $this->parseUrl($uri), $params);
  253. }
  254. /**
  255. * Get response headers
  256. *
  257. * @return array
  258. */
  259. public function getHeaders()
  260. {
  261. return $this->_responseHeaders;
  262. }
  263. /**
  264. * Get response body
  265. *
  266. * @return string
  267. */
  268. public function getBody()
  269. {
  270. return $this->_responseBody;
  271. }
  272. /**
  273. * Get cookies response hash
  274. *
  275. * @return array
  276. */
  277. public function getCookies()
  278. {
  279. if (empty($this->_responseHeaders['Set-Cookie'])) {
  280. return [];
  281. }
  282. $out = [];
  283. foreach ($this->_responseHeaders['Set-Cookie'] as $row) {
  284. $values = explode("; ", $row);
  285. $c = count($values);
  286. if (!$c) {
  287. continue;
  288. }
  289. list($key, $val) = explode("=", $values[0]);
  290. if ($val === null) {
  291. continue;
  292. }
  293. $out[trim($key)] = trim($val);
  294. }
  295. return $out;
  296. }
  297. /**
  298. * Get cookies array with details
  299. * (domain, expire time etc)
  300. *
  301. * @return array
  302. */
  303. public function getCookiesFull()
  304. {
  305. if (empty($this->_responseHeaders['Set-Cookie'])) {
  306. return [];
  307. }
  308. $out = [];
  309. foreach ($this->_responseHeaders['Set-Cookie'] as $row) {
  310. $values = explode("; ", $row);
  311. $c = count($values);
  312. if (!$c) {
  313. continue;
  314. }
  315. list($key, $val) = explode("=", $values[0]);
  316. if ($val === null) {
  317. continue;
  318. }
  319. $out[trim($key)] = ['value' => trim($val)];
  320. array_shift($values);
  321. $c--;
  322. if (!$c) {
  323. continue;
  324. }
  325. for ($i = 0; $i < $c; $i++) {
  326. list($subkey, $val) = explode("=", $values[$i]);
  327. $out[trim($key)][trim($subkey)] = trim($val);
  328. }
  329. }
  330. return $out;
  331. }
  332. /**
  333. * Process response headers
  334. *
  335. * @return void
  336. */
  337. protected function processResponseHeaders()
  338. {
  339. $crlf = "\r\n";
  340. $this->_responseHeaders = [];
  341. while (!feof($this->_sock)) {
  342. $line = fgets($this->_sock, 1024);
  343. if ($line === $crlf) {
  344. return;
  345. }
  346. $name = $value = '';
  347. $out = explode(": ", trim($line), 2);
  348. if (count($out) == 2) {
  349. $name = $out[0];
  350. $value = $out[1];
  351. }
  352. if (!empty($value)) {
  353. if ($name == "Set-Cookie") {
  354. if (!isset($this->_responseHeaders[$name])) {
  355. $this->_responseHeaders[$name] = [];
  356. }
  357. $this->_responseHeaders[$name][] = $value;
  358. } else {
  359. $this->_responseHeaders[$name] = $value;
  360. }
  361. }
  362. }
  363. }
  364. /**
  365. * Process response body
  366. *
  367. * @return void
  368. */
  369. protected function processResponseBody()
  370. {
  371. $this->_responseBody = '';
  372. while (!feof($this->_sock)) {
  373. $this->_responseBody .= @fread($this->_sock, 1024);
  374. }
  375. }
  376. /**
  377. * Process response
  378. *
  379. * @return void
  380. * @SuppressWarnings(PHPMD.UnusedLocalVariable)
  381. */
  382. protected function processResponse()
  383. {
  384. $response = '';
  385. $responseLine = trim(fgets($this->_sock, 1024));
  386. $line = explode(" ", $responseLine, 3);
  387. if (count($line) != 3) {
  388. return $this->doError("Invalid response line returned from server: " . $responseLine);
  389. }
  390. $this->_responseStatus = (int)$line[1];
  391. $this->processResponseHeaders();
  392. $this->processRedirect();
  393. $this->processResponseBody();
  394. }
  395. /**
  396. * Process redirect
  397. *
  398. * @return void
  399. */
  400. protected function processRedirect()
  401. {
  402. // TODO: implement redirects support
  403. }
  404. /**
  405. * Get response status code
  406. *
  407. * @see \Magento\Framework\HTTP\Client#getStatus()
  408. *
  409. * @return int
  410. */
  411. public function getStatus()
  412. {
  413. return $this->_responseStatus;
  414. }
  415. /**
  416. * Make request
  417. *
  418. * @param string $method
  419. * @param string $uri
  420. * @param array|string $params use string in case of JSON or XML POST request
  421. * @return void
  422. * @throws \Exception
  423. */
  424. protected function makeRequest($method, $uri, $params = [])
  425. {
  426. $errno = $errstr = '';
  427. $this->_sock = @fsockopen($this->_host, $this->_port, $errno, $errstr, $this->_timeout);
  428. if (!$this->_sock) {
  429. return $this->doError(sprintf("[errno: %d] %s", $errno, $errstr));
  430. }
  431. $crlf = "\r\n";
  432. $isPost = $method == "POST";
  433. $appendHeaders = [];
  434. $paramsStr = false;
  435. if ($isPost && $params) {
  436. $paramsStr = is_array($params) ? http_build_query($params) : $params;
  437. $appendHeaders['Content-type'] = 'application/x-www-form-urlencoded';
  438. $appendHeaders['Content-length'] = strlen($paramsStr);
  439. }
  440. $out = "{$method} {$uri} HTTP/1.1{$crlf}";
  441. $out .= $this->headersToString($appendHeaders);
  442. $out .= $crlf;
  443. if ($paramsStr) {
  444. $out .= $paramsStr . $crlf;
  445. }
  446. fwrite($this->_sock, $out);
  447. $this->processResponse();
  448. }
  449. /**
  450. * Throw error exception
  451. *
  452. * @param string $string
  453. * @return void
  454. * @throws \Exception
  455. */
  456. public function doError($string)
  457. {
  458. throw new \Exception($string);
  459. }
  460. /**
  461. * Convert headers hash to string
  462. *
  463. * @param array $append
  464. * @return string
  465. */
  466. protected function headersToString($append = [])
  467. {
  468. $headers = [];
  469. $headers["Host"] = $this->_host;
  470. $headers['Connection'] = "close";
  471. $headers = array_merge($headers, $this->_headers, $append);
  472. $str = [];
  473. foreach ($headers as $k => $v) {
  474. $str[] = "{$k}: {$v}\r\n";
  475. }
  476. return implode($str);
  477. }
  478. /**
  479. * TODO
  480. *
  481. * @param array $arr
  482. * @return void
  483. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  484. */
  485. public function setOptions($arr)
  486. {
  487. // Stub
  488. }
  489. /**
  490. * TODO
  491. *
  492. * @param string $name
  493. * @param string $value
  494. * @return void
  495. * @SuppressWarnings(PHPMD.UnusedFormalParameter)
  496. */
  497. public function setOption($name, $value)
  498. {
  499. // Stub
  500. }
  501. }