hpack_compat.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. # -*- coding: utf-8 -*-
  2. """
  3. hpack/hpack_compat
  4. ~~~~~~~~~~~~~~~~~~
  5. Provides an abstraction layer over two HPACK implementations.
  6. This module has a pure-Python greenfield HPACK implementation that can be used
  7. on all Python platforms. However, this implementation is both slower and more
  8. memory-hungry than could be achieved with a C-language version. Additionally,
  9. nghttp2's HPACK implementation currently achieves better compression ratios
  10. than hyper's in almost all benchmarks.
  11. For those who care about efficiency and speed in HPACK, this module allows you
  12. to use nghttp2's HPACK implementation instead of ours. This module detects
  13. whether the nghttp2 bindings are installed, and if they are it wraps them in
  14. a hpack-compatible API and uses them instead of its own. If not, it falls back
  15. to the built-in Python bindings.
  16. """
  17. import logging
  18. from .hpack import _to_bytes
  19. log = logging.getLogger(__name__)
  20. # Attempt to import nghttp2.
  21. try:
  22. import nghttp2
  23. USE_NGHTTP2 = True
  24. log.debug("Using nghttp2's HPACK implementation.")
  25. except ImportError:
  26. USE_NGHTTP2 = False
  27. log.debug("Using our pure-Python HPACK implementation.")
  28. if USE_NGHTTP2: # noqa
  29. class Encoder(object):
  30. """
  31. An HPACK encoder object. This object takes HTTP headers and emits
  32. encoded HTTP/2 header blocks.
  33. """
  34. def __init__(self):
  35. self._e = nghttp2.HDDeflater()
  36. @property
  37. def header_table_size(self):
  38. """
  39. Returns the header table size. For the moment this isn't
  40. useful, so we don't use it.
  41. """
  42. raise NotImplementedError()
  43. @header_table_size.setter
  44. def header_table_size(self, value):
  45. log.debug("Setting header table size to %d", value)
  46. self._e.change_table_size(value)
  47. def encode(self, headers, huffman=True):
  48. """
  49. Encode the headers. The huffman parameter has no effect, it is
  50. simply present for compatibility.
  51. """
  52. log.debug("HPACK encoding %s", headers)
  53. # Turn the headers into a list of tuples if possible. This is the
  54. # natural way to interact with them in HPACK.
  55. if isinstance(headers, dict):
  56. headers = headers.items()
  57. # Next, walk across the headers and turn them all into bytestrings.
  58. headers = [(_to_bytes(n), _to_bytes(v)) for n, v in headers]
  59. # Now, let nghttp2 do its thing.
  60. header_block = self._e.deflate(headers)
  61. return header_block
  62. class Decoder(object):
  63. """
  64. An HPACK decoder object.
  65. """
  66. def __init__(self):
  67. self._d = nghttp2.HDInflater()
  68. @property
  69. def header_table_size(self):
  70. """
  71. Returns the header table size. For the moment this isn't
  72. useful, so we don't use it.
  73. """
  74. raise NotImplementedError()
  75. @header_table_size.setter
  76. def header_table_size(self, value):
  77. log.debug("Setting header table size to %d", value)
  78. self._d.change_table_size(value)
  79. def decode(self, data):
  80. """
  81. Takes an HPACK-encoded header block and decodes it into a header
  82. set.
  83. """
  84. log.debug("Decoding %s", data)
  85. headers = self._d.inflate(data)
  86. return [(n.decode('utf-8'), v.decode('utf-8')) for n, v in headers]
  87. else:
  88. # Grab the built-in encoder and decoder.
  89. from .hpack import Encoder, Decoder # noqa