Skip to content
This repository has been archived by the owner on Jun 1, 2018. It is now read-only.

Commit

Permalink
refactor response model
Browse files Browse the repository at this point in the history
  • Loading branch information
mhils committed Sep 26, 2015
1 parent 106f704 commit 49ea8fc
Show file tree
Hide file tree
Showing 16 changed files with 293 additions and 274 deletions.
15 changes: 6 additions & 9 deletions netlib/http/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
from __future__ import absolute_import, print_function, division
from .headers import Headers
from .message import decoded
from .request import Request
from .models import Response
from .models import ALPN_PROTO_HTTP1, ALPN_PROTO_H2
from .models import HDR_FORM_MULTIPART, HDR_FORM_URLENCODED, CONTENT_MISSING
from .response import Response
from .headers import Headers
from .message import decoded, CONTENT_MISSING
from . import http1, http2

__all__ = [
"Request",
"Response",
"Headers",
"decoded",
"Request", "Response",
"ALPN_PROTO_HTTP1", "ALPN_PROTO_H2",
"HDR_FORM_MULTIPART", "HDR_FORM_URLENCODED", "CONTENT_MISSING",
"decoded", "CONTENT_MISSING",
"http1", "http2",
]
26 changes: 13 additions & 13 deletions netlib/http/headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,8 @@ class Headers(MutableMapping):
.. code-block:: python
# Create header from a list of (header_name, header_value) tuples
>>> h = Headers([
["Host","example.com"],
["Accept","text/html"],
["accept","application/xml"]
])
# Create headers with keyword arguments
>>> h = Headers(host="example.com", content_type="application/xml")
# Headers mostly behave like a normal dict.
>>> h["Host"]
Expand All @@ -51,6 +47,13 @@ class Headers(MutableMapping):
>>> h["host"]
"example.com"
# Headers can also be creatd from a list of raw (header_name, header_value) byte tuples
>>> h = Headers([
[b"Host",b"example.com"],
[b"Accept",b"text/html"],
[b"accept",b"application/xml"]
])
# Multiple headers are folded into a single header as per RFC7230
>>> h["Accept"]
"text/html, application/xml"
Expand All @@ -60,17 +63,14 @@ class Headers(MutableMapping):
>>> h["Accept"]
"application/text"
# str(h) returns a HTTP1 header block.
>>> print(h)
# bytes(h) returns a HTTP1 header block.
>>> print(bytes(h))
Host: example.com
Accept: application/text
# For full control, the raw header fields can be accessed
>>> h.fields
# Headers can also be crated from keyword arguments
>>> h = Headers(host="example.com", content_type="application/xml")
Caveats:
For use with the "Set-Cookie" header, see :py:meth:`get_all`.
"""
Expand All @@ -79,8 +79,8 @@ class Headers(MutableMapping):
def __init__(self, fields=None, **headers):
"""
Args:
fields: (optional) list of ``(name, value)`` header tuples,
e.g. ``[("Host","example.com")]``. All names and values must be bytes.
fields: (optional) list of ``(name, value)`` header byte tuples,
e.g. ``[(b"Host", b"example.com")]``. All names and values must be bytes.
**headers: Additional headers to set. Will overwrite existing values from `fields`.
For convenience, underscores in header names will be transformed to dashes -
this behaviour does not extend to other methods.
Expand Down
16 changes: 8 additions & 8 deletions netlib/http/http1/assemble.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ def assemble_request(request):
if request.content == CONTENT_MISSING:
raise HttpException("Cannot assemble flow with CONTENT_MISSING")
head = assemble_request_head(request)
body = b"".join(assemble_body(request.headers, [request.data.content]))
body = b"".join(assemble_body(request.data.headers, [request.data.content]))
return head + body


Expand All @@ -24,13 +24,13 @@ def assemble_response(response):
if response.content == CONTENT_MISSING:
raise HttpException("Cannot assemble flow with CONTENT_MISSING")
head = assemble_response_head(response)
body = b"".join(assemble_body(response.headers, [response.content]))
body = b"".join(assemble_body(response.data.headers, [response.data.content]))
return head + body


def assemble_response_head(response):
first_line = _assemble_response_line(response)
headers = _assemble_response_headers(response)
first_line = _assemble_response_line(response.data)
headers = _assemble_response_headers(response.data)
return b"%s\r\n%s\r\n" % (first_line, headers)


Expand Down Expand Up @@ -92,11 +92,11 @@ def _assemble_request_headers(request_data):
return bytes(headers)


def _assemble_response_line(response):
def _assemble_response_line(response_data):
return b"%s %d %s" % (
response.http_version,
response.status_code,
response.msg,
response_data.http_version,
response_data.status_code,
response_data.reason,
)


Expand Down
2 changes: 1 addition & 1 deletion netlib/http/http1/read.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def read_request_head(rfile):
def read_response(rfile, request, body_size_limit=None):
response = read_response_head(rfile)
expected_body_size = expected_http_body_size(request, response)
response._body = b"".join(read_body(rfile, expected_body_size, body_size_limit))
response.data.content = b"".join(read_body(rfile, expected_body_size, body_size_limit))
response.timestamp_end = time.time()
return response

Expand Down
4 changes: 2 additions & 2 deletions netlib/http/http2/connections.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from hpack.hpack import Encoder, Decoder
from ... import utils
from .. import Headers, Response, Request, ALPN_PROTO_H2
from .. import Headers, Response, Request
from . import frame


Expand Down Expand Up @@ -283,7 +283,7 @@ def read_frame(self, hide=False):

def check_alpn(self):
alp = self.tcp_handler.get_alpn_proto_negotiated()
if alp != ALPN_PROTO_H2:
if alp != b'h2':
raise NotImplementedError(
"HTTP2Protocol can not handle unknown ALP: %s" % alp)
return True
Expand Down
3 changes: 0 additions & 3 deletions netlib/http/http2/frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@

CLIENT_CONNECTION_PREFACE = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"

ALPN_PROTO_H2 = b'h2'


class Frame(object):

"""
Expand Down
64 changes: 36 additions & 28 deletions netlib/http/message.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@

from .. import encoding, utils


CONTENT_MISSING = 0

if six.PY2:
_native = lambda x: x
_always_bytes = lambda x: x
else:
# While headers _should_ be ASCII, it's not uncommon for certain headers to be utf-8 encoded.
# While the HTTP head _should_ be ASCII, it's not uncommon for certain headers to be utf-8 encoded.
_native = lambda x: x.decode("utf-8", "surrogateescape")
_always_bytes = lambda x: utils.always_bytes(x, "utf-8", "surrogateescape")

Expand All @@ -27,17 +30,6 @@ def __eq__(self, other):
def __ne__(self, other):
return not self.__eq__(other)

@property
def http_version(self):
"""
Version string, e.g. "HTTP/1.1"
"""
return _native(self.data.http_version)

@http_version.setter
def http_version(self, http_version):
self.data.http_version = _always_bytes(http_version)

@property
def headers(self):
"""
Expand All @@ -52,6 +44,32 @@ def headers(self):
def headers(self, h):
self.data.headers = h

@property
def content(self):
"""
The raw (encoded) HTTP message body
See also: :py:attr:`text`
"""
return self.data.content

@content.setter
def content(self, content):
self.data.content = content
if isinstance(content, bytes):
self.headers["content-length"] = str(len(content))

@property
def http_version(self):
"""
Version string, e.g. "HTTP/1.1"
"""
return _native(self.data.http_version)

@http_version.setter
def http_version(self, http_version):
self.data.http_version = _always_bytes(http_version)

@property
def timestamp_start(self):
"""
Expand All @@ -74,26 +92,14 @@ def timestamp_end(self):
def timestamp_end(self, timestamp_end):
self.data.timestamp_end = timestamp_end

@property
def content(self):
"""
The raw (encoded) HTTP message body
See also: :py:attr:`text`
"""
return self.data.content

@content.setter
def content(self, content):
self.data.content = content
if isinstance(content, bytes):
self.headers["content-length"] = str(len(content))

@property
def text(self):
"""
The decoded HTTP message body.
Decoded contents are not cached, so this method is relatively expensive to call.
Decoded contents are not cached, so accessing this attribute repeatedly is relatively expensive.
.. note::
This is not implemented yet.
See also: :py:attr:`content`, :py:class:`decoded`
"""
Expand All @@ -104,6 +110,8 @@ def text(self):
def text(self, text):
raise NotImplementedError()

# Legacy

@property
def body(self):
warnings.warn(".body is deprecated, use .content instead.", DeprecationWarning)
Expand Down
112 changes: 0 additions & 112 deletions netlib/http/models.py

This file was deleted.

0 comments on commit 49ea8fc

Please sign in to comment.