parser.py 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. # -*- coding: utf-8 -*-
  2. """
  3. hyper/http11/parser
  4. ~~~~~~~~~~~~~~~~~~~
  5. This module contains hyper's pure-Python HTTP/1.1 parser. This module defines
  6. an abstraction layer for HTTP/1.1 parsing that allows for dropping in other
  7. modules if needed, in order to obtain speedups on your chosen platform.
  8. """
  9. from collections import namedtuple
  10. Response = namedtuple(
  11. 'Response', ['status', 'msg', 'minor_version', 'headers', 'consumed']
  12. )
  13. class ParseError(Exception):
  14. """
  15. An invalid HTTP message was passed to the parser.
  16. """
  17. pass
  18. class Parser(object):
  19. """
  20. A single HTTP parser object.
  21. This object is not thread-safe, and it does maintain state that is shared
  22. across parsing requests. For this reason, make sure that access to this
  23. object is synchronized if you use it across multiple threads.
  24. """
  25. def __init__(self):
  26. pass
  27. def parse_response(self, buffer):
  28. """
  29. Parses a single HTTP response from a buffer.
  30. :param buffer: A ``memoryview`` object wrapping a buffer containing a
  31. HTTP response.
  32. :returns: A :class:`Response <hyper.http11.parser.Response>` object, or
  33. ``None`` if there is not enough data in the buffer.
  34. """
  35. # Begin by copying the data out of the buffer. This is necessary
  36. # because as much as possible we want to use the built-in bytestring
  37. # methods, rather than looping over the data in Python.
  38. temp_buffer = buffer.tobytes()
  39. index = temp_buffer.find(b'\n')
  40. if index == -1:
  41. return None
  42. version, status, reason = temp_buffer[0:index].split(None, 2)
  43. if not version.startswith(b'HTTP/1.'):
  44. raise ParseError("Not HTTP/1.X!")
  45. minor_version = int(version[7:])
  46. status = int(status)
  47. reason = memoryview(reason.strip())
  48. # Chomp the newline.
  49. index += 1
  50. # Now, parse the headers out.
  51. end_index = index
  52. headers = []
  53. while True:
  54. end_index = temp_buffer.find(b'\n', index)
  55. if end_index == -1:
  56. return None
  57. elif (end_index - index) <= 1:
  58. # Chomp the newline
  59. end_index += 1
  60. break
  61. name, value = temp_buffer[index:end_index].split(b':', 1)
  62. value = value.strip()
  63. headers.append((memoryview(name), memoryview(value)))
  64. index = end_index + 1
  65. resp = Response(status, reason, minor_version, headers, end_index)
  66. return resp