Skip to content

Commit 3c44006

Browse files
committed
Added a pre-processing decorator at api.response.message_extractor
This is to aid custom ``response_callback`` implementation for use in application bindings like ``wsgi.InterceptorMiddleware`` and ``cgi.InterceptorController``. It simplifies the need to pre-process response body data and ensures consistency through hiding details.
1 parent bd2e378 commit 3c44006

File tree

5 files changed

+50
-9
lines changed

5 files changed

+50
-9
lines changed

src/SnapSearch/api/response.py

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@
1212
:date: 2014/03/08
1313
"""
1414

15-
__all__ = ['Response', ]
15+
__all__ = ['Response', 'message_extractor', ]
1616

1717

18+
import functools
19+
1820
from .._compat import u, HTTP_STATUS_CODES
1921

2022

@@ -28,6 +30,8 @@ def _extract_message(response_body):
2830
payload = response_body.get('html', u("")).encode("utf-8")
2931

3032
# response headers
33+
found_content_length = False
34+
3135
headers = []
3236
for item in response_body.get('headers', []):
3337

@@ -44,19 +48,32 @@ def _extract_message(response_body):
4448
str(item['value']).encode("utf-8"))
4549

4650
# dirty entry (needs to be fixed later)
47-
if tup[0] in (b"content-type", b"content-length"):
51+
if tup[0] == b"content-length":
52+
found_content_length = True
4853
continue
4954

5055
# selected entry
5156
headers.append(tup)
5257

53-
# fixed header entries
54-
headers.append((b"content-type", b"text/html"))
55-
headers.append((b"content-length", bytes(len(payload))))
58+
# fixed content-length to avoid truncation of content by the web server in
59+
# case the headers are to be replicated in an HTTP response message.
60+
if found_content_length:
61+
headers.append((b"content-length", bytes(len(payload))))
5662

5763
return {'status': status, 'headers': headers, 'html': payload}
5864

5965

66+
def message_extractor(func):
67+
"""
68+
Decorator for data extracting functions requiring the HTTP message fields
69+
from a raw response body. This is an input preprocessing decorator for the
70+
implementation of ``response_callback`` used in application bindings.
71+
"""
72+
def wrapper(response_body):
73+
return func(_extract_message(response_body))
74+
return functools.update_wrapper(wrapper, func)
75+
76+
6077
class Response(object):
6178
"""
6279
Wraps an HTTP response from SnapSearch's backend service.

src/SnapSearch/cgi.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@
2222
from .interceptor import Interceptor
2323

2424

25+
@api.response.message_extractor
26+
def default_response_callback(response_body):
27+
"""
28+
Removes staled HTTP headers from response body
29+
"""
30+
response_body['headers'] = [
31+
(key, val) for key, val in response_body['headers']
32+
if key.lower() in (b"location", b"server", b"status")]
33+
return response_body
34+
35+
2536
class InterceptorController(object):
2637
"""
2738
Wraps a CGI script (see :RFC:`3875`) by temporarily buffering its standard
@@ -71,7 +82,7 @@ def __init__(self, interceptor, response_callback=None):
7182
#
7283
self.__interceptor = interceptor
7384
self.__response_callback = response_callback \
74-
if callable(response_callback) else api.response._extract_message
85+
if callable(response_callback) else default_response_callback
7586
self.__real_stdout = None
7687
self.__stdout_buffer = None
7788
pass

src/SnapSearch/wsgi.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@
2121
from .interceptor import Interceptor
2222

2323

24+
@api.response.message_extractor
25+
def default_response_callback(response_body):
26+
"""
27+
Removes staled HTTP headers from response body
28+
"""
29+
response_body['headers'] = [
30+
(key, val) for key, val in response_body['headers']
31+
if key.lower() in (b"location", b"server", b"status")]
32+
return response_body
33+
34+
2435
class InterceptorMiddleware(object):
2536
"""
2637
Wraps a WSGI-defined web application (see :PEP:`3333`) and intercepts
@@ -77,7 +88,7 @@ def __init__(self, application, interceptor, response_callback=None):
7788
self.__application = application
7889
self.__interceptor = interceptor
7990
self.__response_callback = response_callback \
80-
if callable(response_callback) else api.response._extract_message
91+
if callable(response_callback) else default_response_callback
8192
pass
8293

8394
def __call__(self, environ, start_response):

tests/test_cgi.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ def test_controller_init(self):
6262
from SnapSearch.cgi import InterceptorController
6363
i = Interceptor(self.client, self.detector)
6464
cb = lambda r: {b"status": 200,
65-
b"headers": [(b"Content-Type", b"text/html"), ],
65+
b"headers": [(b"Content-Type", b"text/html"),
66+
(b"Server", b"apache (CentOS)"), ],
6667
b"html": b"Hello World!\r\n", }
6768
ic = InterceptorController(i, cb)
6869
self.assertEqual(i, ic.interceptor)

tests/test_wsgi.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ def test_middleware_init(self):
7474
a = DummyApp()
7575
i = Interceptor(self.client, self.detector)
7676
cb = lambda r: {b"status": 200,
77-
b"headers": [(b"Content-Type", b"text/html"), ],
77+
b"headers": [(b"Content-Type", b"text/html"),
78+
(b"Server", b"apache (CentOS)"), ],
7879
b"html": b"Hello World!\r\n", }
7980
im = InterceptorMiddleware(a, i, cb)
8081
self.assertEqual(a, im.application)

0 commit comments

Comments
 (0)