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.

524 lines
18 KiB

4 years ago
  1. import asyncio
  2. import asyncio.streams
  3. import traceback
  4. import warnings
  5. from collections import deque
  6. from contextlib import suppress
  7. from html import escape as html_escape
  8. import yarl
  9. from . import helpers
  10. from .base_protocol import BaseProtocol
  11. from .helpers import CeilTimeout
  12. from .http import (HttpProcessingError, HttpRequestParser, HttpVersion10,
  13. RawRequestMessage, StreamWriter)
  14. from .log import access_logger, server_logger
  15. from .streams import EMPTY_PAYLOAD
  16. from .tcp_helpers import tcp_cork, tcp_keepalive, tcp_nodelay
  17. from .web_exceptions import HTTPException
  18. from .web_request import BaseRequest
  19. from .web_response import Response
  20. __all__ = ('RequestHandler', 'RequestPayloadError', 'PayloadAccessError')
  21. ERROR = RawRequestMessage(
  22. 'UNKNOWN', '/', HttpVersion10, {},
  23. {}, True, False, False, False, yarl.URL('/'))
  24. class RequestPayloadError(Exception):
  25. """Payload parsing error."""
  26. class PayloadAccessError(Exception):
  27. """Payload was accesed after responce was sent."""
  28. class RequestHandler(BaseProtocol):
  29. """HTTP protocol implementation.
  30. RequestHandler handles incoming HTTP request. It reads request line,
  31. request headers and request payload and calls handle_request() method.
  32. By default it always returns with 404 response.
  33. RequestHandler handles errors in incoming request, like bad
  34. status line, bad headers or incomplete payload. If any error occurs,
  35. connection gets closed.
  36. :param keepalive_timeout: number of seconds before closing
  37. keep-alive connection
  38. :type keepalive_timeout: int or None
  39. :param bool tcp_keepalive: TCP keep-alive is on, default is on
  40. :param bool debug: enable debug mode
  41. :param logger: custom logger object
  42. :type logger: aiohttp.log.server_logger
  43. :param access_log_class: custom class for access_logger
  44. :type access_log_class: aiohttp.abc.AbstractAccessLogger
  45. :param access_log: custom logging object
  46. :type access_log: aiohttp.log.server_logger
  47. :param str access_log_format: access log format string
  48. :param loop: Optional event loop
  49. :param int max_line_size: Optional maximum header line size
  50. :param int max_field_size: Optional maximum header field size
  51. :param int max_headers: Optional maximum header size
  52. """
  53. KEEPALIVE_RESCHEDULE_DELAY = 1
  54. __slots__ = ('_request_count', '_keep_alive', '_manager',
  55. '_request_handler', '_request_factory', '_tcp_keepalive',
  56. '_keepalive_time', '_keepalive_handle', '_keepalive_timeout',
  57. '_lingering_time', '_messages', '_message_tail',
  58. '_waiter', '_error_handler', '_task_handler',
  59. '_upgrade', '_payload_parser', '_request_parser',
  60. '_reading_paused', 'logger', 'debug', 'access_log',
  61. 'access_logger', '_close', '_force_close')
  62. def __init__(self, manager, *, loop=None,
  63. keepalive_timeout=75, # NGINX default value is 75 secs
  64. tcp_keepalive=True,
  65. logger=server_logger,
  66. access_log_class=helpers.AccessLogger,
  67. access_log=access_logger,
  68. access_log_format=helpers.AccessLogger.LOG_FORMAT,
  69. debug=False,
  70. max_line_size=8190,
  71. max_headers=32768,
  72. max_field_size=8190,
  73. lingering_time=10.0):
  74. super().__init__(loop=loop)
  75. self._request_count = 0
  76. self._keepalive = False
  77. self._manager = manager
  78. self._request_handler = manager.request_handler
  79. self._request_factory = manager.request_factory
  80. self._tcp_keepalive = tcp_keepalive
  81. self._keepalive_time = None
  82. self._keepalive_handle = None
  83. self._keepalive_timeout = keepalive_timeout
  84. self._lingering_time = float(lingering_time)
  85. self._messages = deque()
  86. self._message_tail = b''
  87. self._waiter = None
  88. self._error_handler = None
  89. self._task_handler = None
  90. self._upgrade = False
  91. self._payload_parser = None
  92. self._request_parser = HttpRequestParser(
  93. self, loop,
  94. max_line_size=max_line_size,
  95. max_field_size=max_field_size,
  96. max_headers=max_headers,
  97. payload_exception=RequestPayloadError)
  98. self._reading_paused = False
  99. self.logger = logger
  100. self.debug = debug
  101. self.access_log = access_log
  102. if access_log:
  103. self.access_logger = access_log_class(
  104. access_log, access_log_format)
  105. else:
  106. self.access_logger = None
  107. self._close = False
  108. self._force_close = False
  109. def __repr__(self):
  110. return "<{} {}>".format(
  111. self.__class__.__name__,
  112. 'connected' if self.transport is not None else 'disconnected')
  113. @property
  114. def keepalive_timeout(self):
  115. return self._keepalive_timeout
  116. async def shutdown(self, timeout=15.0):
  117. """Worker process is about to exit, we need cleanup everything and
  118. stop accepting requests. It is especially important for keep-alive
  119. connections."""
  120. self._force_close = True
  121. if self._keepalive_handle is not None:
  122. self._keepalive_handle.cancel()
  123. if self._waiter:
  124. self._waiter.cancel()
  125. # wait for handlers
  126. with suppress(asyncio.CancelledError, asyncio.TimeoutError):
  127. with CeilTimeout(timeout, loop=self._loop):
  128. if (self._error_handler is not None and
  129. not self._error_handler.done()):
  130. await self._error_handler
  131. if (self._task_handler is not None and
  132. not self._task_handler.done()):
  133. await self._task_handler
  134. # force-close non-idle handler
  135. if self._task_handler is not None:
  136. self._task_handler.cancel()
  137. if self.transport is not None:
  138. self.transport.close()
  139. self.transport = None
  140. def connection_made(self, transport):
  141. super().connection_made(transport)
  142. if self._tcp_keepalive:
  143. tcp_keepalive(transport)
  144. tcp_cork(transport, False)
  145. tcp_nodelay(transport, True)
  146. self._task_handler = self._loop.create_task(self.start())
  147. self._manager.connection_made(self, transport)
  148. def connection_lost(self, exc):
  149. self._manager.connection_lost(self, exc)
  150. super().connection_lost(exc)
  151. self._manager = None
  152. self._force_close = True
  153. self._request_factory = None
  154. self._request_handler = None
  155. self._request_parser = None
  156. if self._keepalive_handle is not None:
  157. self._keepalive_handle.cancel()
  158. if self._task_handler is not None:
  159. self._task_handler.cancel()
  160. if self._error_handler is not None:
  161. self._error_handler.cancel()
  162. self._task_handler = None
  163. if self._payload_parser is not None:
  164. self._payload_parser.feed_eof()
  165. self._payload_parser = None
  166. def set_parser(self, parser):
  167. assert self._payload_parser is None
  168. self._payload_parser = parser
  169. if self._message_tail:
  170. self._payload_parser.feed_data(self._message_tail)
  171. self._message_tail = b''
  172. def eof_received(self):
  173. pass
  174. def data_received(self, data):
  175. if self._force_close or self._close:
  176. return
  177. # parse http messages
  178. if self._payload_parser is None and not self._upgrade:
  179. try:
  180. messages, upgraded, tail = self._request_parser.feed_data(data)
  181. except HttpProcessingError as exc:
  182. # something happened during parsing
  183. self._error_handler = self._loop.create_task(
  184. self.handle_parse_error(
  185. StreamWriter(self, self._loop),
  186. 400, exc, exc.message))
  187. self.close()
  188. except Exception as exc:
  189. # 500: internal error
  190. self._error_handler = self._loop.create_task(
  191. self.handle_parse_error(
  192. StreamWriter(self, self._loop),
  193. 500, exc))
  194. self.close()
  195. else:
  196. if messages:
  197. # sometimes the parser returns no messages
  198. for (msg, payload) in messages:
  199. self._request_count += 1
  200. self._messages.append((msg, payload))
  201. waiter = self._waiter
  202. if waiter is not None:
  203. if not waiter.done():
  204. # don't set result twice
  205. waiter.set_result(None)
  206. self._upgrade = upgraded
  207. if upgraded and tail:
  208. self._message_tail = tail
  209. # no parser, just store
  210. elif self._payload_parser is None and self._upgrade and data:
  211. self._message_tail += data
  212. # feed payload
  213. elif data:
  214. eof, tail = self._payload_parser.feed_data(data)
  215. if eof:
  216. self.close()
  217. def keep_alive(self, val):
  218. """Set keep-alive connection mode.
  219. :param bool val: new state.
  220. """
  221. self._keepalive = val
  222. if self._keepalive_handle:
  223. self._keepalive_handle.cancel()
  224. self._keepalive_handle = None
  225. def close(self):
  226. """Stop accepting new pipelinig messages and close
  227. connection when handlers done processing messages"""
  228. self._close = True
  229. if self._waiter:
  230. self._waiter.cancel()
  231. def force_close(self):
  232. """Force close connection"""
  233. self._force_close = True
  234. if self._waiter:
  235. self._waiter.cancel()
  236. if self.transport is not None:
  237. self.transport.close()
  238. self.transport = None
  239. def log_access(self, request, response, time):
  240. if self.access_logger is not None:
  241. self.access_logger.log(request, response, time)
  242. def log_debug(self, *args, **kw):
  243. if self.debug:
  244. self.logger.debug(*args, **kw)
  245. def log_exception(self, *args, **kw):
  246. self.logger.exception(*args, **kw)
  247. def _process_keepalive(self):
  248. if self._force_close or not self._keepalive:
  249. return
  250. next = self._keepalive_time + self._keepalive_timeout
  251. # handler in idle state
  252. if self._waiter:
  253. if self._loop.time() > next:
  254. self.force_close()
  255. return
  256. # not all request handlers are done,
  257. # reschedule itself to next second
  258. self._keepalive_handle = self._loop.call_later(
  259. self.KEEPALIVE_RESCHEDULE_DELAY, self._process_keepalive)
  260. def pause_reading(self):
  261. if not self._reading_paused:
  262. try:
  263. self.transport.pause_reading()
  264. except (AttributeError, NotImplementedError, RuntimeError):
  265. pass
  266. self._reading_paused = True
  267. def resume_reading(self):
  268. if self._reading_paused:
  269. try:
  270. self.transport.resume_reading()
  271. except (AttributeError, NotImplementedError, RuntimeError):
  272. pass
  273. self._reading_paused = False
  274. async def start(self):
  275. """Process incoming request.
  276. It reads request line, request headers and request payload, then
  277. calls handle_request() method. Subclass has to override
  278. handle_request(). start() handles various exceptions in request
  279. or response handling. Connection is being closed always unless
  280. keep_alive(True) specified.
  281. """
  282. loop = self._loop
  283. handler = self._task_handler
  284. manager = self._manager
  285. keepalive_timeout = self._keepalive_timeout
  286. while not self._force_close:
  287. if not self._messages:
  288. try:
  289. # wait for next request
  290. self._waiter = loop.create_future()
  291. await self._waiter
  292. except asyncio.CancelledError:
  293. break
  294. finally:
  295. self._waiter = None
  296. message, payload = self._messages.popleft()
  297. if self.access_log:
  298. now = loop.time()
  299. manager.requests_count += 1
  300. writer = StreamWriter(self, loop)
  301. request = self._request_factory(
  302. message, payload, self, writer, handler)
  303. try:
  304. try:
  305. resp = await self._request_handler(request)
  306. except HTTPException as exc:
  307. resp = exc
  308. except asyncio.CancelledError:
  309. self.log_debug('Ignored premature client disconnection')
  310. break
  311. except asyncio.TimeoutError:
  312. self.log_debug('Request handler timed out.')
  313. resp = self.handle_error(request, 504)
  314. except Exception as exc:
  315. resp = self.handle_error(request, 500, exc)
  316. else:
  317. # Deprecation warning (See #2415)
  318. if getattr(resp, '__http_exception__', False):
  319. warnings.warn(
  320. "returning HTTPException object is deprecated "
  321. "(#2415) and will be removed, "
  322. "please raise the exception instead",
  323. DeprecationWarning)
  324. await resp.prepare(request)
  325. await resp.write_eof()
  326. # notify server about keep-alive
  327. self._keepalive = resp.keep_alive
  328. # log access
  329. if self.access_log:
  330. self.log_access(request, resp, loop.time() - now)
  331. # check payload
  332. if not payload.is_eof():
  333. lingering_time = self._lingering_time
  334. if not self._force_close and lingering_time:
  335. self.log_debug(
  336. 'Start lingering close timer for %s sec.',
  337. lingering_time)
  338. now = loop.time()
  339. end_t = now + lingering_time
  340. with suppress(
  341. asyncio.TimeoutError, asyncio.CancelledError):
  342. while not payload.is_eof() and now < end_t:
  343. timeout = min(end_t - now, lingering_time)
  344. with CeilTimeout(timeout, loop=loop):
  345. # read and ignore
  346. await payload.readany()
  347. now = loop.time()
  348. # if payload still uncompleted
  349. if not payload.is_eof() and not self._force_close:
  350. self.log_debug('Uncompleted request.')
  351. self.close()
  352. payload.set_exception(PayloadAccessError())
  353. except asyncio.CancelledError:
  354. self.log_debug('Ignored premature client disconnection ')
  355. break
  356. except RuntimeError as exc:
  357. if self.debug:
  358. self.log_exception(
  359. 'Unhandled runtime exception', exc_info=exc)
  360. self.force_close()
  361. except Exception as exc:
  362. self.log_exception('Unhandled exception', exc_info=exc)
  363. self.force_close()
  364. finally:
  365. if self.transport is None:
  366. self.log_debug('Ignored premature client disconnection.')
  367. elif not self._force_close:
  368. if self._keepalive and not self._close:
  369. # start keep-alive timer
  370. if keepalive_timeout is not None:
  371. now = self._loop.time()
  372. self._keepalive_time = now
  373. if self._keepalive_handle is None:
  374. self._keepalive_handle = loop.call_at(
  375. now + keepalive_timeout,
  376. self._process_keepalive)
  377. else:
  378. break
  379. # remove handler, close transport if no handlers left
  380. if not self._force_close:
  381. self._task_handler = None
  382. if self.transport is not None and self._error_handler is None:
  383. self.transport.close()
  384. def handle_error(self, request, status=500, exc=None, message=None):
  385. """Handle errors.
  386. Returns HTTP response with specific status code. Logs additional
  387. information. It always closes current connection."""
  388. self.log_exception("Error handling request", exc_info=exc)
  389. if status == 500:
  390. msg = "<h1>500 Internal Server Error</h1>"
  391. if self.debug:
  392. with suppress(Exception):
  393. tb = traceback.format_exc()
  394. tb = html_escape(tb)
  395. msg += '<br><h2>Traceback:</h2>\n<pre>'
  396. msg += tb
  397. msg += '</pre>'
  398. else:
  399. msg += "Server got itself in trouble"
  400. msg = ("<html><head><title>500 Internal Server Error</title>"
  401. "</head><body>" + msg + "</body></html>")
  402. else:
  403. msg = message
  404. resp = Response(status=status, text=msg, content_type='text/html')
  405. resp.force_close()
  406. # some data already got sent, connection is broken
  407. if request.writer.output_size > 0 or self.transport is None:
  408. self.force_close()
  409. return resp
  410. async def handle_parse_error(self, writer, status, exc=None, message=None):
  411. request = BaseRequest(
  412. ERROR, EMPTY_PAYLOAD,
  413. self, writer, None, self._loop)
  414. resp = self.handle_error(request, status, exc, message)
  415. await resp.prepare(request)
  416. await resp.write_eof()
  417. if self.transport is not None:
  418. self.transport.close()
  419. self._error_handler = None