├── src ├── ConnectionException.php ├── Facade.php ├── RequestException.php ├── Http.php ├── Response.php └── Request.php ├── composer.json ├── LICENSE └── README.md /src/ConnectionException.php: -------------------------------------------------------------------------------- 1 | facade = new $this->facade; 12 | } 13 | 14 | public function __call($name, $params) { 15 | return call_user_func_array([$this->facade, $name], $params); 16 | } 17 | 18 | public static function __callStatic($name, $params) { 19 | return call_user_func_array([new static(), $name], $params); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/RequestException.php: -------------------------------------------------------------------------------- 1 | exception = $exception; 12 | } 13 | 14 | public function getCode() 15 | { 16 | return $this->exception->getCode(); 17 | } 18 | 19 | public function getMessage() 20 | { 21 | return $this->exception->getMessage(); 22 | } 23 | 24 | public function getFile() 25 | { 26 | return $this->exception->getFile(); 27 | } 28 | 29 | public function getLine() 30 | { 31 | return $this->exception->getLine(); 32 | } 33 | 34 | public function getTrace() 35 | { 36 | return $this->exception->getTrace(); 37 | } 38 | 39 | public function getTraceAsString() 40 | { 41 | return $this->exception->getTraceAsString(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gouguoyin/easyhttp", 3 | "description": "EasyHttp 是一个轻量级、语义化、对IDE友好的HTTP客户端,支持常见的HTTP请求、异步请求和并发请求,让你可以快速地使用 HTTP 请求与其他 Web 应用进行通信。", 4 | "license": "MIT", 5 | "keywords": [ 6 | "easyhttp", 7 | "EasyHttp", 8 | "php-http", 9 | "phphttp", 10 | "easy-http", 11 | "php", 12 | "http", 13 | "curl" 14 | ], 15 | "homepage": "https://github.com/gouguoyin/easyhttp", 16 | "authors": [ 17 | { 18 | "name": "gouguoyin", 19 | "email": "245629560@qq.com" 20 | } 21 | ], 22 | "require": { 23 | "php": ">=5.5.0", 24 | "guzzlehttp/guzzle": "~6.0" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "Gouguoyin\\EasyHttp\\": "src/" 29 | } 30 | }, 31 | "minimum-stability": "stable", 32 | "repositories": [ 33 | { 34 | "type": "composer", 35 | "url": "https://mirrors.aliyun.com/composer/" 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014-present rap2hpoutre 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/Http.php: -------------------------------------------------------------------------------- 1 | response = $response; 22 | } 23 | 24 | /** 25 | * Get the body of the response. 26 | * @return string 27 | */ 28 | public function body() 29 | { 30 | return (string) $this->response->getBody(); 31 | } 32 | 33 | /** 34 | * Get the Array decoded body of the response. 35 | * @return array|mixed 36 | */ 37 | public function array() 38 | { 39 | if (!$this->decoded) { 40 | $this->decoded = json_decode((string) $this->response->getBody(), true); 41 | } 42 | 43 | return $this->decoded; 44 | } 45 | 46 | /** 47 | * Get the JSON decoded body of the response. 48 | * @return object|mixed 49 | */ 50 | public function json() 51 | { 52 | if (!$this->decoded) { 53 | $this->decoded = json_decode((string) $this->response->getBody()); 54 | } 55 | 56 | return $this->decoded; 57 | } 58 | 59 | /** 60 | * Get a header from the response. 61 | * @param string $header 62 | * @return mixed 63 | */ 64 | public function header(string $header) 65 | { 66 | return $this->response->getHeaderLine($header); 67 | } 68 | 69 | /** 70 | * Get the headers from the response. 71 | * @return mixed 72 | */ 73 | public function headers() 74 | { 75 | return $this->mapWithKeys($this->response->getHeaders(), function ($v, $k) { 76 | return [$k => $v]; 77 | })->response; 78 | } 79 | 80 | /** 81 | * Get the status code of the response. 82 | * @return int 83 | */ 84 | public function status() 85 | { 86 | return (int) $this->response->getStatusCode(); 87 | } 88 | 89 | /** 90 | * Determine if the request was successful. 91 | * @return bool 92 | */ 93 | public function successful() 94 | { 95 | return $this->status() >= 200 && $this->status() < 300; 96 | } 97 | 98 | /** 99 | * Determine if the response code was "OK". 100 | * @return bool 101 | */ 102 | public function ok() 103 | { 104 | return $this->status() === 200; 105 | } 106 | 107 | /** 108 | * Determine if the response was a redirect. 109 | * @return bool 110 | */ 111 | public function redirect() 112 | { 113 | return $this->status() >= 300 && $this->status() < 400; 114 | } 115 | 116 | /** 117 | * Determine if the response indicates a client error occurred. 118 | * @return bool 119 | */ 120 | public function clientError() 121 | { 122 | return $this->status() >= 400 && $this->status() < 500; 123 | } 124 | 125 | /** 126 | * Determine if the response indicates a server error occurred. 127 | * @return bool 128 | */ 129 | public function serverError() 130 | { 131 | return $this->status() >= 500; 132 | } 133 | 134 | /** 135 | * Determine if the given offset exists. 136 | * 137 | * @param string $offset 138 | * @return bool 139 | */ 140 | public function offsetExists($offset) 141 | { 142 | return isset($this->json()[$offset]); 143 | } 144 | 145 | /** 146 | * Get the value for a given offset. 147 | * 148 | * @param string $offset 149 | * @return mixed 150 | */ 151 | public function offsetGet($offset) 152 | { 153 | return $this->json()[$offset]; 154 | } 155 | 156 | /** 157 | * Set the value at the given offset. 158 | * 159 | * @param string $offset 160 | * @param mixed $value 161 | * @return void 162 | * 163 | * @throws \LogicException 164 | */ 165 | public function offsetSet($offset, $value) 166 | { 167 | throw new LogicException('Response data may not be mutated using array access.'); 168 | } 169 | 170 | /** 171 | * Unset the value at the given offset. 172 | * 173 | * @param string $offset 174 | * @return void 175 | * 176 | * @throws \LogicException 177 | */ 178 | public function offsetUnset($offset) 179 | { 180 | throw new LogicException('Response data may not be mutated using array access.'); 181 | } 182 | 183 | /** 184 | * Get the body of the response. 185 | * 186 | * @return string 187 | */ 188 | public function __toString() 189 | { 190 | return $this->body(); 191 | } 192 | 193 | protected function mapWithKeys($items, callable $callback) 194 | { 195 | $result = []; 196 | 197 | foreach ($items as $key => $value) { 198 | $assoc = $callback($value, $key); 199 | 200 | foreach ($assoc as $mapKey => $mapValue) { 201 | $result[$mapKey] = $mapValue; 202 | } 203 | } 204 | 205 | return new static($result); 206 | } 207 | 208 | } 209 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | EasyHttp 是一个轻量级、语义化、对IDE友好的HTTP客户端,支持常见的HTTP请求、异步请求和并发请求,让你可以快速地使用 HTTP 请求与其他 Web 应用进行通信。 2 | 3 | > EasyHttp并不强制依赖于cURL,如果没有安装cURL,EasyHttp会自动选择使用PHP流处理,或者你也可以提供自己的发送HTTP请求的处理方式。 4 | 5 | 如果您觉得EasyHttp对您有用的话,别忘了给点个赞哦^_^ ! 6 | 7 | github:[github.com/gouguoyin/easyhttp](https://github.com/gouguoyin/easyhttp "github.com/gouguoyin/easyhttp") 8 | 9 | gitee:[gitee.com/gouguoyin/easyhttp](https://gitee.com/gouguoyin/easyhttp "gitee.com/gouguoyin/easyhttp") 10 | 11 | robeeask社区:[https://easyhttp.robeeask.com/](https://easyhttp.robeeask.com/) 12 | # 安装说明 13 | 14 | #### 环境依赖 15 | 16 | - PHP >= 5.5.0 17 | - 如果使用PHP流处理,allow_url_fopen 必须在php.ini中启用。 18 | - 如果使用cURL处理,cURL >= 7.19.4,并且编译了OpenSSL 与 zlib。 19 | 20 | #### 一键安装 21 | 22 | composer require gouguoyin/easyhttp 23 | 24 | ## 发起请求 25 | 26 | #### 同步请求 27 | 28 | ###### 常规请求 29 | 30 | ```php 31 | $response = Http::get('http://httpbin.org/get'); 32 | 33 | $response = Http::get('http://httpbin.org/get?name=gouguoyin'); 34 | 35 | $response = Http::get('http://httpbin.org/get?name=gouguoyin', ['age' => 18]); 36 | 37 | $response = Http::post('http://httpbin.org/post'); 38 | 39 | $response = Http::post('http://httpbin.org/post', ['name' => 'gouguoyun']); 40 | 41 | $response = Http::patch(...); 42 | 43 | $response = Http::put(...); 44 | 45 | $response = Http::delete(...); 46 | 47 | $response = Http::head(...); 48 | 49 | $response = Http::options(...); 50 | ``` 51 | 52 | ###### 发送 Content-Type 编码请求 53 | 54 | ```php 55 | // application/x-www-form-urlencoded(默认) 56 | $response = Http::asForm()->post(...); 57 | 58 | // application/json 59 | $response = Http::asJson()->post(...); 60 | ``` 61 | 62 | ###### 发送 Multipart 表单请求 63 | 64 | ```php 65 | $response = Http::asMultipart( 66 | 'file_input_name', file_get_contents('photo1.jpg'), 'photo2.jpg' 67 | )->post('http://test.com/attachments'); 68 | 69 | $response = Http::asMultipart( 70 | 'file_input_name', fopen('photo1.jpg', 'r'), 'photo2.jpg' 71 | )->post(...); 72 | 73 | $response = Http::attach( 74 | 'file_input_name', file_get_contents('photo1.jpg'), 'photo2.jpg' 75 | )->post(...); 76 | 77 | $response = Http::attach( 78 | 'file_input_name', fopen('photo1.jpg', 'r'), 'photo2.jpg' 79 | )->post(...); 80 | ``` 81 | > 表单enctype属性需要设置成 multipart/form-data 82 | 83 | ###### 携带请求头的请求 84 | 85 | ```php 86 | $response = Http::withHeaders([ 87 | 'x-powered-by' => 'gouguoyin' 88 | ])->post(...); 89 | ``` 90 | 91 | ###### 携带重定向的请求 92 | 93 | ```php 94 | // 默认 95 | $response = Http::withRedirect(false)->post(...); 96 | 97 | $response = Http::withRedirect([ 98 | 'max' => 5, 99 | 'strict' => false, 100 | 'referer' => true, 101 | 'protocols' => ['http', 'https'], 102 | 'track_redirects' => false 103 | ])->post(...); 104 | ``` 105 | 106 | ###### 携带认证的请求 107 | 108 | ```php 109 | // Basic认证 110 | $response = Http::withBasicAuth('username', 'password')->post(...); 111 | 112 | // Digest认证(需要被HTTP服务器支持) 113 | $response = Http::withDigestAuth('username', 'password')->post(...); 114 | ``` 115 | 116 | ###### 携带 User-Agent 的请求 117 | ```php 118 | $response = Http::withUA('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3100.0 Safari/537.36')->post(...); 119 | ``` 120 | 121 | ###### 携带Token令牌的请求 122 | 123 | ```php 124 | $response = Http::withToken('token')->post(...); 125 | ``` 126 | 127 | ###### 携带认证文件的请求 128 | 129 | ```php 130 | $response = Http::withCert('/path/server.pem', 'password')->post(...); 131 | ``` 132 | 133 | ###### 携带SSL证书的请求 134 | 135 | ```php 136 | // 默认 137 | $response = Http::withVerify(false)->post(...); 138 | 139 | $response = Http::withVerify('/path/to/cert.pem')->post(...); 140 | ``` 141 | 142 | ###### 携带COOKIE的请求 143 | 144 | ```php 145 | $response = Http::withCookies(array $cookies, string $domain)->post(...); 146 | ``` 147 | 148 | ###### 携带协议版本的请求 149 | 150 | ```php 151 | $response = Http::withVersion(1.1)->post(...); 152 | ``` 153 | 154 | ###### 携带代理的请求 155 | 156 | ```php 157 | $response = Http::withProxy('tcp://localhost:8125')->post(...); 158 | 159 | $response = Http::withProxy([ 160 | 'http' => 'tcp://localhost:8125', // Use this proxy with "http" 161 | 'https' => 'tcp://localhost:9124', // Use this proxy with "https", 162 | 'no' => ['.com.cn', 'gouguoyin.cn'] // Don't use a proxy with these 163 | ])->post(...); 164 | ``` 165 | 166 | ###### 设置超时时间(单位秒) 167 | 168 | ```php 169 | $response = Http::timeout(60)->post(...); 170 | ``` 171 | 172 | ###### 设置延迟时间(单位秒) 173 | 174 | ```php 175 | $response = Http::delay(60)->post(...); 176 | ``` 177 | 178 | ###### 设置并发次数 179 | 180 | ```php 181 | $response = Http::concurrency(10)->promise(...); 182 | ``` 183 | 184 | #### 异步请求 185 | 186 | ```php 187 | use Gouguoyin\EasyHttp\Response; 188 | use Gouguoyin\EasyHttp\RequestException; 189 | 190 | Http::getAsync('http://easyhttp.gouguoyin.cn/api/sleep3.json', ['token' => TOKEN], function (Response $response) { 191 | echo '异步请求成功,响应内容:' . $response->body() . PHP_EOL; 192 | }, function (RequestException $e) { 193 | echo '异步请求异常,错误码:' . $e->getCode() . ',错误信息:' . $e->getMessage() . PHP_EOL; 194 | }); 195 | echo json_encode(['code' => 200, 'msg' => '请求成功'], JSON_UNESCAPED_UNICODE) . PHP_EOL; 196 | 197 | //输出 198 | {"code":200,"msg":"请求成功"} 199 | 异步请求成功,响应内容:{"code":200,"msg":"success","second":3} 200 | 201 | Http::getAsync('http1://easyhttp.gouguoyin.cn/api/sleep3.json', function (Response $response) { 202 | echo '异步请求成功,响应内容:' . $response->body() . PHP_EOL; 203 | }, function (RequestException $e) { 204 | echo '异步请求异常,错误信息:' . $e->getMessage() . PHP_EOL; 205 | }); 206 | echo json_encode(['code' => 200, 'msg' => '请求成功'], JSON_UNESCAPED_UNICODE) . PHP_EOL; 207 | 208 | //输出 209 | {"code":200,"msg":"请求成功"} 210 | 异步请求异常,错误信息:cURL error 1: Protocol "http1" not supported or disabled in libcurl (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) 211 | 212 | Http::postAsync(...); 213 | 214 | Http::patchAsync(...); 215 | 216 | Http::putAsync(...); 217 | 218 | Http::deleteAsync(...); 219 | 220 | Http::headAsync(...); 221 | 222 | Http::optionsAsync(...); 223 | ``` 224 | 225 | #### 异步并发请求 226 | 227 | ```php 228 | use Gouguoyin\EasyHttp\Response; 229 | use Gouguoyin\EasyHttp\RequestException; 230 | 231 | $promises = [ 232 | Http::getAsync('http://easyhttp.gouguoyin.cn/api/sleep3.json'), 233 | Http::getAsync('http1://easyhttp.gouguoyin.cn/api/sleep1.json', ['name' => 'gouguoyin']), 234 | Http::postAsync('http://easyhttp.gouguoyin.cn/api/sleep2.json', ['name' => 'gouguoyin']), 235 | ]; 236 | 237 | Http::concurrency(10)->multiAsync($promises, function (Response $response, $index) { 238 | echo "发起第 $index 个异步请求,请求时长:" . $response->json()->second . '秒' . PHP_EOL; 239 | }, function (RequestException $e, $index) { 240 | echo "发起第 $index 个请求失败,失败原因:" . $e->getMessage() . PHP_EOL; 241 | }); 242 | 243 | //输出 244 | 发起第 1 个请求失败,失败原因:cURL error 1: Protocol "http1" not supported or disabled in libcurl (see https://curl.haxx.se/libcurl/c/libcurl-errors.html) 245 | 发起第 2 个异步请求,请求时长:2 秒 246 | 发起第 0 个异步请求,请求时长:3 秒 247 | ``` 248 | > 如果未调用concurrency()方法,并发次数默认为$promises的元素个数,$promises数组里必须是异步请求 249 | 250 | ## 使用响应 251 | 252 | 发起请求后会返回一个 Gouguoyin\EasyHttp\Response $response的实例,该实例提供了以下方法来检查请求的响应: 253 | 254 | ```php 255 | $response->body() : string; 256 | $response->json() : object; 257 | $response->array() : array; 258 | $response->status() : int; 259 | $response->ok() : bool; 260 | $response->successful() : bool; 261 | $response->serverError() : bool; 262 | $response->clientError() : bool; 263 | $response->headers() : array; 264 | $response->header($header) : string; 265 | ``` 266 | 267 | ## 异常处理 268 | 269 | 请求在发生客户端或服务端错误时会抛出 Gouguoyin\EasyHttp\RequestException $e异常,该实例提供了以下方法来返回异常信息: 270 | 271 | ```php 272 | $e->getCode() : int; 273 | $e->getMessage() : string; 274 | $e->getFile() : string; 275 | $e->getLine() : int; 276 | $e->getTrace() : array; 277 | $e->getTraceAsString() : string; 278 | ``` 279 | ## 更新日志 280 | ### 2020-03-30 281 | * 修复部分情况下IDE不能智能提示的BUG 282 | * get()、getAsync()方法支持带参数的url 283 | * 新增withUA()方法 284 | * 新增withStream()方法 285 | * 新增asMultipart()方法,attach()的别名 286 | * 新增multiAsync()异步并发请求方法 287 | 288 | ### 2020-03-20 289 | * 新增异步请求getAsync()方法 290 | * 新增异步请求postAsync()方法 291 | * 新增异步请求patchAsync()方法 292 | * 新增异步请求putAsync()方法 293 | * 新增异步请求deleteAsync()方法 294 | * 新增异步请求headAsync()方法 295 | * 新增异步请求optionsAsync()方法 296 | 297 | ## Todo List 298 | - [x] 异步请求 299 | - [x] 并发请求 300 | - [ ] 重试机制 301 | - [ ] 支持http2 302 | - [ ] 支持swoole 303 | -------------------------------------------------------------------------------- /src/Request.php: -------------------------------------------------------------------------------- 1 | client = $this->getInstance(); 66 | 67 | $this->bodyFormat = 'form_params'; 68 | $this->options = [ 69 | 'http_errors' => false, 70 | ]; 71 | } 72 | 73 | /** 74 | * Request destructor. 75 | */ 76 | public function __destruct() 77 | { 78 | if (!empty($this->promises)) { 79 | Promise\settle($this->promises)->wait(); 80 | } 81 | } 82 | 83 | /** 84 | * 获取单例 85 | * @return mixed 86 | */ 87 | public function getInstance() 88 | { 89 | $name = get_called_class(); 90 | 91 | if (!isset(self::$instances[$name])) { 92 | self::$instances[$name] = new Client(); 93 | } 94 | 95 | return self::$instances[$name]; 96 | } 97 | 98 | public function asForm() 99 | { 100 | $this->bodyFormat = 'form_params'; 101 | $this->withHeaders(['Content-Type' => 'application/x-www-form-urlencoded']); 102 | 103 | return $this; 104 | } 105 | 106 | public function asJson() 107 | { 108 | $this->bodyFormat = 'json'; 109 | $this->withHeaders(['Content-Type' => 'application/json']); 110 | 111 | return $this; 112 | } 113 | 114 | public function asMultipart(string $name, string $contents, string $filename = null, array $headers = []) 115 | { 116 | $this->bodyFormat = 'multipart'; 117 | 118 | $this->options = array_filter([ 119 | 'name' => $name, 120 | 'contents' => $contents, 121 | 'headers' => $headers, 122 | 'filename' => $filename, 123 | ]); 124 | 125 | return $this; 126 | } 127 | 128 | public function withOptions(array $options) 129 | { 130 | $this->options = array_merge_recursive($this->options, $options); 131 | 132 | return $this; 133 | } 134 | 135 | public function withCert(string $path, string $password) 136 | { 137 | $this->options['cert'] = [$path, $password]; 138 | 139 | return $this; 140 | } 141 | 142 | public function withHeaders(array $headers) 143 | { 144 | $this->options = array_merge_recursive($this->options, [ 145 | 'headers' => $headers, 146 | ]); 147 | 148 | return $this; 149 | } 150 | 151 | public function withBasicAuth(string $username, string $password) 152 | { 153 | $this->options['auth'] = [$username, $password]; 154 | 155 | return $this; 156 | } 157 | 158 | public function withDigestAuth(string $username, string $password) 159 | { 160 | $this->options['auth'] = [$username, $password, 'digest']; 161 | 162 | return $this; 163 | } 164 | 165 | public function withUA(string $ua) 166 | { 167 | $this->options['headers']['User-Agent'] = trim($ua); 168 | 169 | return $this; 170 | } 171 | 172 | public function withToken(string $token, string $type = 'Bearer') 173 | { 174 | $this->options['headers']['Authorization'] = trim($type . ' ' . $token); 175 | 176 | return $this; 177 | } 178 | 179 | public function withCookies(array $cookies, string $domain) 180 | { 181 | $this->options = array_merge_recursive($this->options, [ 182 | 'cookies' => CookieJar::fromArray($cookies, $domain), 183 | ]); 184 | 185 | return $this; 186 | } 187 | 188 | public function withProxy($proxy) 189 | { 190 | $this->options['proxy'] = $proxy; 191 | 192 | return $this; 193 | } 194 | 195 | public function withVersion($version) 196 | { 197 | $this->options['version'] = $version; 198 | 199 | return $this; 200 | } 201 | 202 | public function withRedirect($redirect = false) 203 | { 204 | $this->options['allow_redirects'] = $redirect; 205 | 206 | return $this; 207 | } 208 | 209 | public function withVerify($verify = false) 210 | { 211 | $this->options['verify'] = $verify; 212 | 213 | return $this; 214 | } 215 | 216 | public function withStream($boolean = false) 217 | { 218 | $this->options['stream'] = $boolean; 219 | 220 | return $this; 221 | } 222 | 223 | public function concurrency(int $times) 224 | { 225 | $this->concurrency = $times; 226 | 227 | return $this; 228 | } 229 | 230 | public function delay(int $seconds) 231 | { 232 | $this->options['delay'] = $seconds * 1000; 233 | 234 | return $this; 235 | } 236 | 237 | public function timeout(int $seconds) 238 | { 239 | $this->options['timeout'] = $seconds * 1000; 240 | 241 | return $this; 242 | } 243 | 244 | public function attach(string $name, string $contents, string $filename = null, array $headers = []) 245 | { 246 | $this->options['multipart'] = array_filter([ 247 | 'name' => $name, 248 | 'contents' => $contents, 249 | 'headers' => $headers, 250 | 'filename' => $filename, 251 | ]); 252 | 253 | return $this; 254 | } 255 | 256 | public function get(string $url, array $query = []) 257 | { 258 | parse_str(parse_url($url, PHP_URL_QUERY), $result); 259 | 260 | $this->options['query'] = array_merge($result, $query); 261 | 262 | return $this->request('GET', $url, $query); 263 | } 264 | 265 | public function post(string $url, array $data = []) 266 | { 267 | $this->options[$this->bodyFormat] = $data; 268 | 269 | return $this->request('POST', $url, $data); 270 | } 271 | 272 | public function patch(string $url, array $data = []) 273 | { 274 | $this->options[$this->bodyFormat] = $data; 275 | 276 | return $this->request('PATCH', $url, $data); 277 | } 278 | 279 | public function put(string $url, array $data = []) 280 | { 281 | $this->options[$this->bodyFormat] = $data; 282 | 283 | return $this->request('PUT', $url, $data); 284 | } 285 | 286 | public function delete(string $url, array $data = []) 287 | { 288 | $this->options[$this->bodyFormat] = $data; 289 | 290 | return $this->request('DELETE', $url, $data); 291 | } 292 | 293 | public function head(string $url, array $data = []) 294 | { 295 | $this->options[$this->bodyFormat] = $data; 296 | 297 | return $this->request('HEAD', $url, $data); 298 | } 299 | 300 | public function options(string $url, array $data = []) 301 | { 302 | $this->options[$this->bodyFormat] = $data; 303 | 304 | return $this->request('OPTIONS', $url, $data); 305 | } 306 | 307 | public function getAsync(string $url, $query = null, callable $success = null, callable $fail = null) 308 | { 309 | is_callable($query) || $this->options['query'] = $query; 310 | 311 | return $this->requestAsync('GET', $url, $query, $success, $fail); 312 | } 313 | 314 | public function postAsync(string $url, $data = null, callable $success = null, callable $fail = null) 315 | { 316 | is_callable($data) || $this->options[$this->bodyFormat] = $data; 317 | 318 | return $this->requestAsync('POST', $url, $data, $success, $fail); 319 | } 320 | 321 | public function patchAsync(string $url, $data = null, callable $success = null, callable $fail = null) 322 | { 323 | is_callable($data) || $this->options[$this->bodyFormat] = $data; 324 | 325 | return $this->requestAsync('PATCH', $url, $data, $success, $fail); 326 | } 327 | 328 | public function putAsync(string $url, $data = null, callable $success = null, callable $fail = null) 329 | { 330 | is_callable($data) || $this->options[$this->bodyFormat] = $data; 331 | 332 | return $this->requestAsync('PUT', $url, $data, $success, $fail); 333 | } 334 | 335 | public function deleteAsync(string $url, $data = null, callable $success = null, callable $fail = null) 336 | { 337 | is_callable($data) || $this->options[$this->bodyFormat] = $data; 338 | 339 | return $this->requestAsync('DELETE', $url, $data, $success, $fail); 340 | } 341 | 342 | public function headAsync(string $url, $data = null, callable $success = null, callable $fail = null) 343 | { 344 | is_callable($data) || $this->options[$this->bodyFormat] = $data; 345 | 346 | return $this->requestAsync('HEAD', $url, $data, $success, $fail); 347 | } 348 | 349 | public function optionsAsync(string $url, $data = null, callable $success = null, callable $fail = null) 350 | { 351 | is_callable($data) || $this->options[$this->bodyFormat] = $data; 352 | 353 | return $this->requestAsync('OPTIONS', $url, $data, $success, $fail); 354 | } 355 | 356 | public function multiAsync(array $promises, callable $success = null, callable $fail = null) 357 | { 358 | $count = count($promises); 359 | 360 | $this->concurrency = $this->concurrency ? : $count; 361 | 362 | $requests = function () use ($promises) { 363 | foreach ($promises as $promise) { 364 | yield function() use ($promise) { 365 | return $promise; 366 | }; 367 | } 368 | }; 369 | 370 | $fulfilled = function ($response, $index) use ($success){ 371 | if (!is_null($success)) { 372 | $response = $this->response($response); 373 | call_user_func_array($success, [$response, $index]); 374 | } 375 | }; 376 | 377 | $rejected = function ($exception, $index) use ($fail){ 378 | if (!is_null($fail)) { 379 | $exception = $this->exception($exception); 380 | call_user_func_array($fail, [$exception, $index]); 381 | } 382 | }; 383 | 384 | $pool = new Pool($this->client, $requests(), [ 385 | 'concurrency' => $this->concurrency, 386 | 'fulfilled' => $fulfilled, 387 | 'rejected' => $rejected, 388 | ]); 389 | 390 | $pool->promise(); 391 | 392 | return $pool; 393 | } 394 | 395 | protected function request(string $method, string $url, array $options = []) 396 | { 397 | isset($this->options[$this->bodyFormat]) && $this->options[$this->bodyFormat] = $options; 398 | 399 | try { 400 | $response = $this->client->request($method, $url, $this->options); 401 | return $this->response($response); 402 | } catch (ConnectException $e) { 403 | throw new ConnectionException($e->getMessage(), 0, $e); 404 | } 405 | } 406 | 407 | protected function requestAsync(string $method, string $url, $options = null, callable $success = null, callable $fail = null) 408 | { 409 | if (is_callable($options)) { 410 | $successCallback = $options; 411 | $failCallback = $success; 412 | } else { 413 | $successCallback = $success; 414 | $failCallback = $fail; 415 | } 416 | 417 | isset($this->options[$this->bodyFormat]) && $this->options[$this->bodyFormat] = $options; 418 | 419 | try { 420 | $promise = $this->client->requestAsync($method, $url, $this->options); 421 | 422 | $fulfilled = function ($response) use ($successCallback){ 423 | if (!is_null($successCallback)) { 424 | $response = $this->response($response); 425 | call_user_func_array($successCallback, [$response]); 426 | } 427 | }; 428 | 429 | $rejected = function ($exception) use ($failCallback){ 430 | if (!is_null($failCallback)) { 431 | $exception = $this->exception($exception); 432 | call_user_func_array($failCallback, [$exception]); 433 | } 434 | }; 435 | 436 | $promise->then($fulfilled, $rejected); 437 | 438 | $this->promises[] = $promise; 439 | 440 | return $promise; 441 | 442 | } catch (ConnectException $e) { 443 | throw new ConnectionException($e->getMessage(), 0, $e); 444 | } 445 | } 446 | 447 | protected function response($response) 448 | { 449 | return new Response($response); 450 | } 451 | 452 | protected function exception($exception) 453 | { 454 | return new RequestException($exception); 455 | } 456 | } 457 | --------------------------------------------------------------------------------