From b4e02f6bc34228831a42ed1fea8b070f3392b766 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 20 May 2015 12:26:09 +0530 Subject: [PATCH] Tests for ETag and gzip --- src/calibre/srv/http.py | 4 +++- src/calibre/srv/opts.py | 4 ++++ src/calibre/srv/respond.py | 7 +++++-- src/calibre/srv/tests/base.py | 4 +++- src/calibre/srv/tests/http.py | 27 ++++++++++++++++++++++++++- 5 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/calibre/srv/http.py b/src/calibre/srv/http.py index 0b26f8dd6b..3c0003a5ea 100644 --- a/src/calibre/srv/http.py +++ b/src/calibre/srv/http.py @@ -543,7 +543,9 @@ class HTTPPair(object): self.status_code = httplib.CREATED if self.method == 'POST' else httplib.OK try: - self.status_code, output = finalize_output(output, self.inheaders, self.outheaders, self.status_code, self.response_protocol is HTTP1, self.method) + self.status_code, output = finalize_output( + output, self.inheaders, self.outheaders, self.status_code, + self.response_protocol is HTTP1, self.method, self.server_loop.opts.compress_min_size) except IfNoneMatch as e: if self.method in ('GET', 'HEAD'): self.send_not_modified(e.etag) diff --git a/src/calibre/srv/opts.py b/src/calibre/srv/opts.py index 1fa5ef079d..38b1b6d7dc 100644 --- a/src/calibre/srv/opts.py +++ b/src/calibre/srv/opts.py @@ -59,6 +59,10 @@ raw_options = ( 'max_request_body_size', 500.0, None, + 'Minimum size for which responses use data compression (in bytes)', + 'compress_min_size', 1024, + None, + 'Decrease latency by using the TCP_NODELAY feature', 'no_delay', True, 'no_delay turns on TCP_NODELAY which decreases latency at the cost of' diff --git a/src/calibre/srv/respond.py b/src/calibre/srv/respond.py index de02abbc06..d2e4e23a52 100644 --- a/src/calibre/srv/respond.py +++ b/src/calibre/srv/respond.py @@ -124,6 +124,8 @@ class GeneratedOutput(object): class StaticGeneratedOutput(object): def __init__(self, data): + if isinstance(data, type('')): + data = data.encode('utf-8') self.data = data self.etag = '"%s"' % hashlib.sha1(data).hexdigest() self.content_length = len(data) @@ -145,7 +147,7 @@ def generate_static_output(cache, gso_lock, name, generator): def parse_if_none_match(val): return {x.strip() for x in val.split(',')} -def finalize_output(output, inheaders, outheaders, status_code, is_http1, method): +def finalize_output(output, inheaders, outheaders, status_code, is_http1, method, compress_min_size): ct = outheaders.get('Content-Type', '') compressible = not ct or ct.startswith('text/') or ct.startswith('image/svg') or ct.startswith('application/json') if isinstance(output, file): @@ -156,7 +158,8 @@ def finalize_output(output, inheaders, outheaders, status_code, is_http1, method pass else: output = GeneratedOutput(output, outheaders) - compressible = (status_code == httplib.OK and compressible and output.content_length > 1024 and + compressible = (status_code == httplib.OK and compressible and + (compress_min_size > -1 and output.content_length >= compress_min_size) and acceptable_encoding(inheaders.get('Accept-Encoding', '')) and not is_http1) accept_ranges = (not compressible and output.accept_ranges is not None and status_code == httplib.OK and not is_http1) diff --git a/src/calibre/srv/tests/base.py b/src/calibre/srv/tests/base.py index 65d36985cd..42d162848f 100644 --- a/src/calibre/srv/tests/base.py +++ b/src/calibre/srv/tests/base.py @@ -61,7 +61,9 @@ class TestServer(Thread): def __exit__(self, *args): self.loop.stop() - def connect(self, timeout=0.1): + def connect(self, timeout=None): + if timeout is None: + timeout = self.loop.opts.timeout return httplib.HTTPConnection(self.address[0], self.address[1], strict=True, timeout=timeout) def change_handler(self, handler): diff --git a/src/calibre/srv/tests/http.py b/src/calibre/srv/tests/http.py index 08dc31da1b..cb56b4c442 100644 --- a/src/calibre/srv/tests/http.py +++ b/src/calibre/srv/tests/http.py @@ -6,7 +6,7 @@ from __future__ import (unicode_literals, division, absolute_import, __license__ = 'GPL v3' __copyright__ = '2015, Kovid Goyal ' -import textwrap, httplib +import textwrap, httplib, hashlib, zlib from io import BytesIO from calibre.srv.tests.base import BaseTest, TestServer @@ -159,3 +159,28 @@ class TestHTTP(BaseTest): self.ae(server.loop.requests.idle, 10) # }}} + + def test_http_response(self): # {{{ + 'Test HTTP protocol responses' + def handler(conn): + return conn.generate_static_output('test', lambda : ''.join(conn.path)) + with TestServer(handler, timeout=0.1, compress_min_size=0) as server: + # Test ETag + conn = server.connect() + conn.request('GET', '/an_etagged_path') + r = conn.getresponse() + self.ae(r.status, httplib.OK), self.ae(r.read(), b'an_etagged_path') + etag = r.getheader('ETag') + self.ae(etag, '"%s"' % hashlib.sha1('an_etagged_path').hexdigest()) + conn.request('GET', '/an_etagged_path', headers={'If-None-Match':etag}) + r = conn.getresponse() + self.ae(r.status, httplib.NOT_MODIFIED) + self.ae(r.read(), b'') + + # Test gzip + conn.request('GET', '/an_etagged_path', headers={'Accept-Encoding':'gzip'}) + r = conn.getresponse() + self.ae(r.status, httplib.OK), self.ae(zlib.decompress(r.read(), 16+zlib.MAX_WBITS), b'an_etagged_path') + + # }}} +