mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-08 18:54:09 -04:00
Ignore errors when sending an error response to the client before closing a connection
This commit is contained in:
parent
ea7cac5612
commit
97c59251bb
@ -18,7 +18,7 @@ from calibre.constants import __version__
|
|||||||
from calibre.srv.errors import (
|
from calibre.srv.errors import (
|
||||||
MaxSizeExceeded, NonHTTPConnRequest, HTTP404, IfNoneMatch, BadChunkedInput)
|
MaxSizeExceeded, NonHTTPConnRequest, HTTP404, IfNoneMatch, BadChunkedInput)
|
||||||
from calibre.srv.respond import finalize_output, generate_static_output
|
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'
|
HTTP1 = 'HTTP/1.0'
|
||||||
HTTP11 = 'HTTP/1.1'
|
HTTP11 = 'HTTP/1.1'
|
||||||
@ -149,6 +149,14 @@ def http_communicate(conn):
|
|||||||
def repr_for_pair(pair):
|
def repr_for_pair(pair):
|
||||||
return pair.repr_for_log() if getattr(pair, 'started_request', False) else 'None'
|
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:
|
try:
|
||||||
while True:
|
while True:
|
||||||
# (re)set pair to None so that if something goes wrong in
|
# (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):
|
if (not request_seen) or (pair and pair.started_request):
|
||||||
# Don't bother writing the 408 if the response
|
# Don't bother writing the 408 if the response
|
||||||
# has already started being written.
|
# has already started being written.
|
||||||
if pair and not pair.sent_headers:
|
simple_response(pair, httplib.REQUEST_TIMEOUT)
|
||||||
pair.simple_response(httplib.REQUEST_TIMEOUT)
|
|
||||||
except NonHTTPConnRequest:
|
except NonHTTPConnRequest:
|
||||||
raise
|
raise
|
||||||
except socket.error:
|
except socket.error:
|
||||||
@ -188,19 +195,16 @@ def http_communicate(conn):
|
|||||||
except MaxSizeExceeded as e:
|
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))
|
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
|
# Can happen if the request uses chunked transfer encoding
|
||||||
if pair and not pair.sent_headers:
|
simple_response(pair, httplib.REQUEST_ENTITY_TOO_LARGE,
|
||||||
pair.simple_response(httplib.REQUEST_ENTITY_TOO_LARGE,
|
"The entity sent with the request exceeds the maximum "
|
||||||
"The entity sent with the request exceeds the maximum "
|
"allowed bytes (%d)." % pair.max_request_body_size)
|
||||||
"allowed bytes (%d)." % pair.max_request_body_size)
|
|
||||||
except BadChunkedInput as e:
|
except BadChunkedInput as e:
|
||||||
conn.server_loop.log.warn('Bad chunked encoding (%s) for request:' % as_unicode(e.message), repr_for_pair(pair))
|
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:
|
simple_response(pair, httplib.BAD_REQUEST,
|
||||||
pair.simple_response(httplib.BAD_REQUEST,
|
'Invalid chunked encoding for request body: %s' % as_unicode(e.message))
|
||||||
'Invalid chunked encoding for request body: %s' % as_unicode(e.message))
|
|
||||||
except Exception:
|
except Exception:
|
||||||
conn.server_loop.log.exception('Error serving request:', pair.repr_for_log() if getattr(pair, 'started_request', False) else 'None')
|
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:
|
simple_response(pair, httplib.INTERNAL_SERVER_ERROR)
|
||||||
pair.simple_response(httplib.INTERNAL_SERVER_ERROR)
|
|
||||||
|
|
||||||
class FixedSizeReader(object): # {{{
|
class FixedSizeReader(object): # {{{
|
||||||
|
|
||||||
@ -247,7 +251,7 @@ class ChunkedReader(object): # {{{
|
|||||||
if len(chunk) < chunk_size:
|
if len(chunk) < chunk_size:
|
||||||
raise BadChunkedInput('Bad chunked encoding, chunk truncated: %d < %s' % (len(chunk), chunk_size))
|
raise BadChunkedInput('Bad chunked encoding, chunk truncated: %d < %s' % (len(chunk), chunk_size))
|
||||||
if not chunk.endswith(b'\r\n'):
|
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.rbuf.seek(0, os.SEEK_END)
|
||||||
self.bytes_read += chunk_size
|
self.bytes_read += chunk_size
|
||||||
if chunk_size == 2:
|
if chunk_size == 2:
|
||||||
|
@ -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 socket, os, errno, ssl, time, sys
|
import socket, os, ssl, time, sys
|
||||||
from operator import and_
|
from operator import and_
|
||||||
from Queue import Queue, Full
|
from Queue import Queue, Full
|
||||||
from threading import Thread, current_thread, Lock
|
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.errors import NonHTTPConnRequest, MaxSizeExceeded
|
||||||
from calibre.srv.http import http_communicate
|
from calibre.srv.http import http_communicate
|
||||||
from calibre.srv.opts import Options
|
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.socket_inheritance import set_socket_inherit
|
||||||
from calibre.utils.logging import ThreadSafeLog
|
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): # {{{
|
class SocketFile(object): # {{{
|
||||||
"""Faux file object attached to a socket object. Works with non-blocking
|
"""Faux file object attached to a socket object. Works with non-blocking
|
||||||
sockets, unlike the fileobject created by socket.makefile() """
|
sockets, unlike the fileobject created by socket.makefile() """
|
||||||
|
@ -6,6 +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 errno
|
||||||
from urlparse import parse_qs
|
from urlparse import parse_qs
|
||||||
import repr as reprlib
|
import repr as reprlib
|
||||||
from email.utils import formatdate
|
from email.utils import formatdate
|
||||||
@ -83,3 +84,27 @@ class MultiDict(dict): # {{{
|
|||||||
def pretty(self, leading_whitespace=''):
|
def pretty(self, leading_whitespace=''):
|
||||||
return leading_whitespace + ('\n' + leading_whitespace).join('%s: %s' % (k, v) for k, v in self.items())
|
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')
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user