utils.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import asyncio
  2. import time
  3. from datetime import timedelta
  4. from ssl import SSLError
  5. import aiohttp
  6. from aiohttp import ClientTimeout
  7. from aiohttp.client_exceptions import ClientConnectorError
  8. from django.conf import settings
  9. from django.utils import timezone
  10. from .constants import MonitorCheckReason, MonitorType
  11. from .models import MonitorCheck
  12. DEFAULT_TIMEOUT = 20 # Seconds
  13. PAYLOAD_LIMIT = 2_000_000 # 2mb
  14. PAYLOAD_SAVE_LIMIT = 500_000 # pseudo 500kb
  15. async def process_response(monitor, response):
  16. if response.status == monitor["expected_status"]:
  17. if monitor["expected_body"]:
  18. # Limit size to 2MB
  19. body = await response.content.read(PAYLOAD_LIMIT)
  20. try:
  21. encoding = response.get_encoding()
  22. payload = body.decode(encoding, errors="ignore")
  23. except RuntimeError:
  24. payload = body.decode(errors="ignore")
  25. if monitor["expected_body"] in payload:
  26. monitor["is_up"] = True
  27. else:
  28. monitor["reason"] = MonitorCheckReason.BODY
  29. if monitor["latest_is_up"] != monitor["is_up"]:
  30. # Save only first 500k chars, to roughly reduce disk usage
  31. # Note that a unicode char is not always one byte
  32. # Only save on changes
  33. monitor["data"] = {"payload": payload[:PAYLOAD_SAVE_LIMIT]}
  34. else:
  35. monitor["is_up"] = True
  36. else:
  37. monitor["reason"] = MonitorCheckReason.STATUS
  38. async def fetch(session, monitor):
  39. monitor["is_up"] = False
  40. if monitor["monitor_type"] == MonitorType.HEARTBEAT:
  41. if await MonitorCheck.objects.filter(
  42. monitor_id=monitor["id"],
  43. start_check__gte=timezone.now() - monitor["interval"],
  44. ).aexists():
  45. monitor["is_up"] = True
  46. return monitor
  47. url = monitor["url"]
  48. timeout = monitor["timeout"] or DEFAULT_TIMEOUT
  49. try:
  50. start = time.monotonic()
  51. if monitor["monitor_type"] == MonitorType.PORT:
  52. fut = asyncio.open_connection(*url.split(":"))
  53. await asyncio.wait_for(fut, timeout=timeout)
  54. monitor["is_up"] = True
  55. else:
  56. client_timeout = ClientTimeout(total=monitor["timeout"] or DEFAULT_TIMEOUT)
  57. if monitor["monitor_type"] == MonitorType.PING:
  58. async with session.head(url, timeout=client_timeout):
  59. monitor["is_up"] = True
  60. elif monitor["monitor_type"] == MonitorType.GET:
  61. async with session.get(url, timeout=client_timeout) as response:
  62. await process_response(monitor, response)
  63. elif monitor["monitor_type"] == MonitorType.POST:
  64. async with session.post(url, timeout=client_timeout) as response:
  65. await process_response(monitor, response)
  66. monitor["response_time"] = timedelta(seconds=time.monotonic() - start)
  67. except SSLError:
  68. monitor["reason"] = MonitorCheckReason.SSL
  69. except asyncio.TimeoutError:
  70. monitor["reason"] = MonitorCheckReason.TIMEOUT
  71. except ClientConnectorError:
  72. monitor["reason"] = MonitorCheckReason.NETWORK
  73. except OSError:
  74. monitor["reason"] = MonitorCheckReason.UNKNOWN
  75. return monitor
  76. async def fetch_all(monitors):
  77. async with aiohttp.ClientSession(
  78. headers={"User-Agent": "GlitchTip/" + settings.GLITCHTIP_VERSION}
  79. ) as session:
  80. results = await asyncio.gather(
  81. *[fetch(session, monitor) for monitor in monitors], return_exceptions=True
  82. )
  83. return results