108 lines
3.5 KiB
Python
108 lines
3.5 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
hpack/hpack_compat
|
|
~~~~~~~~~~~~~~~~~~
|
|
|
|
Provides an abstraction layer over two HPACK implementations.
|
|
|
|
This module has a pure-Python greenfield HPACK implementation that can be used
|
|
on all Python platforms. However, this implementation is both slower and more
|
|
memory-hungry than could be achieved with a C-language version. Additionally,
|
|
nghttp2's HPACK implementation currently achieves better compression ratios
|
|
than hyper's in almost all benchmarks.
|
|
|
|
For those who care about efficiency and speed in HPACK, this module allows you
|
|
to use nghttp2's HPACK implementation instead of ours. This module detects
|
|
whether the nghttp2 bindings are installed, and if they are it wraps them in
|
|
a hpack-compatible API and uses them instead of its own. If not, it falls back
|
|
to the built-in Python bindings.
|
|
"""
|
|
import logging
|
|
from .hpack import _to_bytes
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
# Attempt to import nghttp2.
|
|
try:
|
|
import nghttp2
|
|
USE_NGHTTP2 = True
|
|
log.debug("Using nghttp2's HPACK implementation.")
|
|
except ImportError:
|
|
USE_NGHTTP2 = False
|
|
log.debug("Using our pure-Python HPACK implementation.")
|
|
|
|
if USE_NGHTTP2:
|
|
class Encoder(object):
|
|
"""
|
|
An HPACK encoder object. This object takes HTTP headers and emits
|
|
encoded HTTP/2 header blocks.
|
|
"""
|
|
def __init__(self):
|
|
self._e = nghttp2.HDDeflater()
|
|
|
|
@property
|
|
def header_table_size(self):
|
|
"""
|
|
Returns the header table size. For the moment this isn't
|
|
useful, so we don't use it.
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
@header_table_size.setter
|
|
def header_table_size(self, value):
|
|
log.debug("Setting header table size to %d", value)
|
|
self._e.change_table_size(value)
|
|
|
|
def encode(self, headers, huffman=True):
|
|
"""
|
|
Encode the headers. The huffman parameter has no effect, it is
|
|
simply present for compatibility.
|
|
"""
|
|
log.debug("HPACK encoding %s", headers)
|
|
|
|
# Turn the headers into a list of tuples if possible. This is the
|
|
# natural way to interact with them in HPACK.
|
|
if isinstance(headers, dict):
|
|
headers = headers.items()
|
|
|
|
# Next, walk across the headers and turn them all into bytestrings.
|
|
headers = [(_to_bytes(n), _to_bytes(v)) for n, v in headers]
|
|
|
|
# Now, let nghttp2 do its thing.
|
|
header_block = self._e.deflate(headers)
|
|
|
|
return header_block
|
|
|
|
class Decoder(object):
|
|
"""
|
|
An HPACK decoder object.
|
|
"""
|
|
def __init__(self):
|
|
self._d = nghttp2.HDInflater()
|
|
|
|
@property
|
|
def header_table_size(self):
|
|
"""
|
|
Returns the header table size. For the moment this isn't
|
|
useful, so we don't use it.
|
|
"""
|
|
raise NotImplementedError()
|
|
|
|
@header_table_size.setter
|
|
def header_table_size(self, value):
|
|
log.debug("Setting header table size to %d", value)
|
|
self._d.change_table_size(value)
|
|
|
|
def decode(self, data):
|
|
"""
|
|
Takes an HPACK-encoded header block and decodes it into a header
|
|
set.
|
|
"""
|
|
log.debug("Decoding %s", data)
|
|
|
|
headers = self._d.inflate(data)
|
|
return [(n.decode('utf-8'), v.decode('utf-8')) for n, v in headers]
|
|
else:
|
|
# Grab the built-in encoder and decoder.
|
|
from .hpack import Encoder, Decoder
|