Add support for HTTP redirects

This commit is contained in:
Kovid Goyal 2015-05-29 11:03:03 +05:30
parent ab81fe992b
commit f527f41389
3 changed files with 50 additions and 12 deletions

View File

@ -6,9 +6,25 @@ from __future__ import (unicode_literals, division, absolute_import,
__license__ = 'GPL v3'
__copyright__ = '2015, Kovid Goyal <kovid at kovidgoyal.net>'
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)

View File

@ -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

View File

@ -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()