3, 'posts/recent' => 60, 'posts/all' => 300 ]); SDV($PinboardAPICacheFolder, 'pub/cache/pinboard/'); SDV($PinboardAPIResponseCacheDuration, 0); $PinboardAPIResponseCacheDuration = ($PinboardAPIResponseCacheDuration > 0) ? max($PinboardAPIResponseCacheDuration, max($PinboardAPIMethodCooldowns)) : 0; /*********/ /* SETUP */ /*********/ if (!file_exists($PinboardAPICacheFolder)) mkdir($PinboardAPICacheFolder); $response_cache_file = $PinboardAPICacheFolder."response_cache.json"; $response_cache = PinboardAPIGetResponseCache(); $request_log_file = $PinboardAPICacheFolder."request_log.json"; $request_log = PinboardAPIGetRequestLog(); /*************/ /* FUNCTIONS */ /*************/ function PinboardAPIRequest($method, $params) { global $PinboardAPIToken, $PinboardAPIEndpoint, $PinboardAPIAllowedMethods, $PinboardAPIResponseCacheDuration, $PinboardAPIMethodCooldowns; global $request_log, $response_cache; ## Check if specified method is allowed. If not, return an error. if (!(in_array($method, $PinboardAPIAllowedMethods) || (in_array('notes/ID', $PinboardAPIAllowedMethods) && preg_match("^notes\/", $method)))) return [ 'error-text' => "The method “$method” is not permitted.", 'error-html' => "

The method “$method” is not permitted.

\n" ]; ## Build the request. $request_URL = $PinboardAPIEndpoint . $method; $request_params = [ 'format' => 'json', 'auth_token' => $PinboardAPIToken ]; $request_params = array_merge($request_params, $params); $request_URL .= "?" . http_build_query($request_params); ## This is for logging/caching. $request_URL_hash = md5($request_URL); $cur_time = time(); ## If... ## a) a response cache duration is specified, and... ## b) there is a cached response for this request, and... ## c) the cached response isn't too old... ## ... then return the cached response. if ($PinboardAPIResponseCacheDuration > 0 && isset($response_cache[$request_URL_hash]) && ($cur_time - $response_cache[$request_URL_hash]['request_time']) <= $PinboardAPIResponseCacheDuration) { return $response_cache[$request_URL_hash]; } ## Check elapsed time since last request (or last request of this type, in the case of ## methods that have their own rate limits (i.e. posts/recent and posts/all)). ## If the new request comes too soon after the last one, return an error message. $cooldown_category = $PinboardAPIMethodCooldowns[$method] ? $method : 'global'; $cooldown = $PinboardAPIMethodCooldowns[$cooldown_category]; $elapsed = $cur_time - $request_log["last-{$cooldown_category}"]; ## Alternatively, if the last request got an HTTP status code 429 (Too Many Requests), ## then make sure a good long while has elapsed since the last request of any kind; ## if it hasn't, then return an error. ## (What's a "good long while"? Well, "twice as long as the longest cooldown" seems ## like a reasonable value. (The longest cooldown should be the 5-minute cooldown for ## posts/all, so the cooldown after a 429 ought to be 10 minutes (600 seconds).)) if (isset($request_log['last_request_hash']) && $response_cache[$request_log['last_request_hash']]['http_code'] == 429) { $cooldown = 2 * max($PinboardAPIMethodCooldowns); $elapsed = $cur_time - $request_log["last-global"]; } ## In either case, if we're still within the relevant cooldown, return an error. if ($elapsed < $cooldown) return [ 'error-text' => "Too many requests. Wait a bit, then try again.", 'error-html' => "

Too many requests. Wait a bit, then try again.

\n" ]; ## Send the request. $curl = curl_init($request_URL); curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); $curl_response = curl_exec($curl); ## Handle the response. $info = curl_getinfo($curl); $response = json_decode($curl_response, true); $response['http_code'] = $info['http_code']; $response['request_time'] = $cur_time; curl_close($curl); ## Cache the response. PinboardAPIUpdateResponseCache($request_URL_hash, $response); ## Update the last-request info. $request_log['last-global'] = $cur_time; if ($method == 'posts/recent') $request_log['last-posts/recent'] = $cur_time; else if ($method == 'posts/all') $request_log['last-posts/all'] = $cur_time; $request_log['last_request_hash'] = $request_URL_hash; PinboardAPISaveRequestLog($request_log); return $response; } /***************/ /* REQUEST LOG */ /***************/ function PinboardAPIResetRequestLog() { global $request_log_file; file_put_contents($request_log_file, json_encode([ 'last-global' => 0, 'last-posts/recent' => 0, 'last-posts/all' => 0 ])); } function PinboardAPIGetRequestLog() { global $request_log_file; if (!file_exists($request_log_file)) PinboardAPIResetRequestLog(); $request_log = json_decode(file_get_contents($request_log_file), true); return $request_log; } function PinboardAPISaveRequestLog($request_log) { global $request_log_file; file_put_contents($request_log_file, json_encode($request_log)); } /******************/ /* RESPONSE CACHE */ /******************/ function PinboardAPIClearResponseCache() { global $response_cache_file; file_put_contents($response_cache_file, json_encode([ ])); } function PinboardAPIGetResponseCache() { global $response_cache_file; if (!file_exists($response_cache_file)) { PinboardAPIClearResponseCache(); return [ ]; } else { return json_decode(file_get_contents($response_cache_file), true); } } function PinboardAPIUpdateResponseCache($request_URL_hash, $response) { global $response_cache_file, $PinboardAPIResponseCacheDuration; $response_cache = ($PinboardAPIResponseCacheDuration > 0) ? PinboardAPIGetResponseCache() : [ ]; $response_cache[$request_URL_hash] = $response; file_put_contents($response_cache_file, json_encode($response_cache)); }