response.py 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. # -*- coding: utf-8 -*-
  2. """
  3. hyper/http20/response
  4. ~~~~~~~~~~~~~~~~~~~~~
  5. Contains the HTTP/2 equivalent of the HTTPResponse object defined in
  6. httplib/http.client.
  7. """
  8. import logging
  9. import zlib
  10. from ..common.decoder import DeflateDecoder
  11. from ..common.headers import HTTPHeaderMap
  12. log = logging.getLogger(__name__)
  13. def strip_headers(headers):
  14. """
  15. Strips the headers attached to the instance of any header beginning
  16. with a colon that ``hyper`` doesn't understand. This method logs at
  17. warning level about the deleted headers, for discoverability.
  18. """
  19. # Convert to list to ensure that we don't mutate the headers while
  20. # we iterate over them.
  21. for name in list(headers.keys()):
  22. if name.startswith(b':'):
  23. del headers[name]
  24. class HTTP20Response(object):
  25. """
  26. An ``HTTP20Response`` wraps the HTTP/2 response from the server. It
  27. provides access to the response headers and the entity body. The response
  28. is an iterable object and can be used in a with statement (though due to
  29. the persistent connections used in HTTP/2 this has no effect, and is done
  30. soley for compatibility).
  31. """
  32. def __init__(self, headers, stream):
  33. #: The reason phrase returned by the server. This is not used in
  34. #: HTTP/2, and so is always the empty string.
  35. self.reason = ''
  36. status = headers[b':status'][0]
  37. strip_headers(headers)
  38. #: The status code returned by the server.
  39. self.status = int(status)
  40. #: The response headers. These are determined upon creation, assigned
  41. #: once, and never assigned again.
  42. self.headers = headers
  43. # The response trailers. These are always intially ``None``.
  44. self._trailers = None
  45. # The stream this response is being sent over.
  46. self._stream = stream
  47. # We always read in one-data-frame increments from the stream, so we
  48. # may need to buffer some for incomplete reads.
  49. self._data_buffer = b''
  50. # This object is used for decompressing gzipped request bodies. Right
  51. # now we only support gzip because that's all the RFC mandates of us.
  52. # Later we'll add support for more encodings.
  53. # This 16 + MAX_WBITS nonsense is to force gzip. See this
  54. # Stack Overflow answer for more:
  55. # http://stackoverflow.com/a/2695466/1401686
  56. if b'gzip' in self.headers.get(b'content-encoding', []):
  57. self._decompressobj = zlib.decompressobj(16 + zlib.MAX_WBITS)
  58. elif b'deflate' in self.headers.get(b'content-encoding', []):
  59. self._decompressobj = DeflateDecoder()
  60. else:
  61. self._decompressobj = None
  62. @property
  63. def trailers(self):
  64. """
  65. Trailers on the HTTP message, if any.
  66. .. warning:: Note that this property requires that the stream is
  67. totally exhausted. This means that, if you have not
  68. completely read from the stream, all stream data will be
  69. read into memory.
  70. """
  71. if self._trailers is None:
  72. self._trailers = self._stream.gettrailers() or HTTPHeaderMap()
  73. strip_headers(self._trailers)
  74. return self._trailers
  75. def read(self, amt=None, decode_content=True):
  76. """
  77. Reads the response body, or up to the next ``amt`` bytes.
  78. :param amt: (optional) The amount of data to read. If not provided, all
  79. the data will be read from the response.
  80. :param decode_content: (optional) If ``True``, will transparently
  81. decode the response data.
  82. :returns: The read data. Note that if ``decode_content`` is set to
  83. ``True``, the actual amount of data returned may be different to
  84. the amount requested.
  85. """
  86. if amt is not None and amt <= len(self._data_buffer):
  87. data = self._data_buffer[:amt]
  88. self._data_buffer = self._data_buffer[amt:]
  89. response_complete = False
  90. elif amt is not None:
  91. read_amt = amt - len(self._data_buffer)
  92. self._data_buffer += self._stream._read(read_amt)
  93. data = self._data_buffer[:amt]
  94. self._data_buffer = self._data_buffer[amt:]
  95. response_complete = len(data) < amt
  96. else:
  97. data = b''.join([self._data_buffer, self._stream._read()])
  98. response_complete = True
  99. # We may need to decode the body.
  100. if decode_content and self._decompressobj and data:
  101. data = self._decompressobj.decompress(data)
  102. # If we're at the end of the request, we have some cleaning up to do.
  103. # Close the stream, and if necessary flush the buffer.
  104. if response_complete:
  105. if decode_content and self._decompressobj:
  106. data += self._decompressobj.flush()
  107. if self._stream.response_headers:
  108. self.headers.merge(self._stream.response_headers)
  109. # We're at the end, close the connection.
  110. if response_complete:
  111. self.close()
  112. return data
  113. def read_chunked(self, decode_content=True):
  114. """
  115. Reads chunked transfer encoded bodies. This method returns a generator:
  116. each iteration of which yields one data frame *unless* the frames
  117. contain compressed data and ``decode_content`` is ``True``, in which
  118. case it yields whatever the decompressor provides for each chunk.
  119. .. warning:: This may yield the empty string, without that being the
  120. end of the body!
  121. """
  122. while True:
  123. data = self._stream._read_one_frame()
  124. if data is None:
  125. break
  126. if decode_content and self._decompressobj:
  127. data = self._decompressobj.decompress(data)
  128. yield data
  129. if decode_content and self._decompressobj:
  130. yield self._decompressobj.flush()
  131. self.close()
  132. return
  133. def fileno(self):
  134. """
  135. Return the ``fileno`` of the underlying socket. This function is
  136. currently not implemented.
  137. """
  138. raise NotImplementedError("Not currently implemented.")
  139. def close(self):
  140. """
  141. Close the response. In effect this closes the backing HTTP/2 stream.
  142. :returns: Nothing.
  143. """
  144. self._stream.close()
  145. # The following methods implement the context manager protocol.
  146. def __enter__(self):
  147. return self
  148. def __exit__(self, *args):
  149. self.close()
  150. return False # Never swallow exceptions.
  151. class HTTP20Push(object):
  152. """
  153. Represents a request-response pair sent by the server through the server
  154. push mechanism.
  155. """
  156. def __init__(self, request_headers, stream):
  157. #: The scheme of the simulated request
  158. self.scheme = request_headers[b':scheme'][0]
  159. #: The method of the simulated request (must be safe and cacheable,
  160. #: e.g. GET)
  161. self.method = request_headers[b':method'][0]
  162. #: The authority of the simulated request (usually host:port)
  163. self.authority = request_headers[b':authority'][0]
  164. #: The path of the simulated request
  165. self.path = request_headers[b':path'][0]
  166. strip_headers(request_headers)
  167. #: The headers the server attached to the simulated request.
  168. self.request_headers = request_headers
  169. self._stream = stream
  170. def get_response(self):
  171. """
  172. Get the pushed response provided by the server.
  173. :returns: A :class:`HTTP20Response <hyper.HTTP20Response>` object
  174. representing the pushed response.
  175. """
  176. return HTTP20Response(self._stream.getheaders(), self._stream)
  177. def cancel(self):
  178. """
  179. Cancel the pushed response and close the stream.
  180. :returns: Nothing.
  181. """
  182. self._stream.close(8) # CANCEL