Skip to content

Commit 5a54e66

Browse files
committed
Implement optional chunk encoding in ContentServer
This commit adds an implementation of chunk encoding to ContentServer. The decision of whether to apply chunking is controlled by an enum, which takes values YES (apply chunking), NO (don't apply chunking), or AUTO (apply chunking only if requested by the Transfer-Encoding header). I set the default value of the flag to NO for backwards compatibility, but it could be changed to AUTO in the future, which would probably make more sense.
1 parent d8b61f0 commit 5a54e66

File tree

1 file changed

+36
-2
lines changed

1 file changed

+36
-2
lines changed

pytest_localserver/http.py

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
# This program is release under the MIT license. You can find the full text of
44
# the license in the LICENSE file.
55

6+
import enum
7+
import itertools
68
import json
79
import sys
810
import threading
@@ -40,6 +42,21 @@ def url(self):
4042
return '%s://%s:%i' % (proto, host, port)
4143

4244

45+
class Chunked(enum.Enum):
46+
NO = False
47+
YES = True
48+
AUTO = None
49+
50+
def __bool__(self):
51+
return bool(self.value)
52+
53+
54+
def _encode_chunk(chunk, charset):
55+
if isinstance(chunk, str):
56+
chunk = chunk.encode(charset)
57+
return '{0:x}'.format(len(chunk)).encode(charset) + b'\r\n' + chunk + b'\r\n'
58+
59+
4360
class ContentServer(WSGIServer):
4461

4562
"""
@@ -68,6 +85,7 @@ def __init__(self, host='127.0.0.1', port=0, ssl_context=None):
6885
self.show_post_vars = False
6986
self.compress = None
7087
self.requests = []
88+
self.chunked = Chunked.NO
7189

7290
def __call__(self, environ, start_response):
7391
"""
@@ -81,7 +99,21 @@ def __call__(self, environ, start_response):
8199
else:
82100
content = self.content
83101

84-
response = Response(response=self.content, status=self.code)
102+
if (
103+
self.chunked == Chunked.YES
104+
or (self.chunked == Chunked.AUTO and 'chunked' in self.headers.get('Transfer-encoding', ''))
105+
):
106+
# If the code below ever changes to allow setting the charset of
107+
# the Response object, the charset used here should also be changed
108+
# to match. But until that happens, use UTF-8 since it is Werkzeug's
109+
# default.
110+
charset = 'utf-8'
111+
if isinstance(content, (str, bytes)):
112+
content = (_encode_chunk(content, charset), '0\r\n\r\n')
113+
else:
114+
content = itertools.chain((_encode_chunk(item, charset) for item in content), ['0\r\n\r\n'])
115+
116+
response = Response(response=content, status=self.code)
85117
response.headers.clear()
86118
response.headers.extend(self.headers)
87119

@@ -92,14 +124,15 @@ def __call__(self, environ, start_response):
92124

93125
return response(environ, start_response)
94126

95-
def serve_content(self, content, code=200, headers=None):
127+
def serve_content(self, content, code=200, headers=None, chunked=Chunked.NO):
96128
"""
97129
Serves string content (with specified HTTP error code) as response to
98130
all subsequent request.
99131
100132
:param content: content to be displayed
101133
:param code: HTTP status code
102134
:param headers: HTTP headers to be returned
135+
:param chunked: whether to apply chunked transfer encoding to the content
103136
"""
104137
if not isinstance(content, (str, bytes, list, tuple)):
105138
# If content is an iterable which is not known to be a string,
@@ -114,6 +147,7 @@ def serve_content(self, content, code=200, headers=None):
114147
pass
115148
self.content = content
116149
self.code = code
150+
self.chunked = chunked
117151
if headers:
118152
self.headers = Headers(headers)
119153

0 commit comments

Comments
 (0)