m2m模型翻译
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.

154 lines
4.7 KiB

6 months ago
  1. # SPDX-FileCopyrightText: 2015 Eric Larson
  2. #
  3. # SPDX-License-Identifier: Apache-2.0
  4. from __future__ import annotations
  5. import calendar
  6. import time
  7. from datetime import datetime, timedelta, timezone
  8. from email.utils import formatdate, parsedate, parsedate_tz
  9. from typing import TYPE_CHECKING, Any, Mapping
  10. if TYPE_CHECKING:
  11. from pip._vendor.urllib3 import HTTPResponse
  12. TIME_FMT = "%a, %d %b %Y %H:%M:%S GMT"
  13. def expire_after(delta: timedelta, date: datetime | None = None) -> datetime:
  14. date = date or datetime.now(timezone.utc)
  15. return date + delta
  16. def datetime_to_header(dt: datetime) -> str:
  17. return formatdate(calendar.timegm(dt.timetuple()))
  18. class BaseHeuristic:
  19. def warning(self, response: HTTPResponse) -> str | None:
  20. """
  21. Return a valid 1xx warning header value describing the cache
  22. adjustments.
  23. The response is provided too allow warnings like 113
  24. http://tools.ietf.org/html/rfc7234#section-5.5.4 where we need
  25. to explicitly say response is over 24 hours old.
  26. """
  27. return '110 - "Response is Stale"'
  28. def update_headers(self, response: HTTPResponse) -> dict[str, str]:
  29. """Update the response headers with any new headers.
  30. NOTE: This SHOULD always include some Warning header to
  31. signify that the response was cached by the client, not
  32. by way of the provided headers.
  33. """
  34. return {}
  35. def apply(self, response: HTTPResponse) -> HTTPResponse:
  36. updated_headers = self.update_headers(response)
  37. if updated_headers:
  38. response.headers.update(updated_headers)
  39. warning_header_value = self.warning(response)
  40. if warning_header_value is not None:
  41. response.headers.update({"Warning": warning_header_value})
  42. return response
  43. class OneDayCache(BaseHeuristic):
  44. """
  45. Cache the response by providing an expires 1 day in the
  46. future.
  47. """
  48. def update_headers(self, response: HTTPResponse) -> dict[str, str]:
  49. headers = {}
  50. if "expires" not in response.headers:
  51. date = parsedate(response.headers["date"])
  52. expires = expire_after(timedelta(days=1), date=datetime(*date[:6], tzinfo=timezone.utc)) # type: ignore[misc]
  53. headers["expires"] = datetime_to_header(expires)
  54. headers["cache-control"] = "public"
  55. return headers
  56. class ExpiresAfter(BaseHeuristic):
  57. """
  58. Cache **all** requests for a defined time period.
  59. """
  60. def __init__(self, **kw: Any) -> None:
  61. self.delta = timedelta(**kw)
  62. def update_headers(self, response: HTTPResponse) -> dict[str, str]:
  63. expires = expire_after(self.delta)
  64. return {"expires": datetime_to_header(expires), "cache-control": "public"}
  65. def warning(self, response: HTTPResponse) -> str | None:
  66. tmpl = "110 - Automatically cached for %s. Response might be stale"
  67. return tmpl % self.delta
  68. class LastModified(BaseHeuristic):
  69. """
  70. If there is no Expires header already, fall back on Last-Modified
  71. using the heuristic from
  72. http://tools.ietf.org/html/rfc7234#section-4.2.2
  73. to calculate a reasonable value.
  74. Firefox also does something like this per
  75. https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching_FAQ
  76. http://lxr.mozilla.org/mozilla-release/source/netwerk/protocol/http/nsHttpResponseHead.cpp#397
  77. Unlike mozilla we limit this to 24-hr.
  78. """
  79. cacheable_by_default_statuses = {
  80. 200,
  81. 203,
  82. 204,
  83. 206,
  84. 300,
  85. 301,
  86. 404,
  87. 405,
  88. 410,
  89. 414,
  90. 501,
  91. }
  92. def update_headers(self, resp: HTTPResponse) -> dict[str, str]:
  93. headers: Mapping[str, str] = resp.headers
  94. if "expires" in headers:
  95. return {}
  96. if "cache-control" in headers and headers["cache-control"] != "public":
  97. return {}
  98. if resp.status not in self.cacheable_by_default_statuses:
  99. return {}
  100. if "date" not in headers or "last-modified" not in headers:
  101. return {}
  102. time_tuple = parsedate_tz(headers["date"])
  103. assert time_tuple is not None
  104. date = calendar.timegm(time_tuple[:6])
  105. last_modified = parsedate(headers["last-modified"])
  106. if last_modified is None:
  107. return {}
  108. now = time.time()
  109. current_age = max(0, now - date)
  110. delta = date - calendar.timegm(last_modified)
  111. freshness_lifetime = max(0, min(delta / 10, 24 * 3600))
  112. if freshness_lifetime <= current_age:
  113. return {}
  114. expires = date + freshness_lifetime
  115. return {"expires": time.strftime(TIME_FMT, time.gmtime(expires))}
  116. def warning(self, resp: HTTPResponse) -> str | None:
  117. return None