diff --git a/src/calibre/srv/errors.py b/src/calibre/srv/errors.py index 2a120a316a..621ddbb330 100644 --- a/src/calibre/srv/errors.py +++ b/src/calibre/srv/errors.py @@ -6,9 +6,25 @@ from __future__ import (unicode_literals, division, absolute_import, __license__ = 'GPL v3' __copyright__ = '2015, Kovid Goyal ' - -class HTTP404(Exception): - pass +import httplib class JobQueueFull(Exception): pass + +class HTTPSimpleResponse(Exception): + + def __init__(self, http_code, http_message='', close_connection=False, location=None): + Exception.__init__(self, http_message) + self.http_code = http_code + self.close_connection = close_connection + self.location = location + +class HTTPRedirect(HTTPSimpleResponse): + + def __init__(self, location, http_code=httplib.MOVED_PERMANENTLY, http_message='', close_connection=False): + HTTPSimpleResponse.__init__(self, http_code, http_message, close_connection, location) + +class HTTPNotFound(HTTPSimpleResponse): + + def __init__(self, http_message='', close_connection=False): + HTTPSimpleResponse.__init__(self, httplib.NOT_FOUND, http_message, close_connection) diff --git a/src/calibre/srv/http_response.py b/src/calibre/srv/http_response.py index 256ccc8d5e..d74f37a514 100644 --- a/src/calibre/srv/http_response.py +++ b/src/calibre/srv/http_response.py @@ -16,7 +16,7 @@ from functools import wraps from calibre import guess_type, force_unicode from calibre.constants import __version__ from calibre.srv.loop import WRITE -from calibre.srv.errors import HTTP404 +from calibre.srv.errors import HTTPSimpleResponse from calibre.srv.http_request import HTTPRequest, read_headers from calibre.srv.sendfile import file_metadata, sendfile_to_socket_async, CannotSendfile, SendfileInterrupted from calibre.srv.utils import MultiDict, http_date, HTTP1, HTTP11, socket_errors_socket_closed @@ -284,10 +284,15 @@ class HTTPConnection(HTTPRequest): buf.seek(pos + sent) return buf.tell() == end - def simple_response(self, status_code, msg='', close_after_response=True): - if self.response_protocol is HTTP1 and status_code in (httplib.REQUEST_ENTITY_TOO_LARGE, httplib.REQUEST_URI_TOO_LONG): - # HTTP/1.0 has no 413/414 codes - status_code = httplib.BAD_REQUEST + def simple_response(self, status_code, msg='', close_after_response=True, extra_headers=None): + if self.response_protocol is HTTP1: + # HTTP/1.0 has no 413/414/303 codes + status_code = { + httplib.REQUEST_ENTITY_TOO_LARGE:httplib.BAD_REQUEST, + httplib.REQUEST_URI_TOO_LONG:httplib.BAD_REQUEST, + httplib.SEE_OTHER:httplib.FOUND + }.get(status_code, status_code) + self.close_after_response = close_after_response msg = msg.encode('utf-8') ct = 'http' if self.method == 'TRACE' else 'plain' @@ -299,6 +304,9 @@ class HTTPConnection(HTTPRequest): ] if self.close_after_response and self.response_protocol is HTTP11: buf.append("Connection: close") + if extra_headers is not None: + for h, v in extra_headers.iteritems(): + buf.append('%s: %s' % (h, v)) buf.append('') buf = [(x + '\r\n').encode('ascii') for x in buf] if self.method != 'HEAD': @@ -346,8 +354,11 @@ class HTTPConnection(HTTPRequest): def job_done(self, ok, result): if not ok: etype, e, tb = result - if isinstance(e, HTTP404): - return self.simple_response(httplib.NOT_FOUND, msg=e.message or '', close_after_response=False) + if isinstance(e, HTTPSimpleResponse): + eh = {} + if e.location: + eh['Location'] = e.location + return self.simple_response(e.http_code, msg=e.message or '', close_after_response=e.close_connection, extra_headers=eh) raise e, None, tb data, output = result diff --git a/src/calibre/srv/tests/http.py b/src/calibre/srv/tests/http.py index 0faed278e8..3a5a55563a 100644 --- a/src/calibre/srv/tests/http.py +++ b/src/calibre/srv/tests/http.py @@ -89,10 +89,10 @@ class TestHTTP(BaseTest): def test_http_basic(self): # {{{ 'Test basic HTTP protocol conformance' - from calibre.srv.errors import HTTP404 + from calibre.srv.errors import HTTPNotFound, HTTPRedirect body = 'Requested resource not found' def handler(data): - raise HTTP404(body) + raise HTTPNotFound(body) def raw_send(conn, raw): conn.send(raw) conn._HTTPConnection__state = httplib._CS_REQ_SENT @@ -146,6 +146,17 @@ class TestHTTP(BaseTest): self.ae(r.status, httplib.INTERNAL_SERVER_ERROR) server.loop.log.filter_level = orig + # Test 301 + def handler(data): + raise HTTPRedirect('/somewhere-else') + server.change_handler(handler) + conn = server.connect() + conn.request('GET', '/') + r = conn.getresponse() + self.ae(r.status, httplib.MOVED_PERMANENTLY) + self.ae(r.getheader('Location'), '/somewhere-else') + self.ae('', r.read()) + server.change_handler(lambda data:data.path[0] + data.read().decode('ascii')) conn = server.connect()