diff --git a/jupyter_server/auth/login.py b/jupyter_server/auth/login.py index fd251c9b1..41f10e19e 100644 --- a/jupyter_server/auth/login.py +++ b/jupyter_server/auth/login.py @@ -166,7 +166,8 @@ def get_user(cls, handler): return handler._user_id user_id = cls.get_user_token(handler) if user_id is None: - user_id = handler.get_secure_cookie(handler.cookie_name) + get_secure_cookie_kwargs = handler.settings.get('get_secure_cookie_kwargs', {}) + user_id = handler.get_secure_cookie(handler.cookie_name, **get_secure_cookie_kwargs ) else: cls.set_login_cookie(handler, user_id) # Record that the current request has been authenticated with a token. diff --git a/jupyter_server/base/handlers.py b/jupyter_server/base/handlers.py index d84c8be2b..0df951e70 100755 --- a/jupyter_server/base/handlers.py +++ b/jupyter_server/base/handlers.py @@ -35,7 +35,7 @@ from traitlets.config import Application from ipython_genutils.path import filefind -from ipython_genutils.py3compat import string_types +from ipython_genutils.py3compat import string_types, PY3 import jupyter_server from jupyter_server._tz import utcnow @@ -419,6 +419,10 @@ def check_host(self): if host.startswith('[') and host.endswith(']'): host = host[1:-1] + if not PY3: + # ip_address only accepts unicode on Python 2 + host = host.decode('utf8', 'replace') + try: addr = ipaddress.ip_address(host) except ValueError: diff --git a/jupyter_server/serverapp.py b/jupyter_server/serverapp.py index e84701bbd..0e928fee8 100755 --- a/jupyter_server/serverapp.py +++ b/jupyter_server/serverapp.py @@ -219,7 +219,7 @@ def init_settings(self, jupyter_app, kernel_manager, contents_manager, now = utcnow() root_dir = contents_manager.root_dir - home = os.path.expanduser('~') + home = py3compat.str_to_unicode(os.path.expanduser('~'), encoding=sys.getfilesystemencoding()) if root_dir.startswith(home + os.path.sep): # collapse $HOME to ~ root_dir = '~' + root_dir[len(home):] @@ -245,11 +245,6 @@ def init_settings(self, jupyter_app, kernel_manager, contents_manager, iopub_data_rate_limit=jupyter_app.iopub_data_rate_limit, rate_limit_window=jupyter_app.rate_limit_window, - # maximum request sizes - support saving larger notebooks - # tornado defaults are 100 MiB, we increase it to 0.5 GiB - max_body_size = 512 * 1024 * 1024, - max_buffer_size = 512 * 1024 * 1024, - # authentication cookie_secret=jupyter_app.cookie_secret, login_url=url_path_join(base_url, '/login'), @@ -775,6 +770,24 @@ def _token_default(self): self._token_generated = True return binascii.hexlify(os.urandom(24)).decode('ascii') + max_body_size = Integer(512 * 1024 * 1024, config=True, + help=""" + Sets the maximum allowed size of the client request body, specified in + the Content-Length request header field. If the size in a request + exceeds the configured value, a malformed HTTP message is returned to + the client. + + Note: max_body_size is applied even in streaming mode. + """ + ) + + max_buffer_size = Integer(512 * 1024 * 1024, config=True, + help=""" + Gets or sets the maximum amount of memory, in bytes, that is allocated + for use by the buffer manager. + """ + ) + @observe('token') def _token_changed(self, change): self._token_generated = False @@ -931,6 +944,10 @@ def _default_allow_remote(self): help=_("Extra keyword arguments to pass to `set_secure_cookie`." " See tornado's set_secure_cookie docs for details.") ) + get_secure_cookie_kwargs = Dict(config=True, + help=_("Extra keyword arguments to pass to `get_secure_cookie`." + " See tornado's get_secure_cookie docs for details.") + ) ssl_options = Dict(config=True, help=_("""Supply SSL options for the tornado HTTPServer. See the tornado docs for details.""")) @@ -1247,6 +1264,7 @@ def init_webapp(self): self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat) self.tornado_settings['allow_credentials'] = self.allow_credentials self.tornado_settings['cookie_options'] = self.cookie_options + self.tornado_settings['get_secure_cookie_kwargs'] = self.get_secure_cookie_kwargs self.tornado_settings['token'] = self.token if (self.open_browser or self.file_to_run) and not self.password: self.one_time_token = binascii.hexlify(os.urandom(24)).decode('ascii') @@ -1289,7 +1307,9 @@ def init_webapp(self): self.login_handler_class.validate_security(self, ssl_options=ssl_options) self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options, - xheaders=self.trust_xheaders) + xheaders=self.trust_xheaders, + max_body_size=self.max_body_size, + max_buffer_size=self.max_buffer_size) success = None for port in random_ports(self.port, self.port_retries+1): diff --git a/jupyter_server/services/kernels/handlers.py b/jupyter_server/services/kernels/handlers.py index 23d763a84..c75f24364 100644 --- a/jupyter_server/services/kernels/handlers.py +++ b/jupyter_server/services/kernels/handlers.py @@ -462,6 +462,12 @@ def on_close(self): self._close_future.set_result(None) def _send_status_message(self, status): + iopub = self.channels.get('iopub', None) + if iopub and not iopub.closed(): + # flush IOPub before sending a restarting/dead status message + # ensures proper ordering on the IOPub channel + # that all messages from the stopped kernel have been delivered + iopub.flush() msg = self.session.msg("status", {'execution_state': status} )