From f26ce3c72473f26d9ac14bb28cbf0700080d8878 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 18 Oct 2017 10:48:56 +0530 Subject: [PATCH] Content server: Add an option to control the timeout for making AJAX queries to the server. Fixes #1722016 [Private bug](https://bugs.launchpad.net/calibre/+bug/1722016) --- src/calibre/srv/code.py | 16 +++++++--------- src/calibre/srv/opts.py | 4 ++++ src/pyj/ajax.pyj | 17 ++++++++++++++--- src/pyj/srv.pyj | 23 +++++++++++++---------- 4 files changed, 38 insertions(+), 22 deletions(-) diff --git a/src/calibre/srv/code.py b/src/calibre/srv/code.py index 5b8537d599..af53d0670a 100644 --- a/src/calibre/srv/code.py +++ b/src/calibre/srv/code.py @@ -48,16 +48,14 @@ def robots(ctx, rd): return b'User-agent: *\nDisallow: /' -@endpoint('/auto-reload-port', auth_required=False, cache_control='no-cache') -def auto_reload(ctx, rd): +@endpoint('/ajax-setup', auth_required=False, cache_control='no-cache', postprocess=json) +def ajax_setup(ctx, rd): auto_reload_port = getattr(rd.opts, 'auto_reload_port', 0) - rd.outheaders.set('Content-Type', 'text/plain') - return str(max(0, auto_reload_port)) - - -@endpoint('/allow-console-print', cache_control='no-cache', auth_required=False) -def allow_console_print(ctx, rd): - return 'y' if getattr(rd.opts, 'allow_console_print', False) else 'n' + return { + 'auto_reload_port': max(0, auto_reload_port), + 'allow_console_print': bool(getattr(rd.opts, 'allow_console_print', False)), + 'ajax_timeout': rd.opts.ajax_timeout, + } print_lock = Lock() diff --git a/src/calibre/srv/opts.py b/src/calibre/srv/opts.py index 6323699120..7c5b3e9e8e 100644 --- a/src/calibre/srv/opts.py +++ b/src/calibre/srv/opts.py @@ -40,6 +40,10 @@ raw_options = ( 'timeout', 120.0, None, + _('Time (in seconds) to wait for a response from the server when making queries'), + 'ajax_timeout', 60.0, + None, + _('Total time in seconds to wait for clean shutdown'), 'shutdown_timeout', 5.0, None, diff --git a/src/pyj/ajax.pyj b/src/pyj/ajax.pyj index 18fe66d599..102c83e09e 100644 --- a/src/pyj/ajax.pyj +++ b/src/pyj/ajax.pyj @@ -4,6 +4,15 @@ from __python__ import hash_literals from gettext import gettext as _ + +default_timeout = 60 + + +def set_default_timeout(val): + nonlocal default_timeout + default_timeout = val + + def encode_query_component(x): ans = encodeURIComponent(x) # The following exceptions are to make epubcfi() look better @@ -38,7 +47,7 @@ def absolute_path(path): return path -def ajax(path, on_complete, on_progress=None, bypass_cache=True, method='GET', query=None, timeout=30*1000, ok_code=200, progress_totals_needed=True): +def ajax(path, on_complete, on_progress=None, bypass_cache=True, method='GET', query=None, timeout=None, ok_code=200, progress_totals_needed=True): # Run an AJAX request. on_complete must be a function that accepts three # arguments: end_type, xhr, ev where end_type is one of 'abort', 'error', # 'load', 'timeout'. In case end_type is anything other than 'load' you can @@ -47,6 +56,8 @@ def ajax(path, on_complete, on_progress=None, bypass_cache=True, method='GET', q # where loaded is the number of bytes received, total is the total size. # # Returns the xhr object, call xhr.send() to start the request. + if timeout is None: + timeout = default_timeout query = query or {} xhr = XMLHttpRequest() eq = encode_query(query) @@ -62,7 +73,7 @@ def ajax(path, on_complete, on_progress=None, bypass_cache=True, method='GET', q if is_network_error: xhr.error_html = str.format(_('Failed to communicate with "{}", network error, is the server running and accessible?'), xhr.request_path) elif event is 'timeout': - xhr.error_html = str.format(_('Failed to communicate with "{}", timed out after: {} seconds'), xhr.request_path, timeout/1000) + xhr.error_html = str.format(_('Failed to communicate with "{}", timed out after: {} seconds'), xhr.request_path, timeout) elif event is 'abort': xhr.error_html = str.format(_('Failed to communicate with "{}", aborted'), xhr.request_path) else: @@ -99,7 +110,7 @@ def ajax(path, on_complete, on_progress=None, bypass_cache=True, method='GET', q xhr.addEventListener('load', def(ev): complete_callback('load', ev);) xhr.addEventListener('timeout', def(ev): complete_callback('timeout', ev);) xhr.open(method, path) - xhr.timeout = timeout # IE requires timeout to be set after open + xhr.timeout = timeout * 1000 # IE requires timeout to be set after open return xhr def ajax_send(path, data, on_complete, on_progress=None, query=None, timeout=30*1000, ok_code=200): diff --git a/src/pyj/srv.pyj b/src/pyj/srv.pyj index db6b944213..86f3ed699d 100644 --- a/src/pyj/srv.pyj +++ b/src/pyj/srv.pyj @@ -5,7 +5,7 @@ from __python__ import hash_literals from gettext import gettext as _ import initialize # noqa: unused-import -from ajax import ajax, console_print +from ajax import ajax, console_print, set_default_timeout from autoreload import create_auto_reload_watcher from book_list.globals import main_js from book_list.main import main @@ -50,17 +50,20 @@ else: # we know are going to be needed immediately. window.addEventListener('load', main) - ajax('auto-reload-port', def(end_type, xhr, event): - nonlocal autoreload_enabled + ajax('ajax-setup', def(end_type, xhr, event): + nonlocal autoreload_enabled, print if end_type is 'load': - port = parseInt(xhr.responseText) + try: + data = JSON.parse(xhr.responseText) + except: + return + tim = data.ajax_timeout + if not isNaN(tim) and tim > 0: + set_default_timeout(tim) + port = data.auto_reload_port if not isNaN(port) and port > 0: autoreload_enabled = True create_auto_reload_watcher(port) - ).send() # We must bypass cache as otherwise we could get stale port info - ajax('allow-console-print', def(end_type, xhr, event): - nonlocal print - if end_type is 'load': - if xhr.responseText == 'y': + if data.allow_console_print: print = console_print - ).send() + ).send() # We must bypass cache as otherwise we could get stale info