You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

352 lines
10 KiB

  1. <?php
  2. /**
  3. * *************************************************************************
  4. *
  5. * Copyright (c) 2014 Baidu.com, Inc. All Rights Reserved
  6. *
  7. * ************************************************************************
  8. */
  9. /**
  10. *
  11. * Packing the cUrl and make it easy
  12. *
  13. * @file HttpRequest.php
  14. * @encoding UTF-8
  15. *
  16. *
  17. * @date 2014年12月25日
  18. *
  19. */
  20. require_once(PUSH_SDK_HOME.'/lib/PushSimpleLog.php');
  21. require_once(PUSH_SDK_HOME.'/lib/PushException.php');
  22. class HttpRequest {
  23. const HTTP_GET = 'GET';
  24. const HTTP_POST = 'POST';
  25. /**
  26. * @var PushSimpleLog
  27. */
  28. private $log = null;
  29. private $urlBase;
  30. private static $defaultOpts = array (
  31. // default use the http version 1.0 , To prevent on the MAC platform using post speed too slow
  32. CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_0,
  33. CURLOPT_FAILONERROR => false,
  34. CURLOPT_HEADER => true,
  35. CURLOPT_RETURNTRANSFER => true,
  36. CURLOPT_TIMEOUT => 10,
  37. CURLOPT_HTTPHEADER => array (
  38. // server端 ,目前唯一支持编码格式,即为utf-8;
  39. 'Content-type: application/x-www-form-urlencoded;charset=utf-8',
  40. ),
  41. );
  42. private $currentOpts = array();
  43. /**
  44. * Constructor
  45. *
  46. * @param string $urlBase
  47. * url基础路径
  48. * @param array $opts
  49. * cUrl options
  50. * @param PushSimpleLog $log
  51. * @throws Exception
  52. */
  53. function __construct($urlBase = null, $opts = array(), $log = null) {
  54. // 如果未指定, 则创建一个被disable掉的log对象;
  55. $this -> log = $log === null ? new PushSimpleLog() : $log;
  56. if (! is_callable('curl_version')) {
  57. throw new Exception("php extension [cUrl] is not exists!!");
  58. }
  59. if ($urlBase === null) {
  60. throw new HttpException('URL must be a string');
  61. }
  62. if (preg_match('/^http.*/i', $urlBase) === 0) {
  63. throw new HttpException('URL is not correct');
  64. }
  65. if (strrpos($urlBase, '/') != strlen($urlBase) - 1) {
  66. $urlBase .= '/';
  67. }
  68. $this -> urlBase = $urlBase;
  69. if(defined(CURLOPT_IPRESOLVE)){
  70. self::$defaultOpts[CURLOPT_IPRESOLVE] = CURL_IPRESOLVE_V4;
  71. }
  72. $this -> currentOpts = self::$defaultOpts;
  73. if (is_array($opts)) {
  74. foreach ( $opts as $k => $v ) {
  75. $this -> currentOpts [$k] = $v;
  76. }
  77. }
  78. }
  79. /**
  80. * 判断一个字符串是否为以http://开头
  81. *
  82. * @param string $url
  83. * @return boolean
  84. */
  85. public function isUrlFormat($url){
  86. return $this->isMatchReg('/^http:\/\/.*/i',$url);
  87. }
  88. /**
  89. * 判断一个字符串,是否符合给写的格式
  90. *
  91. * @param string $match 正则表达式格式字符串 PREG库风格
  92. * @param string $str 将验证的字符串
  93. * @return boolean
  94. */
  95. function isMatchReg($match,$str){
  96. return preg_match($match,$str) > 0;
  97. }
  98. /**
  99. * 以标准uri风格编码一个array<K,V>
  100. *
  101. * @param array<K,V> $body
  102. * @return string
  103. */
  104. private function encodePostBody($body) {
  105. if ($body === null) {
  106. return '';
  107. }
  108. if (! is_array($body)) {
  109. return urlencode(strval($body));
  110. }
  111. $result = array ();
  112. foreach ( $body as $k => $v ) {
  113. $result [ ] = urlencode($k) . '=' . urlencode($v);
  114. }
  115. return join('&', $result);
  116. }
  117. /**
  118. * 解析curl的response,并折分其中的header与body部份.
  119. * @param string $content
  120. * @return array
  121. */
  122. private function parseResponse($content) {
  123. list ( $headerStr, $body ) = explode("\r\n\r\n", $content, 2);
  124. $header = array ();
  125. $headerPart = explode("\r\n", $headerStr);
  126. list ( $httpProtocol, $statusCode, $statStr ) = explode(' ', array_shift($headerPart), 3);
  127. foreach ( $headerPart as $item ) {
  128. list ( $key, $value ) = explode(':', $item);
  129. $header [$key] = $value;
  130. }
  131. $result = array (
  132. 'protocol' => $httpProtocol,
  133. 'status' => intval($statusCode),
  134. 'statusMsg' => $statStr,
  135. 'header' => $header,
  136. 'content' => $body,
  137. );
  138. return $result;
  139. }
  140. /**
  141. * 发起http请求, 并返回响应内容
  142. *
  143. * @param [string] $path
  144. * url的拼接部份,将拼接到urlBase部份
  145. * @param [string] $method
  146. * 使用的http请求方法, 目前只支持GET和POST
  147. * @param [mixed] $payload
  148. * 请求时的附加的数据.
  149. * @param [array] $reqHeaders
  150. * 请求时附加的header信息
  151. * @param [array] $curlOpts
  152. * 每次请求时, 对curl的配置信息.
  153. * @throws HttpException 请求异常时, 抛出异常
  154. * @return array http response信息 , 具有如下结构 array(
  155. * httpProtocol:'',
  156. * status:'',
  157. * statusMsg:'',
  158. * header:array(),
  159. * content:""
  160. * )
  161. */
  162. function request($path = '', $method = "GET", $payload = "", $reqHeaders = null, $curlOpts = null) {
  163. if ($path === null) {
  164. throw new HttpException('path must be string');
  165. }
  166. // 决定访问位置
  167. $url = $this->resolve($path);
  168. $curlOptsFinal = $this -> currentOpts;
  169. if (is_array($curlOpts)) {
  170. foreach ( $curlOpts as $k => $v ) {
  171. $curlOptsFinal [$k] = $v;
  172. }
  173. }
  174. if (is_array($reqHeaders)) {
  175. if(!is_array($curlOptsFinal [CURLOPT_HTTPHEADER])){
  176. $curlOptsFinal [CURLOPT_HTTPHEADER] = array(); // 这玩意必须是个数组.不是就直接覆盖.
  177. }
  178. $curlOptsFinal [CURLOPT_HTTPHEADER] = array_merge($curlOptsFinal [CURLOPT_HTTPHEADER], $reqHeaders);
  179. //print_r($curlOptsFinal);
  180. }
  181. $port = parse_url($url, PHP_URL_PORT);
  182. $curlOptsFinal [CURLOPT_URL] = $url;
  183. $curlOptsFinal [CURLOPT_PORT] = empty($port) ? 80 : $port;
  184. $req = curl_init();
  185. curl_setopt_array($req, $curlOptsFinal);
  186. switch ($method) {
  187. case self::HTTP_GET :
  188. curl_setopt($req, CURLOPT_CUSTOMREQUEST, 'GET');
  189. break;
  190. case self::HTTP_POST :
  191. curl_setopt($req, CURLOPT_POST, true);
  192. curl_setopt($req, CURLOPT_POSTFIELDS, $payload);
  193. break;
  194. default :
  195. throw new HttpException("method not be support");
  196. }
  197. $response = curl_exec($req);
  198. $errCode = curl_errno($req);
  199. if ($errCode === 0) {
  200. curl_close($req);
  201. // log http access
  202. $response = $this -> parseResponse($response);
  203. $status = $response['status'];
  204. $this -> log -> log("HttpRequest: $status $method $url");
  205. return $response;
  206. } else {
  207. // get message and close curl resource;
  208. $errMsg = curl_error($req);
  209. curl_close($req);
  210. throw new HttpException( "curl_error($errMsg); with url($url)", $errCode);
  211. }
  212. }
  213. /**
  214. * 快速发起get请求
  215. *
  216. * @param string $path
  217. * 请求资源路径
  218. * @param array $query
  219. * 请求参数
  220. * @param array $headers
  221. * 附带header
  222. * @param array $curlOpts
  223. * 附加curlOpts
  224. *
  225. * @return array http response信息 , 具有如下结构 array(
  226. * httpProtocol:'',
  227. * status:'',
  228. * statusMsg:'',
  229. * header:array(),
  230. * content:""
  231. * )
  232. */
  233. function get($path, $query = null, $headers = null, $curlOpts = null) {
  234. $payload = $this -> encodePostBody($query);
  235. if ($payload !== "") {
  236. if (strpos($path, '?')) {
  237. $url = $path . "&" . $payload;
  238. } else {
  239. $url = $path . "?" . $payload;
  240. }
  241. }else{
  242. $url = $path;
  243. }
  244. return $this -> request($url, self::HTTP_GET, '', $headers, $curlOpts);
  245. }
  246. /**
  247. * 快速发起post请求
  248. *
  249. * @param string $path
  250. * 请求资源路径
  251. * @param array $postBody
  252. * 请求参数
  253. * @param array $headers
  254. * 附带header
  255. * @param array $curlOpts
  256. * 附加curlOpts
  257. *
  258. * @return array http response信息 , 具有如下结构 array(
  259. * httpProtocol:'',
  260. * status:'',
  261. * statusMsg:'',
  262. * header:array(),
  263. * content:""
  264. * )
  265. */
  266. function post($path = null, $postBody = null, $headers = null, $curlOpts = null) {
  267. $payload = $this -> encodePostBody($postBody);
  268. $this -> log -> debug("\n\n ====== Dump the payload before Http POST send! ====== \n\n$payload\n\n====== End Dump! ======\n");
  269. return $this -> request($path, self::HTTP_POST, $payload, $headers, $curlOpts);
  270. }
  271. /**
  272. * 根据传入内容,决定具体请求地址;
  273. * @param string $path
  274. * @return Ambigous <string, unknown>
  275. */
  276. public function resolve($path){
  277. // 如果不指定完整的url, 则认为是访问$urlBase内资源
  278. if ($this -> isUrlFormat($path)) {
  279. $url = $path;
  280. }
  281. // 忽略重复的路径分隔符
  282. else if (strpos($path, '/') === 0) {
  283. $url = $this -> urlBase . substr($path, 1);
  284. } else {
  285. $url = $this -> urlBase . $path;
  286. }
  287. return $url;
  288. }
  289. /**
  290. * 取得url中表示资源位置的部份,即去掉query_string及其后的部份
  291. *
  292. * @param string $url
  293. * @return string
  294. */
  295. public function getResourceAddress($url){
  296. $url = $this->resolve($url);
  297. $p = strpos($url,'?');
  298. $p = $p === false ? strpos($url,'#') : $p;
  299. return $p === false ? $url : substr($url, 0, $p);
  300. }
  301. }