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.

145 lines
4.8 KiB

4 years ago
  1. # -*- coding: utf-8 -*-
  2. """aiohttp requester module."""
  3. import asyncio
  4. import logging
  5. import socket
  6. import aiohttp
  7. import aiohttp.web
  8. import async_timeout
  9. from async_upnp_client import UpnpRequester
  10. from async_upnp_client import UpnpEventHandler
  11. _LOGGER = logging.getLogger(__name__)
  12. def get_local_ip(target_host=None) -> str:
  13. """Try to get the local IP of this machine, used to talk to target_url."""
  14. target_host = target_host or '8.8.8.8'
  15. target_port = 80
  16. try:
  17. temp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
  18. temp_sock.connect((target_host, target_port))
  19. return temp_sock.getsockname()[0]
  20. finally:
  21. temp_sock.close()
  22. class AiohttpRequester(UpnpRequester):
  23. """Standard AioHttpUpnpRequester, to be used with UpnpFactory."""
  24. def __init__(self, timeout=5) -> None:
  25. """Initializer."""
  26. self._timeout = timeout
  27. async def async_do_http_request(self, method, url, headers=None, body=None, body_type='text'):
  28. """Do a HTTP request."""
  29. # pylint: disable=too-many-arguments
  30. async with async_timeout.timeout(self._timeout):
  31. async with aiohttp.ClientSession() as session:
  32. async with session.request(method, url, headers=headers, data=body) as response:
  33. status = response.status
  34. headers = response.headers
  35. if body_type == 'text':
  36. body = await response.text()
  37. elif body_type == 'raw':
  38. body = await response.read()
  39. elif body_type == 'ignore':
  40. body = None
  41. return status, headers, body
  42. class AiohttpSessionRequester(UpnpRequester):
  43. """
  44. Standard AiohttpSessionRequester, to be used with UpnpFactory.
  45. With pluggable session.
  46. """
  47. def __init__(self, session, with_sleep=False, timeout=5) -> None:
  48. """Initializer."""
  49. self._session = session
  50. self._with_sleep = with_sleep
  51. self._timeout = timeout
  52. async def async_do_http_request(self, method, url, headers=None, body=None, body_type='text'):
  53. """Do a HTTP request."""
  54. # pylint: disable=too-many-arguments
  55. if self._with_sleep:
  56. await asyncio.sleep(0.01)
  57. async with async_timeout.timeout(self._timeout):
  58. async with self._session.request(method, url, headers=headers, data=body) as response:
  59. status = response.status
  60. headers = response.headers
  61. if body_type == 'text':
  62. body = await response.text()
  63. elif body_type == 'raw':
  64. body = await response.read()
  65. elif body_type == 'ignore':
  66. body = None
  67. return status, headers, body
  68. class AiohttpNotifyServer:
  69. """AIO HTTP Server to handle incoming events."""
  70. def __init__(self, requester, listen_port, listen_host=None, loop=None) -> None:
  71. """Initializer."""
  72. self._listen_port = listen_port
  73. self._listen_host = listen_host or get_local_ip()
  74. self._loop = loop or asyncio.get_event_loop()
  75. self._aiohttp_server = None
  76. self._server = None
  77. callback_url = "http://{}:{}/notify".format(self._listen_host, self._listen_port)
  78. self.event_handler = UpnpEventHandler(callback_url, requester)
  79. async def start_server(self):
  80. """Start the HTTP server."""
  81. self._aiohttp_server = aiohttp.web.Server(self._handle_request)
  82. try:
  83. self._server = await self._loop.create_server(self._aiohttp_server,
  84. self._listen_host,
  85. self._listen_port)
  86. except OSError as error:
  87. _LOGGER.error("Failed to create HTTP server at %s:%d: %s",
  88. self._listen_host, self._listen_port, error)
  89. async def stop_server(self):
  90. """Stop the HTTP server."""
  91. if self._aiohttp_server:
  92. await self._aiohttp_server.shutdown(10)
  93. if self._server:
  94. self._server.close()
  95. async def _handle_request(self, request) -> aiohttp.web.Response:
  96. """Handle incoming requests."""
  97. _LOGGER.debug('Received request: %s', request)
  98. if request.method != 'NOTIFY':
  99. _LOGGER.debug('Not notify')
  100. return aiohttp.web.Response(status=405)
  101. headers = request.headers
  102. body = await request.text()
  103. status = await self.event_handler.handle_notify(headers, body)
  104. _LOGGER.debug('NOTIFY response status: %s', status)
  105. return aiohttp.web.Response(status=status)
  106. @property
  107. def callback_url(self) -> str:
  108. """Return callback URL on which we are callable."""
  109. return self.event_handler.callback_url