httpUtil.php 2.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374
  1. <?php
  2. declare(strict_types=1);
  3. final class FreshRSS_http_Util {
  4. private const RETRY_AFTER_PATH = DATA_PATH . '/Retry-After/';
  5. /**
  6. * Clean up old Retry-After files
  7. */
  8. private static function cleanRetryAfters(): void {
  9. if (!is_dir(self::RETRY_AFTER_PATH)) {
  10. return;
  11. }
  12. $files = glob(self::RETRY_AFTER_PATH . '*.txt', GLOB_NOSORT);
  13. if ($files === false) {
  14. return;
  15. }
  16. foreach ($files as $file) {
  17. if (@filemtime($file) < time()) {
  18. @unlink($file);
  19. }
  20. }
  21. }
  22. /**
  23. * Check whether the URL needs to wait for a Retry-After period.
  24. * @return int The timestamp of when the Retry-After expires, or 0 if not set.
  25. */
  26. public static function getRetryAfter(string $url): int {
  27. if (rand(0, 30) === 1) { // Remove old files once in a while
  28. self::cleanRetryAfters();
  29. }
  30. $domain = parse_url($url, PHP_URL_HOST);
  31. if (!is_string($domain) || $domain === '') {
  32. return 0;
  33. }
  34. $retryAfter = @filemtime(self::RETRY_AFTER_PATH . $domain . '.txt') ?: 0;
  35. if ($retryAfter <= 0) {
  36. return 0;
  37. }
  38. if ($retryAfter < time()) {
  39. @unlink(self::RETRY_AFTER_PATH . $domain . '.txt');
  40. return 0;
  41. }
  42. return $retryAfter;
  43. }
  44. /**
  45. * Store the HTTP Retry-After header value of an HTTP `429 Too Many Requests` or `503 Service Unavailable` response.
  46. */
  47. public static function setRetryAfter(string $url, string $retryAfter): int {
  48. $domain = parse_url($url, PHP_URL_HOST);
  49. if (!is_string($domain) || $domain === '') {
  50. return 0;
  51. }
  52. $limits = FreshRSS_Context::systemConf()->limits;
  53. if (ctype_digit($retryAfter)) {
  54. $retryAfter = time() + (int)$retryAfter;
  55. } else {
  56. $retryAfter = \SimplePie\Misc::parse_date($retryAfter) ?:
  57. (time() + max(600, $limits['retry_after_default'] ?? 0));
  58. }
  59. $retryAfter = min($retryAfter, time() + max(3600, $limits['retry_after_max'] ?? 0));
  60. @mkdir(self::RETRY_AFTER_PATH);
  61. if (!touch(self::RETRY_AFTER_PATH . $domain . '.txt', $retryAfter)) {
  62. Minz_Log::error('Failed to set Retry-After for ' . $domain);
  63. return 0;
  64. }
  65. return $retryAfter;
  66. }
  67. }