Client.php
9 years ago
Exception.php
10 years ago
Result.php
10 years ago
ResultMeta.php
10 years ago
Source.php
10 years ago
Client.php
165 lines
| 1 | <?php |
| 2 | |
| 3 | namespace Tinify; |
| 4 | |
| 5 | class Client { |
| 6 | const API_ENDPOINT = "https://api.tinify.com"; |
| 7 | |
| 8 | const RETRY_COUNT = 1; |
| 9 | const RETRY_DELAY = 500; |
| 10 | |
| 11 | protected $options; |
| 12 | |
| 13 | public static function userAgent() { |
| 14 | $curl = curl_version(); |
| 15 | return "Tinify/" . VERSION . " PHP/" . PHP_VERSION . " curl/" . $curl["version"]; |
| 16 | } |
| 17 | |
| 18 | private static function caBundle() { |
| 19 | return __DIR__ . "/../data/cacert.pem"; |
| 20 | } |
| 21 | |
| 22 | function __construct($key, $appIdentifier = NULL, $proxy = NULL) { |
| 23 | $userAgent = join(" ", array_filter(array(self::userAgent(), $appIdentifier))); |
| 24 | |
| 25 | $this->options = array( |
| 26 | CURLOPT_BINARYTRANSFER => true, |
| 27 | CURLOPT_RETURNTRANSFER => true, |
| 28 | CURLOPT_HEADER => true, |
| 29 | CURLOPT_USERPWD => $key ? ("api:" . $key) : NULL, |
| 30 | CURLOPT_CAINFO => self::caBundle(), |
| 31 | CURLOPT_SSL_VERIFYPEER => true, |
| 32 | CURLOPT_USERAGENT => $userAgent, |
| 33 | ); |
| 34 | |
| 35 | if ($proxy) { |
| 36 | $parts = parse_url($proxy); |
| 37 | if (isset($parts["host"])) { |
| 38 | $this->options[CURLOPT_PROXYTYPE] = CURLPROXY_HTTP; |
| 39 | $this->options[CURLOPT_PROXY] = $parts["host"]; |
| 40 | } else { |
| 41 | throw new ConnectionException("Invalid proxy"); |
| 42 | } |
| 43 | |
| 44 | if (isset($parts["port"])) { |
| 45 | $this->options[CURLOPT_PROXYPORT] = $parts["port"]; |
| 46 | } |
| 47 | |
| 48 | $creds = ""; |
| 49 | if (isset($parts["user"])) $creds .= $parts["user"]; |
| 50 | if (isset($parts["pass"])) $creds .= ":" . $parts["pass"]; |
| 51 | |
| 52 | if ($creds) { |
| 53 | $this->options[CURLOPT_PROXYAUTH] = CURLAUTH_ANY; |
| 54 | $this->options[CURLOPT_PROXYUSERPWD] = $creds; |
| 55 | } |
| 56 | } |
| 57 | } |
| 58 | |
| 59 | function request($method, $url, $body = NULL) { |
| 60 | $header = array(); |
| 61 | if (is_array($body)) { |
| 62 | if (!empty($body)) { |
| 63 | $body = json_encode($body); |
| 64 | array_push($header, "Content-Type: application/json"); |
| 65 | } else { |
| 66 | $body = NULL; |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | for ($retries = self::RETRY_COUNT; $retries >= 0; $retries--) { |
| 71 | if ($retries < self::RETRY_COUNT) { |
| 72 | usleep(self::RETRY_DELAY * 1000); |
| 73 | } |
| 74 | |
| 75 | $request = curl_init(); |
| 76 | if ($request === false || $request === null) { |
| 77 | throw new ConnectionException( |
| 78 | "Error while connecting: curl extension is not functional or disabled." |
| 79 | ); |
| 80 | } |
| 81 | |
| 82 | curl_setopt_array($request, $this->options); |
| 83 | |
| 84 | $url = strtolower(substr($url, 0, 6)) == "https:" ? $url : self::API_ENDPOINT . $url; |
| 85 | curl_setopt($request, CURLOPT_URL, $url); |
| 86 | curl_setopt($request, CURLOPT_CUSTOMREQUEST, strtoupper($method)); |
| 87 | |
| 88 | if (count($header) > 0) { |
| 89 | curl_setopt($request, CURLOPT_HTTPHEADER, $header); |
| 90 | } |
| 91 | |
| 92 | if ($body) { |
| 93 | curl_setopt($request, CURLOPT_POSTFIELDS, $body); |
| 94 | } |
| 95 | |
| 96 | $response = curl_exec($request); |
| 97 | |
| 98 | if (is_string($response)) { |
| 99 | $status = curl_getinfo($request, CURLINFO_HTTP_CODE); |
| 100 | $headerSize = curl_getinfo($request, CURLINFO_HEADER_SIZE); |
| 101 | curl_close($request); |
| 102 | |
| 103 | $headers = self::parseHeaders(substr($response, 0, $headerSize)); |
| 104 | $body = substr($response, $headerSize); |
| 105 | |
| 106 | if (isset($headers["compression-count"])) { |
| 107 | Tinify::setCompressionCount(intval($headers["compression-count"])); |
| 108 | } |
| 109 | |
| 110 | $isJson = false; |
| 111 | if (isset($headers["content-type"])) { |
| 112 | /* Parse JSON response bodies. */ |
| 113 | list($contentType) = explode(";", $headers["content-type"], 2); |
| 114 | if (strtolower(trim($contentType)) == "application/json") { |
| 115 | $isJson = true; |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | /* 1xx and 3xx are unexpected and will be treated as error. */ |
| 120 | $isError = $status <= 199 || $status >= 300; |
| 121 | |
| 122 | if ($isJson || $isError) { |
| 123 | /* Parse JSON bodies, always interpret errors as JSON. */ |
| 124 | $body = json_decode($body); |
| 125 | if (!$body) { |
| 126 | $message = sprintf("Error while parsing response: %s (#%d)", |
| 127 | PHP_VERSION_ID >= 50500 ? json_last_error_msg() : "Error", |
| 128 | json_last_error()); |
| 129 | if ($retries > 0 && $status >= 500) continue; |
| 130 | throw Exception::create($message, "ParseError", $status); |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | if ($isError) { |
| 135 | if ($retries > 0 && $status >= 500) continue; |
| 136 | throw Exception::create($body->message, $body->error, $status); |
| 137 | } |
| 138 | |
| 139 | return (object) array("body" => $body, "headers" => $headers); |
| 140 | } else { |
| 141 | $message = sprintf("%s (#%d)", curl_error($request), curl_errno($request)); |
| 142 | curl_close($request); |
| 143 | if ($retries > 0) continue; |
| 144 | throw new ConnectionException("Error while connecting: " . $message); |
| 145 | } |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | protected static function parseHeaders($headers) { |
| 150 | if (!is_array($headers)) { |
| 151 | $headers = explode("\r\n", $headers); |
| 152 | } |
| 153 | |
| 154 | $result = array(); |
| 155 | foreach ($headers as $header) { |
| 156 | if (empty($header)) continue; |
| 157 | $split = explode(":", $header, 2); |
| 158 | if (count($split) === 2) { |
| 159 | $result[strtolower($split[0])] = trim($split[1]); |
| 160 | } |
| 161 | } |
| 162 | return $result; |
| 163 | } |
| 164 | } |
| 165 |