diff --git a/src/calibre/srv/http.py b/src/calibre/srv/http.py index 578475f626..51a3ee8a04 100644 --- a/src/calibre/srv/http.py +++ b/src/calibre/srv/http.py @@ -18,7 +18,7 @@ from calibre.constants import __version__ from calibre.srv.errors import ( MaxSizeExceeded, NonHTTPConnRequest, HTTP404, IfNoneMatch, BadChunkedInput) from calibre.srv.respond import finalize_output, generate_static_output -from calibre.srv.utils import MultiDict, http_date +from calibre.srv.utils import MultiDict, http_date, socket_errors_to_ignore HTTP1 = 'HTTP/1.0' HTTP11 = 'HTTP/1.1' @@ -149,6 +149,14 @@ def http_communicate(conn): def repr_for_pair(pair): return pair.repr_for_log() if getattr(pair, 'started_request', False) else 'None' + def simple_response(pair, code, msg=''): + if pair and not pair.sent_headers: + try: + pair.simple_response(code, msg=msg) + except socket.error as e: + if e.errno not in socket_errors_to_ignore: + raise + try: while True: # (re)set pair to None so that if something goes wrong in @@ -177,8 +185,7 @@ def http_communicate(conn): if (not request_seen) or (pair and pair.started_request): # Don't bother writing the 408 if the response # has already started being written. - if pair and not pair.sent_headers: - pair.simple_response(httplib.REQUEST_TIMEOUT) + simple_response(pair, httplib.REQUEST_TIMEOUT) except NonHTTPConnRequest: raise except socket.error: @@ -188,19 +195,16 @@ def http_communicate(conn): except MaxSizeExceeded as e: conn.server_loop.log.warn('Too large request body (%d > %d) for request:' % (e.size, e.limit), repr_for_pair(pair)) # Can happen if the request uses chunked transfer encoding - if pair and not pair.sent_headers: - pair.simple_response(httplib.REQUEST_ENTITY_TOO_LARGE, - "The entity sent with the request exceeds the maximum " - "allowed bytes (%d)." % pair.max_request_body_size) + simple_response(pair, httplib.REQUEST_ENTITY_TOO_LARGE, + "The entity sent with the request exceeds the maximum " + "allowed bytes (%d)." % pair.max_request_body_size) except BadChunkedInput as e: conn.server_loop.log.warn('Bad chunked encoding (%s) for request:' % as_unicode(e.message), repr_for_pair(pair)) - if pair and not pair.sent_headers: - pair.simple_response(httplib.BAD_REQUEST, - 'Invalid chunked encoding for request body: %s' % as_unicode(e.message)) + simple_response(pair, httplib.BAD_REQUEST, + 'Invalid chunked encoding for request body: %s' % as_unicode(e.message)) except Exception: conn.server_loop.log.exception('Error serving request:', pair.repr_for_log() if getattr(pair, 'started_request', False) else 'None') - if pair and not pair.sent_headers: - pair.simple_response(httplib.INTERNAL_SERVER_ERROR) + simple_response(pair, httplib.INTERNAL_SERVER_ERROR) class FixedSizeReader(object): # {{{ @@ -247,7 +251,7 @@ class ChunkedReader(object): # {{{ if len(chunk) < chunk_size: raise BadChunkedInput('Bad chunked encoding, chunk truncated: %d < %s' % (len(chunk), chunk_size)) if not chunk.endswith(b'\r\n'): - raise BadChunkedInput('Bad chunked encoding: %r != CRLF' % chunk[:-2]) + raise BadChunkedInput('Bad chunked encoding: %r != CRLF' % chunk[-2:]) self.rbuf.seek(0, os.SEEK_END) self.bytes_read += chunk_size if chunk_size == 2: diff --git a/src/calibre/srv/loop.py b/src/calibre/srv/loop.py index 66dccdc057..6ea9983837 100644 --- a/src/calibre/srv/loop.py +++ b/src/calibre/srv/loop.py @@ -6,7 +6,7 @@ from __future__ import (unicode_literals, division, absolute_import, __license__ = 'GPL v3' __copyright__ = '2015, Kovid Goyal ' -import socket, os, errno, ssl, time, sys +import socket, os, ssl, time, sys from operator import and_ from Queue import Queue, Full from threading import Thread, current_thread, Lock @@ -15,31 +15,10 @@ from io import DEFAULT_BUFFER_SIZE, BytesIO from calibre.srv.errors import NonHTTPConnRequest, MaxSizeExceeded from calibre.srv.http import http_communicate from calibre.srv.opts import Options +from calibre.srv.utils import socket_errors_to_ignore, socket_error_eintr, socket_errors_nonblocking from calibre.utils.socket_inheritance import set_socket_inherit from calibre.utils.logging import ThreadSafeLog -def error_codes(*errnames): - ''' Return error numbers for error names, ignoring non-existent names ''' - ans = {getattr(errno, x, None) for x in errnames} - ans.discard(None) - return ans - -socket_error_eintr = error_codes("EINTR", "WSAEINTR") - -socket_errors_to_ignore = error_codes( # errors indicating a closed connection - "EPIPE", - "EBADF", "WSAEBADF", - "ENOTSOCK", "WSAENOTSOCK", - "ETIMEDOUT", "WSAETIMEDOUT", - "ECONNREFUSED", "WSAECONNREFUSED", - "ECONNRESET", "WSAECONNRESET", - "ECONNABORTED", "WSAECONNABORTED", - "ENETRESET", "WSAENETRESET", - "EHOSTDOWN", "EHOSTUNREACH", -) -socket_errors_nonblocking = error_codes( - 'EAGAIN', 'EWOULDBLOCK', 'WSAEWOULDBLOCK') - class SocketFile(object): # {{{ """Faux file object attached to a socket object. Works with non-blocking sockets, unlike the fileobject created by socket.makefile() """ diff --git a/src/calibre/srv/utils.py b/src/calibre/srv/utils.py index 7537e3f88c..3a61cacf3b 100644 --- a/src/calibre/srv/utils.py +++ b/src/calibre/srv/utils.py @@ -6,6 +6,7 @@ from __future__ import (unicode_literals, division, absolute_import, __license__ = 'GPL v3' __copyright__ = '2015, Kovid Goyal ' +import errno from urlparse import parse_qs import repr as reprlib from email.utils import formatdate @@ -83,3 +84,27 @@ class MultiDict(dict): # {{{ def pretty(self, leading_whitespace=''): return leading_whitespace + ('\n' + leading_whitespace).join('%s: %s' % (k, v) for k, v in self.items()) # }}} + +def error_codes(*errnames): + ''' Return error numbers for error names, ignoring non-existent names ''' + ans = {getattr(errno, x, None) for x in errnames} + ans.discard(None) + return ans + +socket_error_eintr = error_codes("EINTR", "WSAEINTR") + +socket_errors_to_ignore = error_codes( # errors indicating a closed connection + "EPIPE", + "EBADF", "WSAEBADF", + "ENOTSOCK", "WSAENOTSOCK", + "ETIMEDOUT", "WSAETIMEDOUT", + "ECONNREFUSED", "WSAECONNREFUSED", + "ECONNRESET", "WSAECONNRESET", + "ECONNABORTED", "WSAECONNABORTED", + "ENETRESET", "WSAENETRESET", + "EHOSTDOWN", "EHOSTUNREACH", +) +socket_errors_nonblocking = error_codes( + 'EAGAIN', 'EWOULDBLOCK', 'WSAEWOULDBLOCK') + +