_asgi_common.py 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137
  1. import urllib
  2. from sentry_sdk.scope import should_send_default_pii
  3. from sentry_sdk.integrations._wsgi_common import _filter_headers
  4. from typing import TYPE_CHECKING
  5. if TYPE_CHECKING:
  6. from typing import Any
  7. from typing import Dict
  8. from typing import Optional
  9. from typing import Union
  10. from typing_extensions import Literal
  11. from sentry_sdk.utils import AnnotatedValue
  12. def _get_headers(asgi_scope: "Any") -> "Dict[str, str]":
  13. """
  14. Extract headers from the ASGI scope, in the format that the Sentry protocol expects.
  15. """
  16. headers: "Dict[str, str]" = {}
  17. for raw_key, raw_value in asgi_scope["headers"]:
  18. key = raw_key.decode("latin-1")
  19. value = raw_value.decode("latin-1")
  20. if key in headers:
  21. headers[key] = headers[key] + ", " + value
  22. else:
  23. headers[key] = value
  24. return headers
  25. def _get_url(
  26. asgi_scope: "Dict[str, Any]",
  27. default_scheme: "Literal['ws', 'http']",
  28. host: "Optional[Union[AnnotatedValue, str]]",
  29. ) -> str:
  30. """
  31. Extract URL from the ASGI scope, without also including the querystring.
  32. """
  33. scheme = asgi_scope.get("scheme", default_scheme)
  34. server = asgi_scope.get("server", None)
  35. path = asgi_scope.get("root_path", "") + asgi_scope.get("path", "")
  36. if host:
  37. return "%s://%s%s" % (scheme, host, path)
  38. if server is not None:
  39. host, port = server
  40. default_port = {"http": 80, "https": 443, "ws": 80, "wss": 443}.get(scheme)
  41. if port != default_port:
  42. return "%s://%s:%s%s" % (scheme, host, port, path)
  43. return "%s://%s%s" % (scheme, host, path)
  44. return path
  45. def _get_query(asgi_scope: "Any") -> "Any":
  46. """
  47. Extract querystring from the ASGI scope, in the format that the Sentry protocol expects.
  48. """
  49. qs = asgi_scope.get("query_string")
  50. if not qs:
  51. return None
  52. return urllib.parse.unquote(qs.decode("latin-1"))
  53. def _get_ip(asgi_scope: "Any") -> str:
  54. """
  55. Extract IP Address from the ASGI scope based on request headers with fallback to scope client.
  56. """
  57. headers = _get_headers(asgi_scope)
  58. try:
  59. return headers["x-forwarded-for"].split(",")[0].strip()
  60. except (KeyError, IndexError):
  61. pass
  62. try:
  63. return headers["x-real-ip"]
  64. except KeyError:
  65. pass
  66. return asgi_scope.get("client")[0]
  67. def _get_request_data(asgi_scope: "Any") -> "Dict[str, Any]":
  68. """
  69. Returns data related to the HTTP request from the ASGI scope.
  70. """
  71. request_data: "Dict[str, Any]" = {}
  72. ty = asgi_scope["type"]
  73. if ty in ("http", "websocket"):
  74. request_data["method"] = asgi_scope.get("method")
  75. request_data["headers"] = headers = _filter_headers(_get_headers(asgi_scope))
  76. request_data["query_string"] = _get_query(asgi_scope)
  77. request_data["url"] = _get_url(
  78. asgi_scope, "http" if ty == "http" else "ws", headers.get("host")
  79. )
  80. client = asgi_scope.get("client")
  81. if client and should_send_default_pii():
  82. request_data["env"] = {"REMOTE_ADDR": _get_ip(asgi_scope)}
  83. return request_data
  84. def _get_request_attributes(asgi_scope: "Any") -> "dict[str, Any]":
  85. """
  86. Return attributes related to the HTTP request from the ASGI scope.
  87. """
  88. attributes: "dict[str, Any]" = {}
  89. ty = asgi_scope["type"]
  90. if ty in ("http", "websocket"):
  91. if asgi_scope.get("method"):
  92. attributes["http.request.method"] = asgi_scope["method"].upper()
  93. headers = _filter_headers(_get_headers(asgi_scope), use_annotated_value=False)
  94. for header, value in headers.items():
  95. attributes[f"http.request.header.{header.lower()}"] = value
  96. query = _get_query(asgi_scope)
  97. if query:
  98. attributes["http.query"] = query
  99. attributes["url.full"] = _get_url(
  100. asgi_scope, "http" if ty == "http" else "ws", headers.get("host")
  101. )
  102. client = asgi_scope.get("client")
  103. if client and should_send_default_pii():
  104. attributes["client.address"] = _get_ip(asgi_scope)
  105. return attributes