Tests for ETag and gzip

This commit is contained in:
Kovid Goyal 2015-05-20 12:26:09 +05:30
parent 8cc8d83a38
commit b4e02f6bc3
5 changed files with 41 additions and 5 deletions

View File

@ -543,7 +543,9 @@ class HTTPPair(object):
self.status_code = httplib.CREATED if self.method == 'POST' else httplib.OK self.status_code = httplib.CREATED if self.method == 'POST' else httplib.OK
try: 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: except IfNoneMatch as e:
if self.method in ('GET', 'HEAD'): if self.method in ('GET', 'HEAD'):
self.send_not_modified(e.etag) self.send_not_modified(e.etag)

View File

@ -59,6 +59,10 @@ raw_options = (
'max_request_body_size', 500.0, 'max_request_body_size', 500.0,
None, None,
'Minimum size for which responses use data compression (in bytes)',
'compress_min_size', 1024,
None,
'Decrease latency by using the TCP_NODELAY feature', 'Decrease latency by using the TCP_NODELAY feature',
'no_delay', True, 'no_delay', True,
'no_delay turns on TCP_NODELAY which decreases latency at the cost of' 'no_delay turns on TCP_NODELAY which decreases latency at the cost of'

View File

@ -124,6 +124,8 @@ class GeneratedOutput(object):
class StaticGeneratedOutput(object): class StaticGeneratedOutput(object):
def __init__(self, data): def __init__(self, data):
if isinstance(data, type('')):
data = data.encode('utf-8')
self.data = data self.data = data
self.etag = '"%s"' % hashlib.sha1(data).hexdigest() self.etag = '"%s"' % hashlib.sha1(data).hexdigest()
self.content_length = len(data) self.content_length = len(data)
@ -145,7 +147,7 @@ def generate_static_output(cache, gso_lock, name, generator):
def parse_if_none_match(val): def parse_if_none_match(val):
return {x.strip() for x in val.split(',')} 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', '') ct = outheaders.get('Content-Type', '')
compressible = not ct or ct.startswith('text/') or ct.startswith('image/svg') or ct.startswith('application/json') compressible = not ct or ct.startswith('text/') or ct.startswith('image/svg') or ct.startswith('application/json')
if isinstance(output, file): if isinstance(output, file):
@ -156,7 +158,8 @@ def finalize_output(output, inheaders, outheaders, status_code, is_http1, method
pass pass
else: else:
output = GeneratedOutput(output, outheaders) 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) 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 accept_ranges = (not compressible and output.accept_ranges is not None and status_code == httplib.OK and
not is_http1) not is_http1)

View File

@ -61,7 +61,9 @@ class TestServer(Thread):
def __exit__(self, *args): def __exit__(self, *args):
self.loop.stop() 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) return httplib.HTTPConnection(self.address[0], self.address[1], strict=True, timeout=timeout)
def change_handler(self, handler): def change_handler(self, handler):

View File

@ -6,7 +6,7 @@ from __future__ import (unicode_literals, division, absolute_import,
__license__ = 'GPL v3' __license__ = 'GPL v3'
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>' __copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
import textwrap, httplib import textwrap, httplib, hashlib, zlib
from io import BytesIO from io import BytesIO
from calibre.srv.tests.base import BaseTest, TestServer from calibre.srv.tests.base import BaseTest, TestServer
@ -159,3 +159,28 @@ class TestHTTP(BaseTest):
self.ae(server.loop.requests.idle, 10) 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')
# }}}