mirror of
https://github.com/SickGear/SickGear.git
synced 2025-01-22 01:23:43 +00:00
Update Tornado Web Server 4.4.dev1 (c2b4d05) to 4.5.dev1 (92f29b8).
This commit is contained in:
parent
05d15d9e79
commit
68446ea5d6
35 changed files with 633 additions and 363 deletions
|
@ -3,7 +3,7 @@
|
||||||
* Add strict Python version check (equal to, or higher than 2.7.9 and less than 3.0), **exit** if incorrect version
|
* Add strict Python version check (equal to, or higher than 2.7.9 and less than 3.0), **exit** if incorrect version
|
||||||
* Update unidecode library 0.04.11 to 0.04.18 (fd57cbf)
|
* Update unidecode library 0.04.11 to 0.04.18 (fd57cbf)
|
||||||
* Update xmltodict library 0.9.2 (579a005) to 0.9.2 (eac0031)
|
* Update xmltodict library 0.9.2 (579a005) to 0.9.2 (eac0031)
|
||||||
* Update Tornado Web Server 4.3.dev1 (1b6157d) to 4.4.dev1 (c2b4d05)
|
* Update Tornado Web Server 4.3.dev1 (1b6157d) to 4.5.dev1 (92f29b8)
|
||||||
* Update change to suppress reporting of Tornado exception error 1 to updated package (ref:hacks.txt)
|
* Update change to suppress reporting of Tornado exception error 1 to updated package (ref:hacks.txt)
|
||||||
* Change API response header for JSON content type and the return of JSONP data
|
* Change API response header for JSON content type and the return of JSONP data
|
||||||
* Remove redundant MultipartPostHandler
|
* Remove redundant MultipartPostHandler
|
||||||
|
@ -184,6 +184,7 @@
|
||||||
* Fix join clean up
|
* Fix join clean up
|
||||||
* Fix add custom torrent RSS
|
* Fix add custom torrent RSS
|
||||||
* Remove ILT torrent provider
|
* Remove ILT torrent provider
|
||||||
|
* Update Tornado Web Server 4.3.dev1 (1b6157d) to 4.4.dev1 (c2b4d05)
|
||||||
|
|
||||||
|
|
||||||
### 0.11.15 (2016-09-13 19:50:00 UTC)
|
### 0.11.15 (2016-09-13 19:50:00 UTC)
|
||||||
|
|
|
@ -25,5 +25,5 @@ from __future__ import absolute_import, division, print_function, with_statement
|
||||||
# is zero for an official release, positive for a development branch,
|
# is zero for an official release, positive for a development branch,
|
||||||
# or negative for a release candidate or beta (after the base version
|
# or negative for a release candidate or beta (after the base version
|
||||||
# number has been incremented)
|
# number has been incremented)
|
||||||
version = "4.4.dev1"
|
version = "4.5.dev1"
|
||||||
version_info = (4, 4, 0, -100)
|
version_info = (4, 5, 0, -100)
|
||||||
|
|
|
@ -19,15 +19,6 @@
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function, with_statement
|
from __future__ import absolute_import, division, print_function, with_statement
|
||||||
|
|
||||||
# NOTE: This file is supposed to contain unicode strings, which is
|
|
||||||
# exactly what you'd get with e.g. u"Español" in most python versions.
|
|
||||||
# However, Python 3.2 doesn't support the u"" syntax, so we use a u()
|
|
||||||
# function instead. tornado.util.u cannot be used because it doesn't
|
|
||||||
# support non-ascii characters on python 2.
|
|
||||||
# When we drop support for Python 3.2, we can remove the parens
|
|
||||||
# and make these plain unicode strings.
|
|
||||||
from tornado.escape import to_unicode as u
|
|
||||||
|
|
||||||
LOCALE_NAMES = {
|
LOCALE_NAMES = {
|
||||||
"af_ZA": {"name_en": u"Afrikaans", "name": u"Afrikaans"},
|
"af_ZA": {"name_en": u"Afrikaans", "name": u"Afrikaans"},
|
||||||
"am_ET": {"name_en": u"Amharic", "name": u"አማርኛ"},
|
"am_ET": {"name_en": u"Amharic", "name": u"አማርኛ"},
|
||||||
|
|
|
@ -82,22 +82,15 @@ from tornado import escape
|
||||||
from tornado.httputil import url_concat
|
from tornado.httputil import url_concat
|
||||||
from tornado.log import gen_log
|
from tornado.log import gen_log
|
||||||
from tornado.stack_context import ExceptionStackContext
|
from tornado.stack_context import ExceptionStackContext
|
||||||
from tornado.util import unicode_type, ArgReplacer
|
from tornado.util import unicode_type, ArgReplacer, PY3
|
||||||
|
|
||||||
try:
|
if PY3:
|
||||||
import urlparse # py2
|
import urllib.parse as urlparse
|
||||||
except ImportError:
|
import urllib.parse as urllib_parse
|
||||||
import urllib.parse as urlparse # py3
|
long = int
|
||||||
|
else:
|
||||||
try:
|
import urlparse
|
||||||
import urllib.parse as urllib_parse # py3
|
import urllib as urllib_parse
|
||||||
except ImportError:
|
|
||||||
import urllib as urllib_parse # py2
|
|
||||||
|
|
||||||
try:
|
|
||||||
long # py2
|
|
||||||
except NameError:
|
|
||||||
long = int # py3
|
|
||||||
|
|
||||||
|
|
||||||
class AuthError(Exception):
|
class AuthError(Exception):
|
||||||
|
@ -290,7 +283,7 @@ class OpenIdMixin(object):
|
||||||
if name:
|
if name:
|
||||||
user["name"] = name
|
user["name"] = name
|
||||||
elif name_parts:
|
elif name_parts:
|
||||||
user["name"] = u(" ").join(name_parts)
|
user["name"] = u" ".join(name_parts)
|
||||||
elif email:
|
elif email:
|
||||||
user["name"] = email.split("@")[0]
|
user["name"] = email.split("@")[0]
|
||||||
if email:
|
if email:
|
||||||
|
@ -996,6 +989,9 @@ class FacebookGraphMixin(OAuth2Mixin):
|
||||||
callback=functools.partial(
|
callback=functools.partial(
|
||||||
self._on_get_user_info, future, session, fields),
|
self._on_get_user_info, future, session, fields),
|
||||||
access_token=session["access_token"],
|
access_token=session["access_token"],
|
||||||
|
appsecret_proof=hmac.new(key=client_secret.encode('utf8'),
|
||||||
|
msg=session["access_token"].encode('utf8'),
|
||||||
|
digestmod=hashlib.sha256).hexdigest(),
|
||||||
fields=",".join(fields)
|
fields=",".join(fields)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -83,7 +83,7 @@ if __name__ == "__main__":
|
||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
import pkgutil
|
import pkgutil # type: ignore
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
import types
|
import types
|
||||||
|
@ -112,7 +112,7 @@ _has_execv = sys.platform != 'win32'
|
||||||
_watched_files = set()
|
_watched_files = set()
|
||||||
_reload_hooks = []
|
_reload_hooks = []
|
||||||
_reload_attempted = False
|
_reload_attempted = False
|
||||||
_io_loops = weakref.WeakKeyDictionary()
|
_io_loops = weakref.WeakKeyDictionary() # type: ignore
|
||||||
|
|
||||||
|
|
||||||
def start(io_loop=None, check_time=500):
|
def start(io_loop=None, check_time=500):
|
||||||
|
|
|
@ -38,6 +38,11 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
futures = None
|
futures = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
import typing
|
||||||
|
except ImportError:
|
||||||
|
typing = None
|
||||||
|
|
||||||
|
|
||||||
# Can the garbage collector handle cycles that include __del__ methods?
|
# Can the garbage collector handle cycles that include __del__ methods?
|
||||||
# This is true in cpython beginning with version 3.4 (PEP 442).
|
# This is true in cpython beginning with version 3.4 (PEP 442).
|
||||||
|
@ -338,7 +343,7 @@ class Future(object):
|
||||||
TracebackFuture = Future
|
TracebackFuture = Future
|
||||||
|
|
||||||
if futures is None:
|
if futures is None:
|
||||||
FUTURES = Future
|
FUTURES = Future # type: typing.Union[type, typing.Tuple[type, ...]]
|
||||||
else:
|
else:
|
||||||
FUTURES = (futures.Future, Future)
|
FUTURES = (futures.Future, Future)
|
||||||
|
|
||||||
|
@ -500,8 +505,9 @@ def chain_future(a, b):
|
||||||
assert future is a
|
assert future is a
|
||||||
if b.done():
|
if b.done():
|
||||||
return
|
return
|
||||||
if (isinstance(a, TracebackFuture) and isinstance(b, TracebackFuture)
|
if (isinstance(a, TracebackFuture) and
|
||||||
and a.exc_info() is not None):
|
isinstance(b, TracebackFuture) and
|
||||||
|
a.exc_info() is not None):
|
||||||
b.set_exc_info(a.exc_info())
|
b.set_exc_info(a.exc_info())
|
||||||
elif a.exception() is not None:
|
elif a.exception() is not None:
|
||||||
b.set_exception(a.exception())
|
b.set_exception(a.exception())
|
||||||
|
|
|
@ -21,7 +21,7 @@ from __future__ import absolute_import, division, print_function, with_statement
|
||||||
import collections
|
import collections
|
||||||
import functools
|
import functools
|
||||||
import logging
|
import logging
|
||||||
import pycurl
|
import pycurl # type: ignore
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
@ -278,6 +278,9 @@ class CurlAsyncHTTPClient(AsyncHTTPClient):
|
||||||
if curl_log.isEnabledFor(logging.DEBUG):
|
if curl_log.isEnabledFor(logging.DEBUG):
|
||||||
curl.setopt(pycurl.VERBOSE, 1)
|
curl.setopt(pycurl.VERBOSE, 1)
|
||||||
curl.setopt(pycurl.DEBUGFUNCTION, self._curl_debug)
|
curl.setopt(pycurl.DEBUGFUNCTION, self._curl_debug)
|
||||||
|
if hasattr(pycurl,'PROTOCOLS'): # PROTOCOLS first appeared in pycurl 7.19.5 (2014-07-12)
|
||||||
|
curl.setopt(pycurl.PROTOCOLS, pycurl.PROTO_HTTP|pycurl.PROTO_HTTPS)
|
||||||
|
curl.setopt(pycurl.REDIR_PROTOCOLS, pycurl.PROTO_HTTP|pycurl.PROTO_HTTPS)
|
||||||
return curl
|
return curl
|
||||||
|
|
||||||
def _curl_setup_request(self, curl, request, buffer, headers):
|
def _curl_setup_request(self, curl, request, buffer, headers):
|
||||||
|
@ -342,6 +345,15 @@ class CurlAsyncHTTPClient(AsyncHTTPClient):
|
||||||
credentials = '%s:%s' % (request.proxy_username,
|
credentials = '%s:%s' % (request.proxy_username,
|
||||||
request.proxy_password)
|
request.proxy_password)
|
||||||
curl.setopt(pycurl.PROXYUSERPWD, credentials)
|
curl.setopt(pycurl.PROXYUSERPWD, credentials)
|
||||||
|
|
||||||
|
if (request.proxy_auth_mode is None or
|
||||||
|
request.proxy_auth_mode == "basic"):
|
||||||
|
curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_BASIC)
|
||||||
|
elif request.proxy_auth_mode == "digest":
|
||||||
|
curl.setopt(pycurl.PROXYAUTH, pycurl.HTTPAUTH_DIGEST)
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
"Unsupported proxy_auth_mode %s" % request.proxy_auth_mode)
|
||||||
else:
|
else:
|
||||||
curl.setopt(pycurl.PROXY, '')
|
curl.setopt(pycurl.PROXY, '')
|
||||||
curl.unsetopt(pycurl.PROXYUSERPWD)
|
curl.unsetopt(pycurl.PROXYUSERPWD)
|
||||||
|
@ -462,7 +474,7 @@ class CurlAsyncHTTPClient(AsyncHTTPClient):
|
||||||
request.prepare_curl_callback(curl)
|
request.prepare_curl_callback(curl)
|
||||||
|
|
||||||
def _curl_header_callback(self, headers, header_callback, header_line):
|
def _curl_header_callback(self, headers, header_callback, header_line):
|
||||||
header_line = native_str(header_line)
|
header_line = native_str(header_line.decode('latin1'))
|
||||||
if header_callback is not None:
|
if header_callback is not None:
|
||||||
self.io_loop.add_callback(header_callback, header_line)
|
self.io_loop.add_callback(header_callback, header_line)
|
||||||
# header_line as returned by curl includes the end-of-line characters.
|
# header_line as returned by curl includes the end-of-line characters.
|
||||||
|
|
|
@ -22,32 +22,26 @@ have crept in over time.
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function, with_statement
|
from __future__ import absolute_import, division, print_function, with_statement
|
||||||
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from tornado.util import unicode_type, basestring_type
|
|
||||||
|
|
||||||
try:
|
|
||||||
from urllib.parse import parse_qs as _parse_qs # py3
|
|
||||||
except ImportError:
|
|
||||||
from urlparse import parse_qs as _parse_qs # Python 2.6+
|
|
||||||
|
|
||||||
try:
|
|
||||||
import htmlentitydefs # py2
|
|
||||||
except ImportError:
|
|
||||||
import html.entities as htmlentitydefs # py3
|
|
||||||
|
|
||||||
try:
|
|
||||||
import urllib.parse as urllib_parse # py3
|
|
||||||
except ImportError:
|
|
||||||
import urllib as urllib_parse # py2
|
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
from tornado.util import PY3, unicode_type, basestring_type
|
||||||
|
|
||||||
|
if PY3:
|
||||||
|
from urllib.parse import parse_qs as _parse_qs
|
||||||
|
import html.entities as htmlentitydefs
|
||||||
|
import urllib.parse as urllib_parse
|
||||||
|
unichr = chr
|
||||||
|
else:
|
||||||
|
from urlparse import parse_qs as _parse_qs
|
||||||
|
import htmlentitydefs
|
||||||
|
import urllib as urllib_parse
|
||||||
|
|
||||||
try:
|
try:
|
||||||
unichr
|
import typing # noqa
|
||||||
except NameError:
|
except ImportError:
|
||||||
unichr = chr
|
pass
|
||||||
|
|
||||||
|
|
||||||
_XHTML_ESCAPE_RE = re.compile('[&<>"\']')
|
_XHTML_ESCAPE_RE = re.compile('[&<>"\']')
|
||||||
_XHTML_ESCAPE_DICT = {'&': '&', '<': '<', '>': '>', '"': '"',
|
_XHTML_ESCAPE_DICT = {'&': '&', '<': '<', '>': '>', '"': '"',
|
||||||
|
@ -116,7 +110,7 @@ def url_escape(value, plus=True):
|
||||||
# python 3 changed things around enough that we need two separate
|
# python 3 changed things around enough that we need two separate
|
||||||
# implementations of url_unescape. We also need our own implementation
|
# implementations of url_unescape. We also need our own implementation
|
||||||
# of parse_qs since python 3's version insists on decoding everything.
|
# of parse_qs since python 3's version insists on decoding everything.
|
||||||
if sys.version_info[0] < 3:
|
if not PY3:
|
||||||
def url_unescape(value, encoding='utf-8', plus=True):
|
def url_unescape(value, encoding='utf-8', plus=True):
|
||||||
"""Decodes the given value from a URL.
|
"""Decodes the given value from a URL.
|
||||||
|
|
||||||
|
@ -191,6 +185,7 @@ _UTF8_TYPES = (bytes, type(None))
|
||||||
|
|
||||||
|
|
||||||
def utf8(value):
|
def utf8(value):
|
||||||
|
# type: (typing.Union[bytes,unicode_type,None])->typing.Union[bytes,None]
|
||||||
"""Converts a string argument to a byte string.
|
"""Converts a string argument to a byte string.
|
||||||
|
|
||||||
If the argument is already a byte string or None, it is returned unchanged.
|
If the argument is already a byte string or None, it is returned unchanged.
|
||||||
|
|
|
@ -83,16 +83,18 @@ import os
|
||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
import types
|
import types
|
||||||
|
import weakref
|
||||||
|
|
||||||
from tornado.concurrent import Future, TracebackFuture, is_future, chain_future
|
from tornado.concurrent import Future, TracebackFuture, is_future, chain_future
|
||||||
from tornado.ioloop import IOLoop
|
from tornado.ioloop import IOLoop
|
||||||
from tornado.log import app_log
|
from tornado.log import app_log
|
||||||
from tornado import stack_context
|
from tornado import stack_context
|
||||||
from tornado.util import raise_exc_info
|
from tornado.util import PY3, raise_exc_info
|
||||||
|
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
from functools import singledispatch # py34+
|
# py34+
|
||||||
|
from functools import singledispatch # type: ignore
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from singledispatch import singledispatch # backport
|
from singledispatch import singledispatch # backport
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -108,12 +110,14 @@ except ImportError:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
from collections.abc import Generator as GeneratorType # py35+
|
# py35+
|
||||||
|
from collections.abc import Generator as GeneratorType # type: ignore
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from backports_abc import Generator as GeneratorType
|
from backports_abc import Generator as GeneratorType # type: ignore
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from inspect import isawaitable # py35+
|
# py35+
|
||||||
|
from inspect import isawaitable # type: ignore
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from backports_abc import isawaitable
|
from backports_abc import isawaitable
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
@ -121,12 +125,12 @@ except ImportError:
|
||||||
raise
|
raise
|
||||||
from types import GeneratorType
|
from types import GeneratorType
|
||||||
|
|
||||||
def isawaitable(x):
|
def isawaitable(x): # type: ignore
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
if PY3:
|
||||||
import builtins # py3
|
import builtins
|
||||||
except ImportError:
|
else:
|
||||||
import __builtin__ as builtins
|
import __builtin__ as builtins
|
||||||
|
|
||||||
|
|
||||||
|
@ -241,6 +245,24 @@ def coroutine(func, replace_callback=True):
|
||||||
"""
|
"""
|
||||||
return _make_coroutine_wrapper(func, replace_callback=True)
|
return _make_coroutine_wrapper(func, replace_callback=True)
|
||||||
|
|
||||||
|
# Ties lifetime of runners to their result futures. Github Issue #1769
|
||||||
|
# Generators, like any object in Python, must be strong referenced
|
||||||
|
# in order to not be cleaned up by the garbage collector. When using
|
||||||
|
# coroutines, the Runner object is what strong-refs the inner
|
||||||
|
# generator. However, the only item that strong-reffed the Runner
|
||||||
|
# was the last Future that the inner generator yielded (via the
|
||||||
|
# Future's internal done_callback list). Usually this is enough, but
|
||||||
|
# it is also possible for this Future to not have any strong references
|
||||||
|
# other than other objects referenced by the Runner object (usually
|
||||||
|
# when using other callback patterns and/or weakrefs). In this
|
||||||
|
# situation, if a garbage collection ran, a cycle would be detected and
|
||||||
|
# Runner objects could be destroyed along with their inner generators
|
||||||
|
# and everything in their local scope.
|
||||||
|
# This map provides strong references to Runner objects as long as
|
||||||
|
# their result future objects also have strong references (typically
|
||||||
|
# from the parent coroutine's Runner). This keeps the coroutine's
|
||||||
|
# Runner alive.
|
||||||
|
_futures_to_runners = weakref.WeakKeyDictionary()
|
||||||
|
|
||||||
def _make_coroutine_wrapper(func, replace_callback):
|
def _make_coroutine_wrapper(func, replace_callback):
|
||||||
"""The inner workings of ``@gen.coroutine`` and ``@gen.engine``.
|
"""The inner workings of ``@gen.coroutine`` and ``@gen.engine``.
|
||||||
|
@ -291,7 +313,7 @@ def _make_coroutine_wrapper(func, replace_callback):
|
||||||
except Exception:
|
except Exception:
|
||||||
future.set_exc_info(sys.exc_info())
|
future.set_exc_info(sys.exc_info())
|
||||||
else:
|
else:
|
||||||
Runner(result, future, yielded)
|
_futures_to_runners[future] = Runner(result, future, yielded)
|
||||||
try:
|
try:
|
||||||
return future
|
return future
|
||||||
finally:
|
finally:
|
||||||
|
@ -830,7 +852,7 @@ def maybe_future(x):
|
||||||
|
|
||||||
|
|
||||||
def with_timeout(timeout, future, io_loop=None, quiet_exceptions=()):
|
def with_timeout(timeout, future, io_loop=None, quiet_exceptions=()):
|
||||||
"""Wraps a `.Future` in a timeout.
|
"""Wraps a `.Future` (or other yieldable object) in a timeout.
|
||||||
|
|
||||||
Raises `TimeoutError` if the input future does not complete before
|
Raises `TimeoutError` if the input future does not complete before
|
||||||
``timeout``, which may be specified in any form allowed by
|
``timeout``, which may be specified in any form allowed by
|
||||||
|
@ -841,15 +863,18 @@ def with_timeout(timeout, future, io_loop=None, quiet_exceptions=()):
|
||||||
will be logged unless it is of a type contained in ``quiet_exceptions``
|
will be logged unless it is of a type contained in ``quiet_exceptions``
|
||||||
(which may be an exception type or a sequence of types).
|
(which may be an exception type or a sequence of types).
|
||||||
|
|
||||||
Currently only supports Futures, not other `YieldPoint` classes.
|
Does not support `YieldPoint` subclasses.
|
||||||
|
|
||||||
.. versionadded:: 4.0
|
.. versionadded:: 4.0
|
||||||
|
|
||||||
.. versionchanged:: 4.1
|
.. versionchanged:: 4.1
|
||||||
Added the ``quiet_exceptions`` argument and the logging of unhandled
|
Added the ``quiet_exceptions`` argument and the logging of unhandled
|
||||||
exceptions.
|
exceptions.
|
||||||
|
|
||||||
|
.. versionchanged:: 4.4
|
||||||
|
Added support for yieldable objects other than `.Future`.
|
||||||
"""
|
"""
|
||||||
# TODO: allow yield points in addition to futures?
|
# TODO: allow YieldPoints in addition to other yieldables?
|
||||||
# Tricky to do with stack_context semantics.
|
# Tricky to do with stack_context semantics.
|
||||||
#
|
#
|
||||||
# It's tempting to optimize this by cancelling the input future on timeout
|
# It's tempting to optimize this by cancelling the input future on timeout
|
||||||
|
@ -857,6 +882,7 @@ def with_timeout(timeout, future, io_loop=None, quiet_exceptions=()):
|
||||||
# one waiting on the input future, so cancelling it might disrupt other
|
# one waiting on the input future, so cancelling it might disrupt other
|
||||||
# callers and B) concurrent futures can only be cancelled while they are
|
# callers and B) concurrent futures can only be cancelled while they are
|
||||||
# in the queue, so cancellation cannot reliably bound our waiting time.
|
# in the queue, so cancellation cannot reliably bound our waiting time.
|
||||||
|
future = convert_yielded(future)
|
||||||
result = Future()
|
result = Future()
|
||||||
chain_future(future, result)
|
chain_future(future, result)
|
||||||
if io_loop is None:
|
if io_loop is None:
|
||||||
|
|
|
@ -30,7 +30,7 @@ from tornado import httputil
|
||||||
from tornado import iostream
|
from tornado import iostream
|
||||||
from tornado.log import gen_log, app_log
|
from tornado.log import gen_log, app_log
|
||||||
from tornado import stack_context
|
from tornado import stack_context
|
||||||
from tornado.util import GzipDecompressor
|
from tornado.util import GzipDecompressor, PY3
|
||||||
|
|
||||||
|
|
||||||
class _QuietException(Exception):
|
class _QuietException(Exception):
|
||||||
|
@ -351,7 +351,7 @@ class HTTP1Connection(httputil.HTTPConnection):
|
||||||
# 304 responses have no body (not even a zero-length body), and so
|
# 304 responses have no body (not even a zero-length body), and so
|
||||||
# should not have either Content-Length or Transfer-Encoding.
|
# should not have either Content-Length or Transfer-Encoding.
|
||||||
# headers.
|
# headers.
|
||||||
start_line.code != 304 and
|
start_line.code not in (204, 304) and
|
||||||
# No need to chunk the output if a Content-Length is specified.
|
# No need to chunk the output if a Content-Length is specified.
|
||||||
'Content-Length' not in headers and
|
'Content-Length' not in headers and
|
||||||
# Applications are discouraged from touching Transfer-Encoding,
|
# Applications are discouraged from touching Transfer-Encoding,
|
||||||
|
@ -359,8 +359,8 @@ class HTTP1Connection(httputil.HTTPConnection):
|
||||||
'Transfer-Encoding' not in headers)
|
'Transfer-Encoding' not in headers)
|
||||||
# If a 1.0 client asked for keep-alive, add the header.
|
# If a 1.0 client asked for keep-alive, add the header.
|
||||||
if (self._request_start_line.version == 'HTTP/1.0' and
|
if (self._request_start_line.version == 'HTTP/1.0' and
|
||||||
(self._request_headers.get('Connection', '').lower()
|
(self._request_headers.get('Connection', '').lower() ==
|
||||||
== 'keep-alive')):
|
'keep-alive')):
|
||||||
headers['Connection'] = 'Keep-Alive'
|
headers['Connection'] = 'Keep-Alive'
|
||||||
if self._chunking_output:
|
if self._chunking_output:
|
||||||
headers['Transfer-Encoding'] = 'chunked'
|
headers['Transfer-Encoding'] = 'chunked'
|
||||||
|
@ -372,7 +372,14 @@ class HTTP1Connection(httputil.HTTPConnection):
|
||||||
self._expected_content_remaining = int(headers['Content-Length'])
|
self._expected_content_remaining = int(headers['Content-Length'])
|
||||||
else:
|
else:
|
||||||
self._expected_content_remaining = None
|
self._expected_content_remaining = None
|
||||||
lines.extend([utf8(n) + b": " + utf8(v) for n, v in headers.get_all()])
|
# TODO: headers are supposed to be of type str, but we still have some
|
||||||
|
# cases that let bytes slip through. Remove these native_str calls when those
|
||||||
|
# are fixed.
|
||||||
|
header_lines = (native_str(n) + ": " + native_str(v) for n, v in headers.get_all())
|
||||||
|
if PY3:
|
||||||
|
lines.extend(l.encode('latin1') for l in header_lines)
|
||||||
|
else:
|
||||||
|
lines.extend(header_lines)
|
||||||
for line in lines:
|
for line in lines:
|
||||||
if b'\n' in line:
|
if b'\n' in line:
|
||||||
raise ValueError('Newline in header: ' + repr(line))
|
raise ValueError('Newline in header: ' + repr(line))
|
||||||
|
@ -479,9 +486,11 @@ class HTTP1Connection(httputil.HTTPConnection):
|
||||||
connection_header = connection_header.lower()
|
connection_header = connection_header.lower()
|
||||||
if start_line.version == "HTTP/1.1":
|
if start_line.version == "HTTP/1.1":
|
||||||
return connection_header != "close"
|
return connection_header != "close"
|
||||||
elif ("Content-Length" in headers
|
elif ("Content-Length" in headers or
|
||||||
or headers.get("Transfer-Encoding", "").lower() == "chunked"
|
headers.get("Transfer-Encoding", "").lower() == "chunked" or
|
||||||
or start_line.method in ("HEAD", "GET")):
|
getattr(start_line, 'method', None) in ("HEAD", "GET")):
|
||||||
|
# start_line may be a request or reponse start line; only
|
||||||
|
# the former has a method attribute.
|
||||||
return connection_header == "keep-alive"
|
return connection_header == "keep-alive"
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
@ -531,7 +540,13 @@ class HTTP1Connection(httputil.HTTPConnection):
|
||||||
"Multiple unequal Content-Lengths: %r" %
|
"Multiple unequal Content-Lengths: %r" %
|
||||||
headers["Content-Length"])
|
headers["Content-Length"])
|
||||||
headers["Content-Length"] = pieces[0]
|
headers["Content-Length"] = pieces[0]
|
||||||
content_length = int(headers["Content-Length"])
|
|
||||||
|
try:
|
||||||
|
content_length = int(headers["Content-Length"])
|
||||||
|
except ValueError:
|
||||||
|
# Handles non-integer Content-Length value.
|
||||||
|
raise httputil.HTTPInputError(
|
||||||
|
"Only integer Content-Length is allowed: %s" % headers["Content-Length"])
|
||||||
|
|
||||||
if content_length > self._max_body_size:
|
if content_length > self._max_body_size:
|
||||||
raise httputil.HTTPInputError("Content-Length too long")
|
raise httputil.HTTPInputError("Content-Length too long")
|
||||||
|
@ -550,7 +565,7 @@ class HTTP1Connection(httputil.HTTPConnection):
|
||||||
|
|
||||||
if content_length is not None:
|
if content_length is not None:
|
||||||
return self._read_fixed_body(content_length, delegate)
|
return self._read_fixed_body(content_length, delegate)
|
||||||
if headers.get("Transfer-Encoding") == "chunked":
|
if headers.get("Transfer-Encoding", "").lower() == "chunked":
|
||||||
return self._read_chunked_body(delegate)
|
return self._read_chunked_body(delegate)
|
||||||
if self.is_client:
|
if self.is_client:
|
||||||
return self._read_body_until_close(delegate)
|
return self._read_body_until_close(delegate)
|
||||||
|
@ -711,9 +726,8 @@ class HTTP1ServerConnection(object):
|
||||||
# This exception was already logged.
|
# This exception was already logged.
|
||||||
conn.close()
|
conn.close()
|
||||||
return
|
return
|
||||||
except Exception as e:
|
except Exception:
|
||||||
if 1 != e.errno:
|
gen_log.error("Uncaught exception", exc_info=True)
|
||||||
gen_log.error("Uncaught exception", exc_info=True)
|
|
||||||
conn.close()
|
conn.close()
|
||||||
return
|
return
|
||||||
if not ret:
|
if not ret:
|
||||||
|
|
|
@ -25,7 +25,7 @@ to switch to ``curl_httpclient`` for reasons such as the following:
|
||||||
Note that if you are using ``curl_httpclient``, it is highly
|
Note that if you are using ``curl_httpclient``, it is highly
|
||||||
recommended that you use a recent version of ``libcurl`` and
|
recommended that you use a recent version of ``libcurl`` and
|
||||||
``pycurl``. Currently the minimum supported version of libcurl is
|
``pycurl``. Currently the minimum supported version of libcurl is
|
||||||
7.21.1, and the minimum version of pycurl is 7.18.2. It is highly
|
7.22.0, and the minimum version of pycurl is 7.18.2. It is highly
|
||||||
recommended that your ``libcurl`` installation is built with
|
recommended that your ``libcurl`` installation is built with
|
||||||
asynchronous DNS resolver (threaded or c-ares), otherwise you may
|
asynchronous DNS resolver (threaded or c-ares), otherwise you may
|
||||||
encounter various problems with request timeouts (for more
|
encounter various problems with request timeouts (for more
|
||||||
|
@ -61,7 +61,7 @@ class HTTPClient(object):
|
||||||
http_client = httpclient.HTTPClient()
|
http_client = httpclient.HTTPClient()
|
||||||
try:
|
try:
|
||||||
response = http_client.fetch("http://www.google.com/")
|
response = http_client.fetch("http://www.google.com/")
|
||||||
print response.body
|
print(response.body)
|
||||||
except httpclient.HTTPError as e:
|
except httpclient.HTTPError as e:
|
||||||
# HTTPError is raised for non-200 responses; the response
|
# HTTPError is raised for non-200 responses; the response
|
||||||
# can be found in e.response.
|
# can be found in e.response.
|
||||||
|
@ -108,14 +108,14 @@ class AsyncHTTPClient(Configurable):
|
||||||
|
|
||||||
Example usage::
|
Example usage::
|
||||||
|
|
||||||
def handle_request(response):
|
def handle_response(response):
|
||||||
if response.error:
|
if response.error:
|
||||||
print "Error:", response.error
|
print("Error: %s" % response.error)
|
||||||
else:
|
else:
|
||||||
print response.body
|
print(response.body)
|
||||||
|
|
||||||
http_client = AsyncHTTPClient()
|
http_client = AsyncHTTPClient()
|
||||||
http_client.fetch("http://www.google.com/", handle_request)
|
http_client.fetch("http://www.google.com/", handle_response)
|
||||||
|
|
||||||
The constructor for this class is magic in several respects: It
|
The constructor for this class is magic in several respects: It
|
||||||
actually creates an instance of an implementation-specific
|
actually creates an instance of an implementation-specific
|
||||||
|
@ -227,6 +227,9 @@ class AsyncHTTPClient(Configurable):
|
||||||
raise RuntimeError("fetch() called on closed AsyncHTTPClient")
|
raise RuntimeError("fetch() called on closed AsyncHTTPClient")
|
||||||
if not isinstance(request, HTTPRequest):
|
if not isinstance(request, HTTPRequest):
|
||||||
request = HTTPRequest(url=request, **kwargs)
|
request = HTTPRequest(url=request, **kwargs)
|
||||||
|
else:
|
||||||
|
if kwargs:
|
||||||
|
raise ValueError("kwargs can't be used if request is an HTTPRequest object")
|
||||||
# We may modify this (to add Host, Accept-Encoding, etc),
|
# We may modify this (to add Host, Accept-Encoding, etc),
|
||||||
# so make sure we don't modify the caller's object. This is also
|
# so make sure we don't modify the caller's object. This is also
|
||||||
# where normal dicts get converted to HTTPHeaders objects.
|
# where normal dicts get converted to HTTPHeaders objects.
|
||||||
|
@ -307,10 +310,10 @@ class HTTPRequest(object):
|
||||||
network_interface=None, streaming_callback=None,
|
network_interface=None, streaming_callback=None,
|
||||||
header_callback=None, prepare_curl_callback=None,
|
header_callback=None, prepare_curl_callback=None,
|
||||||
proxy_host=None, proxy_port=None, proxy_username=None,
|
proxy_host=None, proxy_port=None, proxy_username=None,
|
||||||
proxy_password=None, allow_nonstandard_methods=None,
|
proxy_password=None, proxy_auth_mode=None,
|
||||||
validate_cert=None, ca_certs=None,
|
allow_nonstandard_methods=None, validate_cert=None,
|
||||||
allow_ipv6=None,
|
ca_certs=None, allow_ipv6=None, client_key=None,
|
||||||
client_key=None, client_cert=None, body_producer=None,
|
client_cert=None, body_producer=None,
|
||||||
expect_100_continue=False, decompress_response=None,
|
expect_100_continue=False, decompress_response=None,
|
||||||
ssl_options=None):
|
ssl_options=None):
|
||||||
r"""All parameters except ``url`` are optional.
|
r"""All parameters except ``url`` are optional.
|
||||||
|
@ -369,12 +372,14 @@ class HTTPRequest(object):
|
||||||
a ``pycurl.Curl`` object to allow the application to make additional
|
a ``pycurl.Curl`` object to allow the application to make additional
|
||||||
``setopt`` calls.
|
``setopt`` calls.
|
||||||
:arg string proxy_host: HTTP proxy hostname. To use proxies,
|
:arg string proxy_host: HTTP proxy hostname. To use proxies,
|
||||||
``proxy_host`` and ``proxy_port`` must be set; ``proxy_username`` and
|
``proxy_host`` and ``proxy_port`` must be set; ``proxy_username``,
|
||||||
``proxy_pass`` are optional. Proxies are currently only supported
|
``proxy_pass`` and ``proxy_auth_mode`` are optional. Proxies are
|
||||||
with ``curl_httpclient``.
|
currently only supported with ``curl_httpclient``.
|
||||||
:arg int proxy_port: HTTP proxy port
|
:arg int proxy_port: HTTP proxy port
|
||||||
:arg string proxy_username: HTTP proxy username
|
:arg string proxy_username: HTTP proxy username
|
||||||
:arg string proxy_password: HTTP proxy password
|
:arg string proxy_password: HTTP proxy password
|
||||||
|
:arg string proxy_auth_mode: HTTP proxy Authentication mode;
|
||||||
|
default is "basic". supports "basic" and "digest"
|
||||||
:arg bool allow_nonstandard_methods: Allow unknown values for ``method``
|
:arg bool allow_nonstandard_methods: Allow unknown values for ``method``
|
||||||
argument?
|
argument?
|
||||||
:arg bool validate_cert: For HTTPS requests, validate the server's
|
:arg bool validate_cert: For HTTPS requests, validate the server's
|
||||||
|
@ -427,6 +432,7 @@ class HTTPRequest(object):
|
||||||
self.proxy_port = proxy_port
|
self.proxy_port = proxy_port
|
||||||
self.proxy_username = proxy_username
|
self.proxy_username = proxy_username
|
||||||
self.proxy_password = proxy_password
|
self.proxy_password = proxy_password
|
||||||
|
self.proxy_auth_mode = proxy_auth_mode
|
||||||
self.url = url
|
self.url = url
|
||||||
self.method = method
|
self.method = method
|
||||||
self.body = body
|
self.body = body
|
||||||
|
@ -527,7 +533,7 @@ class HTTPResponse(object):
|
||||||
|
|
||||||
* buffer: ``cStringIO`` object for response body
|
* buffer: ``cStringIO`` object for response body
|
||||||
|
|
||||||
* body: response body as string (created on demand from ``self.buffer``)
|
* body: response body as bytes (created on demand from ``self.buffer``)
|
||||||
|
|
||||||
* error: Exception object, if any
|
* error: Exception object, if any
|
||||||
|
|
||||||
|
@ -569,7 +575,8 @@ class HTTPResponse(object):
|
||||||
self.request_time = request_time
|
self.request_time = request_time
|
||||||
self.time_info = time_info or {}
|
self.time_info = time_info or {}
|
||||||
|
|
||||||
def _get_body(self):
|
@property
|
||||||
|
def body(self):
|
||||||
if self.buffer is None:
|
if self.buffer is None:
|
||||||
return None
|
return None
|
||||||
elif self._body is None:
|
elif self._body is None:
|
||||||
|
@ -577,8 +584,6 @@ class HTTPResponse(object):
|
||||||
|
|
||||||
return self._body
|
return self._body
|
||||||
|
|
||||||
body = property(_get_body)
|
|
||||||
|
|
||||||
def rethrow(self):
|
def rethrow(self):
|
||||||
"""If there was an error on the request, raise an `HTTPError`."""
|
"""If there was an error on the request, raise an `HTTPError`."""
|
||||||
if self.error:
|
if self.error:
|
||||||
|
@ -612,6 +617,12 @@ class HTTPError(Exception):
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "HTTP %d: %s" % (self.code, self.message)
|
return "HTTP %d: %s" % (self.code, self.message)
|
||||||
|
|
||||||
|
# There is a cyclic reference between self and self.response,
|
||||||
|
# which breaks the default __repr__ implementation.
|
||||||
|
# (especially on pypy, which doesn't have the same recursion
|
||||||
|
# detection as cpython).
|
||||||
|
__repr__ = __str__
|
||||||
|
|
||||||
|
|
||||||
class _RequestProxy(object):
|
class _RequestProxy(object):
|
||||||
"""Combines an object with a dictionary of defaults.
|
"""Combines an object with a dictionary of defaults.
|
||||||
|
|
|
@ -33,33 +33,36 @@ import time
|
||||||
|
|
||||||
from tornado.escape import native_str, parse_qs_bytes, utf8
|
from tornado.escape import native_str, parse_qs_bytes, utf8
|
||||||
from tornado.log import gen_log
|
from tornado.log import gen_log
|
||||||
from tornado.util import ObjectDict
|
from tornado.util import ObjectDict, PY3
|
||||||
|
|
||||||
try:
|
if PY3:
|
||||||
import Cookie # py2
|
import http.cookies as Cookie
|
||||||
except ImportError:
|
from http.client import responses
|
||||||
import http.cookies as Cookie # py3
|
from urllib.parse import urlencode
|
||||||
|
else:
|
||||||
|
import Cookie
|
||||||
|
from httplib import responses
|
||||||
|
from urllib import urlencode
|
||||||
|
|
||||||
try:
|
|
||||||
from httplib import responses # py2
|
|
||||||
except ImportError:
|
|
||||||
from http.client import responses # py3
|
|
||||||
|
|
||||||
# responses is unused in this file, but we re-export it to other files.
|
# responses is unused in this file, but we re-export it to other files.
|
||||||
# Reference it so pyflakes doesn't complain.
|
# Reference it so pyflakes doesn't complain.
|
||||||
responses
|
responses
|
||||||
|
|
||||||
try:
|
|
||||||
from urllib import urlencode # py2
|
|
||||||
except ImportError:
|
|
||||||
from urllib.parse import urlencode # py3
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from ssl import SSLError
|
from ssl import SSLError
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# ssl is unavailable on app engine.
|
# ssl is unavailable on app engine.
|
||||||
class SSLError(Exception):
|
class _SSLError(Exception):
|
||||||
pass
|
pass
|
||||||
|
# Hack around a mypy limitation. We can't simply put "type: ignore"
|
||||||
|
# on the class definition itself; must go through an assignment.
|
||||||
|
SSLError = _SSLError # type: ignore
|
||||||
|
|
||||||
|
try:
|
||||||
|
import typing
|
||||||
|
except ImportError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
# RFC 7230 section 3.5: a recipient MAY recognize a single LF as a line
|
# RFC 7230 section 3.5: a recipient MAY recognize a single LF as a line
|
||||||
|
@ -127,8 +130,8 @@ class HTTPHeaders(collections.MutableMapping):
|
||||||
Set-Cookie: C=D
|
Set-Cookie: C=D
|
||||||
"""
|
"""
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self._dict = {}
|
self._dict = {} # type: typing.Dict[str, str]
|
||||||
self._as_list = {}
|
self._as_list = {} # type: typing.Dict[str, typing.List[str]]
|
||||||
self._last_key = None
|
self._last_key = None
|
||||||
if (len(args) == 1 and len(kwargs) == 0 and
|
if (len(args) == 1 and len(kwargs) == 0 and
|
||||||
isinstance(args[0], HTTPHeaders)):
|
isinstance(args[0], HTTPHeaders)):
|
||||||
|
@ -142,6 +145,7 @@ class HTTPHeaders(collections.MutableMapping):
|
||||||
# new public methods
|
# new public methods
|
||||||
|
|
||||||
def add(self, name, value):
|
def add(self, name, value):
|
||||||
|
# type: (str, str) -> None
|
||||||
"""Adds a new value for the given key."""
|
"""Adds a new value for the given key."""
|
||||||
norm_name = _normalized_headers[name]
|
norm_name = _normalized_headers[name]
|
||||||
self._last_key = norm_name
|
self._last_key = norm_name
|
||||||
|
@ -158,6 +162,7 @@ class HTTPHeaders(collections.MutableMapping):
|
||||||
return self._as_list.get(norm_name, [])
|
return self._as_list.get(norm_name, [])
|
||||||
|
|
||||||
def get_all(self):
|
def get_all(self):
|
||||||
|
# type: () -> typing.Iterable[typing.Tuple[str, str]]
|
||||||
"""Returns an iterable of all (name, value) pairs.
|
"""Returns an iterable of all (name, value) pairs.
|
||||||
|
|
||||||
If a header has multiple values, multiple pairs will be
|
If a header has multiple values, multiple pairs will be
|
||||||
|
@ -206,6 +211,7 @@ class HTTPHeaders(collections.MutableMapping):
|
||||||
self._as_list[norm_name] = [value]
|
self._as_list[norm_name] = [value]
|
||||||
|
|
||||||
def __getitem__(self, name):
|
def __getitem__(self, name):
|
||||||
|
# type: (str) -> str
|
||||||
return self._dict[_normalized_headers[name]]
|
return self._dict[_normalized_headers[name]]
|
||||||
|
|
||||||
def __delitem__(self, name):
|
def __delitem__(self, name):
|
||||||
|
@ -228,6 +234,14 @@ class HTTPHeaders(collections.MutableMapping):
|
||||||
# the appearance that HTTPHeaders is a single container.
|
# the appearance that HTTPHeaders is a single container.
|
||||||
__copy__ = copy
|
__copy__ = copy
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
lines = []
|
||||||
|
for name, value in self.get_all():
|
||||||
|
lines.append("%s: %s\n" % (name, value))
|
||||||
|
return "".join(lines)
|
||||||
|
|
||||||
|
__unicode__ = __str__
|
||||||
|
|
||||||
|
|
||||||
class HTTPServerRequest(object):
|
class HTTPServerRequest(object):
|
||||||
"""A single HTTP request.
|
"""A single HTTP request.
|
||||||
|
@ -743,7 +757,7 @@ def parse_multipart_form_data(boundary, data, arguments, files):
|
||||||
name = disp_params["name"]
|
name = disp_params["name"]
|
||||||
if disp_params.get("filename"):
|
if disp_params.get("filename"):
|
||||||
ctype = headers.get("Content-Type", "application/unknown")
|
ctype = headers.get("Content-Type", "application/unknown")
|
||||||
files.setdefault(name, []).append(HTTPFile(
|
files.setdefault(name, []).append(HTTPFile( # type: ignore
|
||||||
filename=disp_params["filename"], body=value,
|
filename=disp_params["filename"], body=value,
|
||||||
content_type=ctype))
|
content_type=ctype))
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -45,20 +45,20 @@ import math
|
||||||
|
|
||||||
from tornado.concurrent import TracebackFuture, is_future
|
from tornado.concurrent import TracebackFuture, is_future
|
||||||
from tornado.log import app_log, gen_log
|
from tornado.log import app_log, gen_log
|
||||||
|
from tornado.platform.auto import set_close_exec, Waker
|
||||||
from tornado import stack_context
|
from tornado import stack_context
|
||||||
from tornado.util import Configurable, errno_from_exception, timedelta_to_seconds
|
from tornado.util import PY3, Configurable, errno_from_exception, timedelta_to_seconds
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import signal
|
import signal
|
||||||
except ImportError:
|
except ImportError:
|
||||||
signal = None
|
signal = None
|
||||||
|
|
||||||
try:
|
|
||||||
import thread # py2
|
|
||||||
except ImportError:
|
|
||||||
import _thread as thread # py3
|
|
||||||
|
|
||||||
from tornado.platform.auto import set_close_exec, Waker
|
if PY3:
|
||||||
|
import _thread as thread
|
||||||
|
else:
|
||||||
|
import thread
|
||||||
|
|
||||||
|
|
||||||
_POLL_TIMEOUT = 3600.0
|
_POLL_TIMEOUT = 3600.0
|
||||||
|
@ -172,6 +172,10 @@ class IOLoop(Configurable):
|
||||||
This is normally not necessary as `instance()` will create
|
This is normally not necessary as `instance()` will create
|
||||||
an `IOLoop` on demand, but you may want to call `install` to use
|
an `IOLoop` on demand, but you may want to call `install` to use
|
||||||
a custom subclass of `IOLoop`.
|
a custom subclass of `IOLoop`.
|
||||||
|
|
||||||
|
When using an `IOLoop` subclass, `install` must be called prior
|
||||||
|
to creating any objects that implicitly create their own
|
||||||
|
`IOLoop` (e.g., :class:`tornado.httpclient.AsyncHTTPClient`).
|
||||||
"""
|
"""
|
||||||
assert not IOLoop.initialized()
|
assert not IOLoop.initialized()
|
||||||
IOLoop._instance = self
|
IOLoop._instance = self
|
||||||
|
@ -612,10 +616,14 @@ class IOLoop(Configurable):
|
||||||
# result, which should just be ignored.
|
# result, which should just be ignored.
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
self.add_future(ret, lambda f: f.result())
|
self.add_future(ret, self._discard_future_result)
|
||||||
except Exception:
|
except Exception:
|
||||||
self.handle_callback_exception(callback)
|
self.handle_callback_exception(callback)
|
||||||
|
|
||||||
|
def _discard_future_result(self, future):
|
||||||
|
"""Avoid unhandled-exception warnings from spawned coroutines."""
|
||||||
|
future.result()
|
||||||
|
|
||||||
def handle_callback_exception(self, callback):
|
def handle_callback_exception(self, callback):
|
||||||
"""This method is called whenever a callback run by the `IOLoop`
|
"""This method is called whenever a callback run by the `IOLoop`
|
||||||
throws an exception.
|
throws an exception.
|
||||||
|
@ -814,8 +822,8 @@ class PollIOLoop(IOLoop):
|
||||||
due_timeouts.append(heapq.heappop(self._timeouts))
|
due_timeouts.append(heapq.heappop(self._timeouts))
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
if (self._cancellations > 512
|
if (self._cancellations > 512 and
|
||||||
and self._cancellations > (len(self._timeouts) >> 1)):
|
self._cancellations > (len(self._timeouts) >> 1)):
|
||||||
# Clean up the timeout queue when it gets large and it's
|
# Clean up the timeout queue when it gets large and it's
|
||||||
# more than half cancellations.
|
# more than half cancellations.
|
||||||
self._cancellations = 0
|
self._cancellations = 0
|
||||||
|
@ -874,7 +882,7 @@ class PollIOLoop(IOLoop):
|
||||||
# Pop one fd at a time from the set of pending fds and run
|
# Pop one fd at a time from the set of pending fds and run
|
||||||
# its handler. Since that handler may perform actions on
|
# its handler. Since that handler may perform actions on
|
||||||
# other file descriptors, there may be reentrant calls to
|
# other file descriptors, there may be reentrant calls to
|
||||||
# this IOLoop that update self._events
|
# this IOLoop that modify self._events
|
||||||
self._events.update(event_pairs)
|
self._events.update(event_pairs)
|
||||||
while self._events:
|
while self._events:
|
||||||
fd, events = self._events.popitem()
|
fd, events = self._events.popitem()
|
||||||
|
@ -966,26 +974,24 @@ class _Timeout(object):
|
||||||
"""An IOLoop timeout, a UNIX timestamp and a callback"""
|
"""An IOLoop timeout, a UNIX timestamp and a callback"""
|
||||||
|
|
||||||
# Reduce memory overhead when there are lots of pending callbacks
|
# Reduce memory overhead when there are lots of pending callbacks
|
||||||
__slots__ = ['deadline', 'callback', 'tiebreaker']
|
__slots__ = ['deadline', 'callback', 'tdeadline']
|
||||||
|
|
||||||
def __init__(self, deadline, callback, io_loop):
|
def __init__(self, deadline, callback, io_loop):
|
||||||
if not isinstance(deadline, numbers.Real):
|
if not isinstance(deadline, numbers.Real):
|
||||||
raise TypeError("Unsupported deadline %r" % deadline)
|
raise TypeError("Unsupported deadline %r" % deadline)
|
||||||
self.deadline = deadline
|
self.deadline = deadline
|
||||||
self.callback = callback
|
self.callback = callback
|
||||||
self.tiebreaker = next(io_loop._timeout_counter)
|
self.tdeadline = (deadline, next(io_loop._timeout_counter))
|
||||||
|
|
||||||
# Comparison methods to sort by deadline, with object id as a tiebreaker
|
# Comparison methods to sort by deadline, with object id as a tiebreaker
|
||||||
# to guarantee a consistent ordering. The heapq module uses __le__
|
# to guarantee a consistent ordering. The heapq module uses __le__
|
||||||
# in python2.5, and __lt__ in 2.6+ (sort() and most other comparisons
|
# in python2.5, and __lt__ in 2.6+ (sort() and most other comparisons
|
||||||
# use __lt__).
|
# use __lt__).
|
||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
return ((self.deadline, self.tiebreaker) <
|
return self.tdeadline < other.tdeadline
|
||||||
(other.deadline, other.tiebreaker))
|
|
||||||
|
|
||||||
def __le__(self, other):
|
def __le__(self, other):
|
||||||
return ((self.deadline, self.tiebreaker) <=
|
return self.tdeadline <= other.tdeadline
|
||||||
(other.deadline, other.tiebreaker))
|
|
||||||
|
|
||||||
|
|
||||||
class PeriodicCallback(object):
|
class PeriodicCallback(object):
|
||||||
|
@ -1048,6 +1054,7 @@ class PeriodicCallback(object):
|
||||||
|
|
||||||
if self._next_timeout <= current_time:
|
if self._next_timeout <= current_time:
|
||||||
callback_time_sec = self.callback_time / 1000.0
|
callback_time_sec = self.callback_time / 1000.0
|
||||||
self._next_timeout += (math.floor((current_time - self._next_timeout) / callback_time_sec) + 1) * callback_time_sec
|
self._next_timeout += (math.floor((current_time - self._next_timeout) /
|
||||||
|
callback_time_sec) + 1) * callback_time_sec
|
||||||
|
|
||||||
self._timeout = self.io_loop.add_timeout(self._next_timeout, self._run)
|
self._timeout = self.io_loop.add_timeout(self._next_timeout, self._run)
|
||||||
|
|
|
@ -58,7 +58,7 @@ except ImportError:
|
||||||
_ERRNO_WOULDBLOCK = (errno.EWOULDBLOCK, errno.EAGAIN)
|
_ERRNO_WOULDBLOCK = (errno.EWOULDBLOCK, errno.EAGAIN)
|
||||||
|
|
||||||
if hasattr(errno, "WSAEWOULDBLOCK"):
|
if hasattr(errno, "WSAEWOULDBLOCK"):
|
||||||
_ERRNO_WOULDBLOCK += (errno.WSAEWOULDBLOCK,)
|
_ERRNO_WOULDBLOCK += (errno.WSAEWOULDBLOCK,) # type: ignore
|
||||||
|
|
||||||
# These errnos indicate that a connection has been abruptly terminated.
|
# These errnos indicate that a connection has been abruptly terminated.
|
||||||
# They should be caught and handled less noisily than other errors.
|
# They should be caught and handled less noisily than other errors.
|
||||||
|
@ -66,7 +66,7 @@ _ERRNO_CONNRESET = (errno.ECONNRESET, errno.ECONNABORTED, errno.EPIPE,
|
||||||
errno.ETIMEDOUT)
|
errno.ETIMEDOUT)
|
||||||
|
|
||||||
if hasattr(errno, "WSAECONNRESET"):
|
if hasattr(errno, "WSAECONNRESET"):
|
||||||
_ERRNO_CONNRESET += (errno.WSAECONNRESET, errno.WSAECONNABORTED, errno.WSAETIMEDOUT)
|
_ERRNO_CONNRESET += (errno.WSAECONNRESET, errno.WSAECONNABORTED, errno.WSAETIMEDOUT) # type: ignore
|
||||||
|
|
||||||
if sys.platform == 'darwin':
|
if sys.platform == 'darwin':
|
||||||
# OSX appears to have a race condition that causes send(2) to return
|
# OSX appears to have a race condition that causes send(2) to return
|
||||||
|
@ -74,13 +74,13 @@ if sys.platform == 'darwin':
|
||||||
# http://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/
|
# http://erickt.github.io/blog/2014/11/19/adventures-in-debugging-a-potential-osx-kernel-bug/
|
||||||
# Since the socket is being closed anyway, treat this as an ECONNRESET
|
# Since the socket is being closed anyway, treat this as an ECONNRESET
|
||||||
# instead of an unexpected error.
|
# instead of an unexpected error.
|
||||||
_ERRNO_CONNRESET += (errno.EPROTOTYPE,)
|
_ERRNO_CONNRESET += (errno.EPROTOTYPE,) # type: ignore
|
||||||
|
|
||||||
# More non-portable errnos:
|
# More non-portable errnos:
|
||||||
_ERRNO_INPROGRESS = (errno.EINPROGRESS,)
|
_ERRNO_INPROGRESS = (errno.EINPROGRESS,)
|
||||||
|
|
||||||
if hasattr(errno, "WSAEINPROGRESS"):
|
if hasattr(errno, "WSAEINPROGRESS"):
|
||||||
_ERRNO_INPROGRESS += (errno.WSAEINPROGRESS,)
|
_ERRNO_INPROGRESS += (errno.WSAEINPROGRESS,) # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class StreamClosedError(IOError):
|
class StreamClosedError(IOError):
|
||||||
|
@ -648,8 +648,7 @@ class BaseIOStream(object):
|
||||||
except UnsatisfiableReadError:
|
except UnsatisfiableReadError:
|
||||||
raise
|
raise
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if 1 != e.errno:
|
gen_log.warning("error on read: %s" % e)
|
||||||
gen_log.warning("error on read: %s" % e)
|
|
||||||
self.close(exc_info=True)
|
self.close(exc_info=True)
|
||||||
return
|
return
|
||||||
if pos is not None:
|
if pos is not None:
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
To load a locale and generate a translated string::
|
To load a locale and generate a translated string::
|
||||||
|
|
||||||
user_locale = tornado.locale.get("es_LA")
|
user_locale = tornado.locale.get("es_LA")
|
||||||
print user_locale.translate("Sign out")
|
print(user_locale.translate("Sign out"))
|
||||||
|
|
||||||
`tornado.locale.get()` returns the closest matching locale, not necessarily the
|
`tornado.locale.get()` returns the closest matching locale, not necessarily the
|
||||||
specific locale you requested. You can support pluralization with
|
specific locale you requested. You can support pluralization with
|
||||||
|
@ -28,7 +28,7 @@ additional arguments to `~Locale.translate()`, e.g.::
|
||||||
people = [...]
|
people = [...]
|
||||||
message = user_locale.translate(
|
message = user_locale.translate(
|
||||||
"%(list)s is online", "%(list)s are online", len(people))
|
"%(list)s is online", "%(list)s are online", len(people))
|
||||||
print message % {"list": user_locale.list(people)}
|
print(message % {"list": user_locale.list(people)})
|
||||||
|
|
||||||
The first string is chosen if ``len(people) == 1``, otherwise the second
|
The first string is chosen if ``len(people) == 1``, otherwise the second
|
||||||
string is chosen.
|
string is chosen.
|
||||||
|
@ -51,11 +51,12 @@ import re
|
||||||
|
|
||||||
from tornado import escape
|
from tornado import escape
|
||||||
from tornado.log import gen_log
|
from tornado.log import gen_log
|
||||||
|
from tornado.util import PY3
|
||||||
|
|
||||||
from tornado._locale_data import LOCALE_NAMES
|
from tornado._locale_data import LOCALE_NAMES
|
||||||
|
|
||||||
_default_locale = "en_US"
|
_default_locale = "en_US"
|
||||||
_translations = {}
|
_translations = {} # type: dict
|
||||||
_supported_locales = frozenset([_default_locale])
|
_supported_locales = frozenset([_default_locale])
|
||||||
_use_gettext = False
|
_use_gettext = False
|
||||||
CONTEXT_SEPARATOR = "\x04"
|
CONTEXT_SEPARATOR = "\x04"
|
||||||
|
@ -147,11 +148,11 @@ def load_translations(directory, encoding=None):
|
||||||
# in most cases but is common with CSV files because Excel
|
# in most cases but is common with CSV files because Excel
|
||||||
# cannot read utf-8 files without a BOM.
|
# cannot read utf-8 files without a BOM.
|
||||||
encoding = 'utf-8-sig'
|
encoding = 'utf-8-sig'
|
||||||
try:
|
if PY3:
|
||||||
# python 3: csv.reader requires a file open in text mode.
|
# python 3: csv.reader requires a file open in text mode.
|
||||||
# Force utf8 to avoid dependence on $LANG environment variable.
|
# Force utf8 to avoid dependence on $LANG environment variable.
|
||||||
f = open(full_path, "r", encoding=encoding)
|
f = open(full_path, "r", encoding=encoding)
|
||||||
except TypeError:
|
else:
|
||||||
# python 2: csv can only handle byte strings (in ascii-compatible
|
# python 2: csv can only handle byte strings (in ascii-compatible
|
||||||
# encodings), which we decode below. Transcode everything into
|
# encodings), which we decode below. Transcode everything into
|
||||||
# utf8 before passing it to csv.reader.
|
# utf8 before passing it to csv.reader.
|
||||||
|
|
|
@ -14,13 +14,13 @@
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function, with_statement
|
from __future__ import absolute_import, division, print_function, with_statement
|
||||||
|
|
||||||
__all__ = ['Condition', 'Event', 'Semaphore', 'BoundedSemaphore', 'Lock']
|
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
|
|
||||||
from tornado import gen, ioloop
|
from tornado import gen, ioloop
|
||||||
from tornado.concurrent import Future
|
from tornado.concurrent import Future
|
||||||
|
|
||||||
|
__all__ = ['Condition', 'Event', 'Semaphore', 'BoundedSemaphore', 'Lock']
|
||||||
|
|
||||||
|
|
||||||
class _TimeoutGarbageCollector(object):
|
class _TimeoutGarbageCollector(object):
|
||||||
"""Base class for objects that periodically clean up timed-out waiters.
|
"""Base class for objects that periodically clean up timed-out waiters.
|
||||||
|
|
|
@ -38,7 +38,7 @@ from tornado.escape import _unicode
|
||||||
from tornado.util import unicode_type, basestring_type
|
from tornado.util import unicode_type, basestring_type
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import curses
|
import curses # type: ignore
|
||||||
except ImportError:
|
except ImportError:
|
||||||
curses = None
|
curses = None
|
||||||
|
|
||||||
|
@ -77,8 +77,8 @@ class LogFormatter(logging.Formatter):
|
||||||
* Robust against str/bytes encoding problems.
|
* Robust against str/bytes encoding problems.
|
||||||
|
|
||||||
This formatter is enabled automatically by
|
This formatter is enabled automatically by
|
||||||
`tornado.options.parse_command_line` (unless ``--logging=none`` is
|
`tornado.options.parse_command_line` or `tornado.options.parse_config_file`
|
||||||
used).
|
(unless ``--logging=none`` is used).
|
||||||
"""
|
"""
|
||||||
DEFAULT_FORMAT = '%(color)s[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d]%(end_color)s %(message)s'
|
DEFAULT_FORMAT = '%(color)s[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d]%(end_color)s %(message)s'
|
||||||
DEFAULT_DATE_FORMAT = '%y%m%d %H:%M:%S'
|
DEFAULT_DATE_FORMAT = '%y%m%d %H:%M:%S'
|
||||||
|
@ -183,7 +183,8 @@ def enable_pretty_logging(options=None, logger=None):
|
||||||
and `tornado.options.parse_config_file`.
|
and `tornado.options.parse_config_file`.
|
||||||
"""
|
"""
|
||||||
if options is None:
|
if options is None:
|
||||||
from tornado.options import options
|
import tornado.options
|
||||||
|
options = tornado.options.options
|
||||||
if options.logging is None or options.logging.lower() == 'none':
|
if options.logging is None or options.logging.lower() == 'none':
|
||||||
return
|
return
|
||||||
if logger is None:
|
if logger is None:
|
||||||
|
@ -228,7 +229,8 @@ def define_logging_options(options=None):
|
||||||
"""
|
"""
|
||||||
if options is None:
|
if options is None:
|
||||||
# late import to prevent cycle
|
# late import to prevent cycle
|
||||||
from tornado.options import options
|
import tornado.options
|
||||||
|
options = tornado.options.options
|
||||||
options.define("logging", default="info",
|
options.define("logging", default="info",
|
||||||
help=("Set the Python log level. If 'none', tornado won't touch the "
|
help=("Set the Python log level. If 'none', tornado won't touch the "
|
||||||
"logging configuration."),
|
"logging configuration."),
|
||||||
|
|
|
@ -27,7 +27,7 @@ import stat
|
||||||
from tornado.concurrent import dummy_executor, run_on_executor
|
from tornado.concurrent import dummy_executor, run_on_executor
|
||||||
from tornado.ioloop import IOLoop
|
from tornado.ioloop import IOLoop
|
||||||
from tornado.platform.auto import set_close_exec
|
from tornado.platform.auto import set_close_exec
|
||||||
from tornado.util import Configurable, errno_from_exception
|
from tornado.util import PY3, Configurable, errno_from_exception
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import ssl
|
import ssl
|
||||||
|
@ -44,20 +44,18 @@ except ImportError:
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
try:
|
if PY3:
|
||||||
xrange # py2
|
xrange = range
|
||||||
except NameError:
|
|
||||||
xrange = range # py3
|
|
||||||
|
|
||||||
if hasattr(ssl, 'match_hostname') and hasattr(ssl, 'CertificateError'): # python 3.2+
|
if hasattr(ssl, 'match_hostname') and hasattr(ssl, 'CertificateError'): # python 3.2+
|
||||||
ssl_match_hostname = ssl.match_hostname
|
ssl_match_hostname = ssl.match_hostname
|
||||||
SSLCertificateError = ssl.CertificateError
|
SSLCertificateError = ssl.CertificateError
|
||||||
elif ssl is None:
|
elif ssl is None:
|
||||||
ssl_match_hostname = SSLCertificateError = None
|
ssl_match_hostname = SSLCertificateError = None # type: ignore
|
||||||
else:
|
else:
|
||||||
import backports.ssl_match_hostname
|
import backports.ssl_match_hostname
|
||||||
ssl_match_hostname = backports.ssl_match_hostname.match_hostname
|
ssl_match_hostname = backports.ssl_match_hostname.match_hostname
|
||||||
SSLCertificateError = backports.ssl_match_hostname.CertificateError
|
SSLCertificateError = backports.ssl_match_hostname.CertificateError # type: ignore
|
||||||
|
|
||||||
if hasattr(ssl, 'SSLContext'):
|
if hasattr(ssl, 'SSLContext'):
|
||||||
if hasattr(ssl, 'create_default_context'):
|
if hasattr(ssl, 'create_default_context'):
|
||||||
|
@ -104,7 +102,7 @@ u'foo'.encode('idna')
|
||||||
_ERRNO_WOULDBLOCK = (errno.EWOULDBLOCK, errno.EAGAIN)
|
_ERRNO_WOULDBLOCK = (errno.EWOULDBLOCK, errno.EAGAIN)
|
||||||
|
|
||||||
if hasattr(errno, "WSAEWOULDBLOCK"):
|
if hasattr(errno, "WSAEWOULDBLOCK"):
|
||||||
_ERRNO_WOULDBLOCK += (errno.WSAEWOULDBLOCK,)
|
_ERRNO_WOULDBLOCK += (errno.WSAEWOULDBLOCK,) # type: ignore
|
||||||
|
|
||||||
# Default backlog used when calling sock.listen()
|
# Default backlog used when calling sock.listen()
|
||||||
_DEFAULT_BACKLOG = 128
|
_DEFAULT_BACKLOG = 128
|
||||||
|
@ -131,7 +129,7 @@ def bind_sockets(port, address=None, family=socket.AF_UNSPEC,
|
||||||
``flags`` is a bitmask of AI_* flags to `~socket.getaddrinfo`, like
|
``flags`` is a bitmask of AI_* flags to `~socket.getaddrinfo`, like
|
||||||
``socket.AI_PASSIVE | socket.AI_NUMERICHOST``.
|
``socket.AI_PASSIVE | socket.AI_NUMERICHOST``.
|
||||||
|
|
||||||
``resuse_port`` option sets ``SO_REUSEPORT`` option for every socket
|
``reuse_port`` option sets ``SO_REUSEPORT`` option for every socket
|
||||||
in the list. If your platform doesn't support this option ValueError will
|
in the list. If your platform doesn't support this option ValueError will
|
||||||
be raised.
|
be raised.
|
||||||
"""
|
"""
|
||||||
|
@ -334,6 +332,11 @@ class Resolver(Configurable):
|
||||||
port)`` pair for IPv4; additional fields may be present for
|
port)`` pair for IPv4; additional fields may be present for
|
||||||
IPv6). If a ``callback`` is passed, it will be run with the
|
IPv6). If a ``callback`` is passed, it will be run with the
|
||||||
result as an argument when it is complete.
|
result as an argument when it is complete.
|
||||||
|
|
||||||
|
:raises IOError: if the address cannot be resolved.
|
||||||
|
|
||||||
|
.. versionchanged:: 4.4
|
||||||
|
Standardized all implementations to raise `IOError`.
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@ -413,8 +416,8 @@ class ThreadedResolver(ExecutorResolver):
|
||||||
All ``ThreadedResolvers`` share a single thread pool, whose
|
All ``ThreadedResolvers`` share a single thread pool, whose
|
||||||
size is set by the first one to be created.
|
size is set by the first one to be created.
|
||||||
"""
|
"""
|
||||||
_threadpool = None
|
_threadpool = None # type: ignore
|
||||||
_threadpool_pid = None
|
_threadpool_pid = None # type: int
|
||||||
|
|
||||||
def initialize(self, io_loop=None, num_threads=10):
|
def initialize(self, io_loop=None, num_threads=10):
|
||||||
threadpool = ThreadedResolver._create_threadpool(num_threads)
|
threadpool = ThreadedResolver._create_threadpool(num_threads)
|
||||||
|
@ -518,4 +521,4 @@ def ssl_wrap_socket(socket, ssl_options, server_hostname=None, **kwargs):
|
||||||
else:
|
else:
|
||||||
return context.wrap_socket(socket, **kwargs)
|
return context.wrap_socket(socket, **kwargs)
|
||||||
else:
|
else:
|
||||||
return ssl.wrap_socket(socket, **dict(context, **kwargs))
|
return ssl.wrap_socket(socket, **dict(context, **kwargs)) # type: ignore
|
||||||
|
|
|
@ -43,8 +43,8 @@ either::
|
||||||
|
|
||||||
.. note:
|
.. note:
|
||||||
|
|
||||||
When using tornado.options.parse_command_line or
|
When using tornado.options.parse_command_line or
|
||||||
tornado.options.parse_config_file, the only options that are set are
|
tornado.options.parse_config_file, the only options that are set are
|
||||||
ones that were previously defined with tornado.options.define.
|
ones that were previously defined with tornado.options.define.
|
||||||
|
|
||||||
Command line formats are what you would expect (``--myoption=myvalue``).
|
Command line formats are what you would expect (``--myoption=myvalue``).
|
||||||
|
@ -308,8 +308,12 @@ class OptionParser(object):
|
||||||
.. versionchanged:: 4.1
|
.. versionchanged:: 4.1
|
||||||
Config files are now always interpreted as utf-8 instead of
|
Config files are now always interpreted as utf-8 instead of
|
||||||
the system default encoding.
|
the system default encoding.
|
||||||
|
|
||||||
|
.. versionchanged:: 4.4
|
||||||
|
The special variable ``__file__`` is available inside config
|
||||||
|
files, specifying the absolute path to the config file itself.
|
||||||
"""
|
"""
|
||||||
config = {}
|
config = {'__file__': os.path.abspath(path)}
|
||||||
with open(path, 'rb') as f:
|
with open(path, 'rb') as f:
|
||||||
exec_in(native_str(f.read()), config, config)
|
exec_in(native_str(f.read()), config, config)
|
||||||
for name in config:
|
for name in config:
|
||||||
|
|
|
@ -14,9 +14,9 @@ loops.
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
Tornado requires the `~asyncio.BaseEventLoop.add_reader` family of methods,
|
Tornado requires the `~asyncio.AbstractEventLoop.add_reader` family of
|
||||||
so it is not compatible with the `~asyncio.ProactorEventLoop` on Windows.
|
methods, so it is not compatible with the `~asyncio.ProactorEventLoop` on
|
||||||
Use the `~asyncio.SelectorEventLoop` instead.
|
Windows. Use the `~asyncio.SelectorEventLoop` instead.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function, with_statement
|
from __future__ import absolute_import, division, print_function, with_statement
|
||||||
|
@ -30,11 +30,11 @@ from tornado import stack_context
|
||||||
try:
|
try:
|
||||||
# Import the real asyncio module for py33+ first. Older versions of the
|
# Import the real asyncio module for py33+ first. Older versions of the
|
||||||
# trollius backport also use this name.
|
# trollius backport also use this name.
|
||||||
import asyncio
|
import asyncio # type: ignore
|
||||||
except ImportError as e:
|
except ImportError as e:
|
||||||
# Asyncio itself isn't available; see if trollius is (backport to py26+).
|
# Asyncio itself isn't available; see if trollius is (backport to py26+).
|
||||||
try:
|
try:
|
||||||
import trollius as asyncio
|
import trollius as asyncio # type: ignore
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# Re-raise the original asyncio error, not the trollius one.
|
# Re-raise the original asyncio error, not the trollius one.
|
||||||
raise e
|
raise e
|
||||||
|
@ -141,6 +141,8 @@ class BaseAsyncIOLoop(IOLoop):
|
||||||
|
|
||||||
def add_callback(self, callback, *args, **kwargs):
|
def add_callback(self, callback, *args, **kwargs):
|
||||||
if self.closing:
|
if self.closing:
|
||||||
|
# TODO: this is racy; we need a lock to ensure that the
|
||||||
|
# loop isn't closed during call_soon_threadsafe.
|
||||||
raise RuntimeError("IOLoop is closing")
|
raise RuntimeError("IOLoop is closing")
|
||||||
self.asyncio_loop.call_soon_threadsafe(
|
self.asyncio_loop.call_soon_threadsafe(
|
||||||
self._run_callback,
|
self._run_callback,
|
||||||
|
@ -158,6 +160,9 @@ class AsyncIOMainLoop(BaseAsyncIOLoop):
|
||||||
import asyncio
|
import asyncio
|
||||||
AsyncIOMainLoop().install()
|
AsyncIOMainLoop().install()
|
||||||
asyncio.get_event_loop().run_forever()
|
asyncio.get_event_loop().run_forever()
|
||||||
|
|
||||||
|
See also :meth:`tornado.ioloop.IOLoop.install` for general notes on
|
||||||
|
installing alternative IOLoops.
|
||||||
"""
|
"""
|
||||||
def initialize(self, **kwargs):
|
def initialize(self, **kwargs):
|
||||||
super(AsyncIOMainLoop, self).initialize(asyncio.get_event_loop(),
|
super(AsyncIOMainLoop, self).initialize(asyncio.get_event_loop(),
|
||||||
|
@ -213,4 +218,4 @@ def to_asyncio_future(tornado_future):
|
||||||
return af
|
return af
|
||||||
|
|
||||||
if hasattr(convert_yielded, 'register'):
|
if hasattr(convert_yielded, 'register'):
|
||||||
convert_yielded.register(asyncio.Future, to_tornado_future)
|
convert_yielded.register(asyncio.Future, to_tornado_future) # type: ignore
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from __future__ import absolute_import, division, print_function, with_statement
|
from __future__ import absolute_import, division, print_function, with_statement
|
||||||
import pycares
|
import pycares # type: ignore
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
from tornado import gen
|
from tornado import gen
|
||||||
|
@ -61,8 +61,8 @@ class CaresResolver(Resolver):
|
||||||
assert not callback_args.kwargs
|
assert not callback_args.kwargs
|
||||||
result, error = callback_args.args
|
result, error = callback_args.args
|
||||||
if error:
|
if error:
|
||||||
raise Exception('C-Ares returned error %s: %s while resolving %s' %
|
raise IOError('C-Ares returned error %s: %s while resolving %s' %
|
||||||
(error, pycares.errno.strerror(error), host))
|
(error, pycares.errno.strerror(error), host))
|
||||||
addresses = result.addresses
|
addresses = result.addresses
|
||||||
addrinfo = []
|
addrinfo = []
|
||||||
for address in addresses:
|
for address in addresses:
|
||||||
|
@ -73,7 +73,7 @@ class CaresResolver(Resolver):
|
||||||
else:
|
else:
|
||||||
address_family = socket.AF_UNSPEC
|
address_family = socket.AF_UNSPEC
|
||||||
if family != socket.AF_UNSPEC and family != address_family:
|
if family != socket.AF_UNSPEC and family != address_family:
|
||||||
raise Exception('Requested socket family %d but got %d' %
|
raise IOError('Requested socket family %d but got %d' %
|
||||||
(family, address_family))
|
(family, address_family))
|
||||||
addrinfo.append((address_family, (address, port)))
|
addrinfo.append((address_family, (address, port)))
|
||||||
raise gen.Return(addrinfo)
|
raise gen.Return(addrinfo)
|
||||||
|
|
|
@ -61,3 +61,6 @@ class Waker(object):
|
||||||
def close(self):
|
def close(self):
|
||||||
"""Closes the waker's file descriptor(s)."""
|
"""Closes the waker's file descriptor(s)."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def monotonic_time():
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
|
@ -29,19 +29,18 @@ import numbers
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
import twisted.internet.abstract
|
import twisted.internet.abstract # type: ignore
|
||||||
from twisted.internet.defer import Deferred
|
from twisted.internet.defer import Deferred # type: ignore
|
||||||
from twisted.internet.posixbase import PosixReactorBase
|
from twisted.internet.posixbase import PosixReactorBase # type: ignore
|
||||||
from twisted.internet.interfaces import \
|
from twisted.internet.interfaces import IReactorFDSet, IDelayedCall, IReactorTime, IReadDescriptor, IWriteDescriptor # type: ignore
|
||||||
IReactorFDSet, IDelayedCall, IReactorTime, IReadDescriptor, IWriteDescriptor
|
from twisted.python import failure, log # type: ignore
|
||||||
from twisted.python import failure, log
|
from twisted.internet import error # type: ignore
|
||||||
from twisted.internet import error
|
import twisted.names.cache # type: ignore
|
||||||
import twisted.names.cache
|
import twisted.names.client # type: ignore
|
||||||
import twisted.names.client
|
import twisted.names.hosts # type: ignore
|
||||||
import twisted.names.hosts
|
import twisted.names.resolve # type: ignore
|
||||||
import twisted.names.resolve
|
|
||||||
|
|
||||||
from zope.interface import implementer
|
from zope.interface import implementer # type: ignore
|
||||||
|
|
||||||
from tornado.concurrent import Future
|
from tornado.concurrent import Future
|
||||||
from tornado.escape import utf8
|
from tornado.escape import utf8
|
||||||
|
@ -354,7 +353,7 @@ def install(io_loop=None):
|
||||||
if not io_loop:
|
if not io_loop:
|
||||||
io_loop = tornado.ioloop.IOLoop.current()
|
io_loop = tornado.ioloop.IOLoop.current()
|
||||||
reactor = TornadoReactor(io_loop)
|
reactor = TornadoReactor(io_loop)
|
||||||
from twisted.internet.main import installReactor
|
from twisted.internet.main import installReactor # type: ignore
|
||||||
installReactor(reactor)
|
installReactor(reactor)
|
||||||
return reactor
|
return reactor
|
||||||
|
|
||||||
|
@ -408,11 +407,14 @@ class TwistedIOLoop(tornado.ioloop.IOLoop):
|
||||||
Not compatible with `tornado.process.Subprocess.set_exit_callback`
|
Not compatible with `tornado.process.Subprocess.set_exit_callback`
|
||||||
because the ``SIGCHLD`` handlers used by Tornado and Twisted conflict
|
because the ``SIGCHLD`` handlers used by Tornado and Twisted conflict
|
||||||
with each other.
|
with each other.
|
||||||
|
|
||||||
|
See also :meth:`tornado.ioloop.IOLoop.install` for general notes on
|
||||||
|
installing alternative IOLoops.
|
||||||
"""
|
"""
|
||||||
def initialize(self, reactor=None, **kwargs):
|
def initialize(self, reactor=None, **kwargs):
|
||||||
super(TwistedIOLoop, self).initialize(**kwargs)
|
super(TwistedIOLoop, self).initialize(**kwargs)
|
||||||
if reactor is None:
|
if reactor is None:
|
||||||
import twisted.internet.reactor
|
import twisted.internet.reactor # type: ignore
|
||||||
reactor = twisted.internet.reactor
|
reactor = twisted.internet.reactor
|
||||||
self.reactor = reactor
|
self.reactor = reactor
|
||||||
self.fds = {}
|
self.fds = {}
|
||||||
|
@ -554,7 +556,10 @@ class TwistedResolver(Resolver):
|
||||||
deferred = self.resolver.getHostByName(utf8(host))
|
deferred = self.resolver.getHostByName(utf8(host))
|
||||||
resolved = yield gen.Task(deferred.addBoth)
|
resolved = yield gen.Task(deferred.addBoth)
|
||||||
if isinstance(resolved, failure.Failure):
|
if isinstance(resolved, failure.Failure):
|
||||||
resolved.raiseException()
|
try:
|
||||||
|
resolved.raiseException()
|
||||||
|
except twisted.names.error.DomainError as e:
|
||||||
|
raise IOError(e)
|
||||||
elif twisted.internet.abstract.isIPAddress(resolved):
|
elif twisted.internet.abstract.isIPAddress(resolved):
|
||||||
resolved_family = socket.AF_INET
|
resolved_family = socket.AF_INET
|
||||||
elif twisted.internet.abstract.isIPv6Address(resolved):
|
elif twisted.internet.abstract.isIPv6Address(resolved):
|
||||||
|
@ -570,7 +575,7 @@ class TwistedResolver(Resolver):
|
||||||
raise gen.Return(result)
|
raise gen.Return(result)
|
||||||
|
|
||||||
if hasattr(gen.convert_yielded, 'register'):
|
if hasattr(gen.convert_yielded, 'register'):
|
||||||
@gen.convert_yielded.register(Deferred)
|
@gen.convert_yielded.register(Deferred) # type: ignore
|
||||||
def _(d):
|
def _(d):
|
||||||
f = Future()
|
f = Future()
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
|
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function, with_statement
|
from __future__ import absolute_import, division, print_function, with_statement
|
||||||
import ctypes
|
import ctypes # type: ignore
|
||||||
import ctypes.wintypes
|
import ctypes.wintypes # type: ignore
|
||||||
|
|
||||||
# See: http://msdn.microsoft.com/en-us/library/ms724935(VS.85).aspx
|
# See: http://msdn.microsoft.com/en-us/library/ms724935(VS.85).aspx
|
||||||
SetHandleInformation = ctypes.windll.kernel32.SetHandleInformation
|
SetHandleInformation = ctypes.windll.kernel32.SetHandleInformation
|
||||||
|
@ -17,4 +17,4 @@ HANDLE_FLAG_INHERIT = 0x00000001
|
||||||
def set_close_exec(fd):
|
def set_close_exec(fd):
|
||||||
success = SetHandleInformation(fd, HANDLE_FLAG_INHERIT, 0)
|
success = SetHandleInformation(fd, HANDLE_FLAG_INHERIT, 0)
|
||||||
if not success:
|
if not success:
|
||||||
raise ctypes.GetLastError()
|
raise ctypes.WinError()
|
||||||
|
|
|
@ -35,7 +35,7 @@ from tornado.iostream import PipeIOStream
|
||||||
from tornado.log import gen_log
|
from tornado.log import gen_log
|
||||||
from tornado.platform.auto import set_close_exec
|
from tornado.platform.auto import set_close_exec
|
||||||
from tornado import stack_context
|
from tornado import stack_context
|
||||||
from tornado.util import errno_from_exception
|
from tornado.util import errno_from_exception, PY3
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import multiprocessing
|
import multiprocessing
|
||||||
|
@ -43,11 +43,8 @@ except ImportError:
|
||||||
# Multiprocessing is not available on Google App Engine.
|
# Multiprocessing is not available on Google App Engine.
|
||||||
multiprocessing = None
|
multiprocessing = None
|
||||||
|
|
||||||
try:
|
if PY3:
|
||||||
long # py2
|
long = int
|
||||||
except NameError:
|
|
||||||
long = int # py3
|
|
||||||
|
|
||||||
|
|
||||||
# Re-export this exception for convenience.
|
# Re-export this exception for convenience.
|
||||||
try:
|
try:
|
||||||
|
@ -147,6 +144,7 @@ def fork_processes(num_processes, max_restarts=100):
|
||||||
else:
|
else:
|
||||||
children[pid] = i
|
children[pid] = i
|
||||||
return None
|
return None
|
||||||
|
|
||||||
for i in range(num_processes):
|
for i in range(num_processes):
|
||||||
id = start_child(i)
|
id = start_child(i)
|
||||||
if id is not None:
|
if id is not None:
|
||||||
|
@ -204,13 +202,19 @@ class Subprocess(object):
|
||||||
attribute of the resulting Subprocess a `.PipeIOStream`.
|
attribute of the resulting Subprocess a `.PipeIOStream`.
|
||||||
* A new keyword argument ``io_loop`` may be used to pass in an IOLoop.
|
* A new keyword argument ``io_loop`` may be used to pass in an IOLoop.
|
||||||
|
|
||||||
|
The ``Subprocess.STREAM`` option and the ``set_exit_callback`` and
|
||||||
|
``wait_for_exit`` methods do not work on Windows. There is
|
||||||
|
therefore no reason to use this class instead of
|
||||||
|
``subprocess.Popen`` on that platform.
|
||||||
|
|
||||||
.. versionchanged:: 4.1
|
.. versionchanged:: 4.1
|
||||||
The ``io_loop`` argument is deprecated.
|
The ``io_loop`` argument is deprecated.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
STREAM = object()
|
STREAM = object()
|
||||||
|
|
||||||
_initialized = False
|
_initialized = False
|
||||||
_waiting = {}
|
_waiting = {} # type: ignore
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
self.io_loop = kwargs.pop('io_loop', None) or ioloop.IOLoop.current()
|
self.io_loop = kwargs.pop('io_loop', None) or ioloop.IOLoop.current()
|
||||||
|
|
|
@ -14,8 +14,6 @@
|
||||||
|
|
||||||
from __future__ import absolute_import, division, print_function, with_statement
|
from __future__ import absolute_import, division, print_function, with_statement
|
||||||
|
|
||||||
__all__ = ['Queue', 'PriorityQueue', 'LifoQueue', 'QueueFull', 'QueueEmpty']
|
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
import heapq
|
import heapq
|
||||||
|
|
||||||
|
@ -23,6 +21,8 @@ from tornado import gen, ioloop
|
||||||
from tornado.concurrent import Future
|
from tornado.concurrent import Future
|
||||||
from tornado.locks import Event
|
from tornado.locks import Event
|
||||||
|
|
||||||
|
__all__ = ['Queue', 'PriorityQueue', 'LifoQueue', 'QueueFull', 'QueueEmpty']
|
||||||
|
|
||||||
|
|
||||||
class QueueEmpty(Exception):
|
class QueueEmpty(Exception):
|
||||||
"""Raised by `.Queue.get_nowait` when the queue has no items."""
|
"""Raised by `.Queue.get_nowait` when the queue has no items."""
|
||||||
|
|
|
@ -11,6 +11,7 @@ from tornado.netutil import Resolver, OverrideResolver, _client_ssl_defaults
|
||||||
from tornado.log import gen_log
|
from tornado.log import gen_log
|
||||||
from tornado import stack_context
|
from tornado import stack_context
|
||||||
from tornado.tcpclient import TCPClient
|
from tornado.tcpclient import TCPClient
|
||||||
|
from tornado.util import PY3
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
import collections
|
import collections
|
||||||
|
@ -22,10 +23,10 @@ import sys
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
|
||||||
|
|
||||||
try:
|
if PY3:
|
||||||
import urlparse # py2
|
import urllib.parse as urlparse
|
||||||
except ImportError:
|
else:
|
||||||
import urllib.parse as urlparse # py3
|
import urlparse
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import ssl
|
import ssl
|
||||||
|
@ -126,7 +127,7 @@ class SimpleAsyncHTTPClient(AsyncHTTPClient):
|
||||||
timeout_handle = self.io_loop.add_timeout(
|
timeout_handle = self.io_loop.add_timeout(
|
||||||
self.io_loop.time() + min(request.connect_timeout,
|
self.io_loop.time() + min(request.connect_timeout,
|
||||||
request.request_timeout),
|
request.request_timeout),
|
||||||
functools.partial(self._on_timeout, key))
|
functools.partial(self._on_timeout, key, "in request queue"))
|
||||||
else:
|
else:
|
||||||
timeout_handle = None
|
timeout_handle = None
|
||||||
self.waiting[key] = (request, callback, timeout_handle)
|
self.waiting[key] = (request, callback, timeout_handle)
|
||||||
|
@ -167,11 +168,20 @@ class SimpleAsyncHTTPClient(AsyncHTTPClient):
|
||||||
self.io_loop.remove_timeout(timeout_handle)
|
self.io_loop.remove_timeout(timeout_handle)
|
||||||
del self.waiting[key]
|
del self.waiting[key]
|
||||||
|
|
||||||
def _on_timeout(self, key):
|
def _on_timeout(self, key, info=None):
|
||||||
|
"""Timeout callback of request.
|
||||||
|
|
||||||
|
Construct a timeout HTTPResponse when a timeout occurs.
|
||||||
|
|
||||||
|
:arg object key: A simple object to mark the request.
|
||||||
|
:info string key: More detailed timeout information.
|
||||||
|
"""
|
||||||
request, callback, timeout_handle = self.waiting[key]
|
request, callback, timeout_handle = self.waiting[key]
|
||||||
self.queue.remove((key, request, callback))
|
self.queue.remove((key, request, callback))
|
||||||
|
|
||||||
|
error_message = "Timeout {0}".format(info) if info else "Timeout"
|
||||||
timeout_response = HTTPResponse(
|
timeout_response = HTTPResponse(
|
||||||
request, 599, error=HTTPError(599, "Timeout"),
|
request, 599, error=HTTPError(599, error_message),
|
||||||
request_time=self.io_loop.time() - request.start_time)
|
request_time=self.io_loop.time() - request.start_time)
|
||||||
self.io_loop.add_callback(callback, timeout_response)
|
self.io_loop.add_callback(callback, timeout_response)
|
||||||
del self.waiting[key]
|
del self.waiting[key]
|
||||||
|
@ -229,7 +239,7 @@ class _HTTPConnection(httputil.HTTPMessageDelegate):
|
||||||
if timeout:
|
if timeout:
|
||||||
self._timeout = self.io_loop.add_timeout(
|
self._timeout = self.io_loop.add_timeout(
|
||||||
self.start_time + timeout,
|
self.start_time + timeout,
|
||||||
stack_context.wrap(self._on_timeout))
|
stack_context.wrap(functools.partial(self._on_timeout, "while connecting")))
|
||||||
self.tcp_client.connect(host, port, af=af,
|
self.tcp_client.connect(host, port, af=af,
|
||||||
ssl_options=ssl_options,
|
ssl_options=ssl_options,
|
||||||
max_buffer_size=self.max_buffer_size,
|
max_buffer_size=self.max_buffer_size,
|
||||||
|
@ -284,10 +294,17 @@ class _HTTPConnection(httputil.HTTPMessageDelegate):
|
||||||
return ssl_options
|
return ssl_options
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _on_timeout(self):
|
def _on_timeout(self, info=None):
|
||||||
|
"""Timeout callback of _HTTPConnection instance.
|
||||||
|
|
||||||
|
Raise a timeout HTTPError when a timeout occurs.
|
||||||
|
|
||||||
|
:info string key: More detailed timeout information.
|
||||||
|
"""
|
||||||
self._timeout = None
|
self._timeout = None
|
||||||
|
error_message = "Timeout {0}".format(info) if info else "Timeout"
|
||||||
if self.final_callback is not None:
|
if self.final_callback is not None:
|
||||||
raise HTTPError(599, "Timeout")
|
raise HTTPError(599, error_message)
|
||||||
|
|
||||||
def _remove_timeout(self):
|
def _remove_timeout(self):
|
||||||
if self._timeout is not None:
|
if self._timeout is not None:
|
||||||
|
@ -307,13 +324,14 @@ class _HTTPConnection(httputil.HTTPMessageDelegate):
|
||||||
if self.request.request_timeout:
|
if self.request.request_timeout:
|
||||||
self._timeout = self.io_loop.add_timeout(
|
self._timeout = self.io_loop.add_timeout(
|
||||||
self.start_time + self.request.request_timeout,
|
self.start_time + self.request.request_timeout,
|
||||||
stack_context.wrap(self._on_timeout))
|
stack_context.wrap(functools.partial(self._on_timeout, "during request")))
|
||||||
if (self.request.method not in self._SUPPORTED_METHODS and
|
if (self.request.method not in self._SUPPORTED_METHODS and
|
||||||
not self.request.allow_nonstandard_methods):
|
not self.request.allow_nonstandard_methods):
|
||||||
raise KeyError("unknown method %s" % self.request.method)
|
raise KeyError("unknown method %s" % self.request.method)
|
||||||
for key in ('network_interface',
|
for key in ('network_interface',
|
||||||
'proxy_host', 'proxy_port',
|
'proxy_host', 'proxy_port',
|
||||||
'proxy_username', 'proxy_password'):
|
'proxy_username', 'proxy_password',
|
||||||
|
'proxy_auth_mode'):
|
||||||
if getattr(self.request, key, None):
|
if getattr(self.request, key, None):
|
||||||
raise NotImplementedError('%s not supported' % key)
|
raise NotImplementedError('%s not supported' % key)
|
||||||
if "Connection" not in self.request.headers:
|
if "Connection" not in self.request.headers:
|
||||||
|
|
|
@ -177,7 +177,13 @@ class TCPClient(object):
|
||||||
def _create_stream(self, max_buffer_size, af, addr):
|
def _create_stream(self, max_buffer_size, af, addr):
|
||||||
# Always connect in plaintext; we'll convert to ssl if necessary
|
# Always connect in plaintext; we'll convert to ssl if necessary
|
||||||
# after one connection has completed.
|
# after one connection has completed.
|
||||||
stream = IOStream(socket.socket(af),
|
try:
|
||||||
io_loop=self.io_loop,
|
stream = IOStream(socket.socket(af),
|
||||||
max_buffer_size=max_buffer_size)
|
io_loop=self.io_loop,
|
||||||
return stream.connect(addr)
|
max_buffer_size=max_buffer_size)
|
||||||
|
except socket.error as e:
|
||||||
|
fu = Future()
|
||||||
|
fu.set_exception(e)
|
||||||
|
return fu
|
||||||
|
else:
|
||||||
|
return stream.connect(addr)
|
||||||
|
|
|
@ -39,7 +39,21 @@ class TCPServer(object):
|
||||||
r"""A non-blocking, single-threaded TCP server.
|
r"""A non-blocking, single-threaded TCP server.
|
||||||
|
|
||||||
To use `TCPServer`, define a subclass which overrides the `handle_stream`
|
To use `TCPServer`, define a subclass which overrides the `handle_stream`
|
||||||
method.
|
method. For example, a simple echo server could be defined like this::
|
||||||
|
|
||||||
|
from tornado.tcpserver import TCPServer
|
||||||
|
from tornado.iostream import StreamClosedError
|
||||||
|
from tornado import gen
|
||||||
|
|
||||||
|
class EchoServer(TCPServer):
|
||||||
|
@gen.coroutine
|
||||||
|
def handle_stream(self, stream, address):
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
data = yield stream.read_until(b"\n")
|
||||||
|
yield stream.write(data)
|
||||||
|
except StreamClosedError:
|
||||||
|
break
|
||||||
|
|
||||||
To make this server serve SSL traffic, send the ``ssl_options`` keyword
|
To make this server serve SSL traffic, send the ``ssl_options`` keyword
|
||||||
argument with an `ssl.SSLContext` object. For compatibility with older
|
argument with an `ssl.SSLContext` object. For compatibility with older
|
||||||
|
@ -147,7 +161,8 @@ class TCPServer(object):
|
||||||
"""Singular version of `add_sockets`. Takes a single socket object."""
|
"""Singular version of `add_sockets`. Takes a single socket object."""
|
||||||
self.add_sockets([socket])
|
self.add_sockets([socket])
|
||||||
|
|
||||||
def bind(self, port, address=None, family=socket.AF_UNSPEC, backlog=128, reuse_port=False):
|
def bind(self, port, address=None, family=socket.AF_UNSPEC, backlog=128,
|
||||||
|
reuse_port=False):
|
||||||
"""Binds this server to the given port on the given address.
|
"""Binds this server to the given port on the given address.
|
||||||
|
|
||||||
To start the server, call `start`. If you want to run this server
|
To start the server, call `start`. If you want to run this server
|
||||||
|
@ -162,10 +177,14 @@ class TCPServer(object):
|
||||||
both will be used if available.
|
both will be used if available.
|
||||||
|
|
||||||
The ``backlog`` argument has the same meaning as for
|
The ``backlog`` argument has the same meaning as for
|
||||||
`socket.listen <socket.socket.listen>`.
|
`socket.listen <socket.socket.listen>`. The ``reuse_port`` argument
|
||||||
|
has the same meaning as for `.bind_sockets`.
|
||||||
|
|
||||||
This method may be called multiple times prior to `start` to listen
|
This method may be called multiple times prior to `start` to listen
|
||||||
on multiple ports or interfaces.
|
on multiple ports or interfaces.
|
||||||
|
|
||||||
|
.. versionchanged:: 4.4
|
||||||
|
Added the ``reuse_port`` argument.
|
||||||
"""
|
"""
|
||||||
sockets = bind_sockets(port, address=address, family=family,
|
sockets = bind_sockets(port, address=address, family=family,
|
||||||
backlog=backlog, reuse_port=reuse_port)
|
backlog=backlog, reuse_port=reuse_port)
|
||||||
|
|
|
@ -19,13 +19,13 @@
|
||||||
Basic usage looks like::
|
Basic usage looks like::
|
||||||
|
|
||||||
t = template.Template("<html>{{ myvalue }}</html>")
|
t = template.Template("<html>{{ myvalue }}</html>")
|
||||||
print t.generate(myvalue="XXX")
|
print(t.generate(myvalue="XXX"))
|
||||||
|
|
||||||
`Loader` is a class that loads templates from a root directory and caches
|
`Loader` is a class that loads templates from a root directory and caches
|
||||||
the compiled templates::
|
the compiled templates::
|
||||||
|
|
||||||
loader = template.Loader("/home/btaylor")
|
loader = template.Loader("/home/btaylor")
|
||||||
print loader.load("test.html").generate(myvalue="XXX")
|
print(loader.load("test.html").generate(myvalue="XXX"))
|
||||||
|
|
||||||
We compile all templates to raw Python. Error-reporting is currently... uh,
|
We compile all templates to raw Python. Error-reporting is currently... uh,
|
||||||
interesting. Syntax for the templates::
|
interesting. Syntax for the templates::
|
||||||
|
@ -94,12 +94,15 @@ Syntax Reference
|
||||||
Template expressions are surrounded by double curly braces: ``{{ ... }}``.
|
Template expressions are surrounded by double curly braces: ``{{ ... }}``.
|
||||||
The contents may be any python expression, which will be escaped according
|
The contents may be any python expression, which will be escaped according
|
||||||
to the current autoescape setting and inserted into the output. Other
|
to the current autoescape setting and inserted into the output. Other
|
||||||
template directives use ``{% %}``. These tags may be escaped as ``{{!``
|
template directives use ``{% %}``.
|
||||||
and ``{%!`` if you need to include a literal ``{{`` or ``{%`` in the output.
|
|
||||||
|
|
||||||
To comment out a section so that it is omitted from the output, surround it
|
To comment out a section so that it is omitted from the output, surround it
|
||||||
with ``{# ... #}``.
|
with ``{# ... #}``.
|
||||||
|
|
||||||
|
These tags may be escaped as ``{{!``, ``{%!``, and ``{#!``
|
||||||
|
if you need to include a literal ``{{``, ``{%``, or ``{#`` in the output.
|
||||||
|
|
||||||
|
|
||||||
``{% apply *function* %}...{% end %}``
|
``{% apply *function* %}...{% end %}``
|
||||||
Applies a function to the output of all template code between ``apply``
|
Applies a function to the output of all template code between ``apply``
|
||||||
and ``end``::
|
and ``end``::
|
||||||
|
@ -204,12 +207,12 @@ import threading
|
||||||
|
|
||||||
from tornado import escape
|
from tornado import escape
|
||||||
from tornado.log import app_log
|
from tornado.log import app_log
|
||||||
from tornado.util import ObjectDict, exec_in, unicode_type
|
from tornado.util import ObjectDict, exec_in, unicode_type, PY3
|
||||||
|
|
||||||
try:
|
if PY3:
|
||||||
from cStringIO import StringIO # py2
|
from io import StringIO
|
||||||
except ImportError:
|
else:
|
||||||
from io import StringIO # py3
|
from cStringIO import StringIO
|
||||||
|
|
||||||
_DEFAULT_AUTOESCAPE = "xhtml_escape"
|
_DEFAULT_AUTOESCAPE = "xhtml_escape"
|
||||||
_UNSET = object()
|
_UNSET = object()
|
||||||
|
@ -665,7 +668,7 @@ class ParseError(Exception):
|
||||||
.. versionchanged:: 4.3
|
.. versionchanged:: 4.3
|
||||||
Added ``filename`` and ``lineno`` attributes.
|
Added ``filename`` and ``lineno`` attributes.
|
||||||
"""
|
"""
|
||||||
def __init__(self, message, filename, lineno):
|
def __init__(self, message, filename=None, lineno=0):
|
||||||
self.message = message
|
self.message = message
|
||||||
# The names "filename" and "lineno" are chosen for consistency
|
# The names "filename" and "lineno" are chosen for consistency
|
||||||
# with python SyntaxError.
|
# with python SyntaxError.
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"""Support classes for automated testing.
|
"""Support classes for automated testing.
|
||||||
|
|
||||||
* `AsyncTestCase` and `AsyncHTTPTestCase`: Subclasses of unittest.TestCase
|
* `AsyncTestCase` and `AsyncHTTPTestCase`: Subclasses of unittest.TestCase
|
||||||
with additional support for testing asynchronous (`.IOLoop` based) code.
|
with additional support for testing asynchronous (`.IOLoop`-based) code.
|
||||||
|
|
||||||
* `ExpectLog` and `LogTrapTestCase`: Make test logs less spammy.
|
* `ExpectLog` and `LogTrapTestCase`: Make test logs less spammy.
|
||||||
|
|
||||||
|
@ -23,16 +23,16 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
# These modules are not importable on app engine. Parts of this module
|
# These modules are not importable on app engine. Parts of this module
|
||||||
# won't work, but e.g. LogTrapTestCase and main() will.
|
# won't work, but e.g. LogTrapTestCase and main() will.
|
||||||
AsyncHTTPClient = None
|
AsyncHTTPClient = None # type: ignore
|
||||||
gen = None
|
gen = None # type: ignore
|
||||||
HTTPServer = None
|
HTTPServer = None # type: ignore
|
||||||
IOLoop = None
|
IOLoop = None # type: ignore
|
||||||
netutil = None
|
netutil = None # type: ignore
|
||||||
SimpleAsyncHTTPClient = None
|
SimpleAsyncHTTPClient = None # type: ignore
|
||||||
Subprocess = None
|
Subprocess = None # type: ignore
|
||||||
from tornado.log import gen_log, app_log
|
from tornado.log import gen_log, app_log
|
||||||
from tornado.stack_context import ExceptionStackContext
|
from tornado.stack_context import ExceptionStackContext
|
||||||
from tornado.util import raise_exc_info, basestring_type
|
from tornado.util import raise_exc_info, basestring_type, PY3
|
||||||
import functools
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
import logging
|
import logging
|
||||||
|
@ -42,19 +42,19 @@ import signal
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
try:
|
if PY3:
|
||||||
from cStringIO import StringIO # py2
|
from io import StringIO
|
||||||
except ImportError:
|
else:
|
||||||
from io import StringIO # py3
|
from cStringIO import StringIO
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from collections.abc import Generator as GeneratorType # py35+
|
from collections.abc import Generator as GeneratorType # type: ignore
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from types import GeneratorType
|
from types import GeneratorType # type: ignore
|
||||||
|
|
||||||
if sys.version_info >= (3, 5):
|
if sys.version_info >= (3, 5):
|
||||||
iscoroutine = inspect.iscoroutine
|
iscoroutine = inspect.iscoroutine # type: ignore
|
||||||
iscoroutinefunction = inspect.iscoroutinefunction
|
iscoroutinefunction = inspect.iscoroutinefunction # type: ignore
|
||||||
else:
|
else:
|
||||||
iscoroutine = iscoroutinefunction = lambda f: False
|
iscoroutine = iscoroutinefunction = lambda f: False
|
||||||
|
|
||||||
|
@ -62,16 +62,16 @@ else:
|
||||||
# (either py27+ or unittest2) so tornado.test.util enforces
|
# (either py27+ or unittest2) so tornado.test.util enforces
|
||||||
# this requirement, but for other users of tornado.testing we want
|
# this requirement, but for other users of tornado.testing we want
|
||||||
# to allow the older version if unitest2 is not available.
|
# to allow the older version if unitest2 is not available.
|
||||||
if sys.version_info >= (3,):
|
if PY3:
|
||||||
# On python 3, mixing unittest2 and unittest (including doctest)
|
# On python 3, mixing unittest2 and unittest (including doctest)
|
||||||
# doesn't seem to work, so always use unittest.
|
# doesn't seem to work, so always use unittest.
|
||||||
import unittest
|
import unittest
|
||||||
else:
|
else:
|
||||||
# On python 2, prefer unittest2 when available.
|
# On python 2, prefer unittest2 when available.
|
||||||
try:
|
try:
|
||||||
import unittest2 as unittest
|
import unittest2 as unittest # type: ignore
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import unittest
|
import unittest # type: ignore
|
||||||
|
|
||||||
_next_port = 10000
|
_next_port = 10000
|
||||||
|
|
||||||
|
@ -96,9 +96,13 @@ def bind_unused_port(reuse_port=False):
|
||||||
"""Binds a server socket to an available port on localhost.
|
"""Binds a server socket to an available port on localhost.
|
||||||
|
|
||||||
Returns a tuple (socket, port).
|
Returns a tuple (socket, port).
|
||||||
|
|
||||||
|
.. versionchanged:: 4.4
|
||||||
|
Always binds to ``127.0.0.1`` without resolving the name
|
||||||
|
``localhost``.
|
||||||
"""
|
"""
|
||||||
sock = netutil.bind_sockets(None, '127.0.0.1', family=socket.AF_INET,
|
sock = netutil.bind_sockets(None, '127.0.0.1', family=socket.AF_INET,
|
||||||
reuse_port=reuse_port)[0]
|
reuse_port=reuse_port)[0]
|
||||||
port = sock.getsockname()[1]
|
port = sock.getsockname()[1]
|
||||||
return sock, port
|
return sock, port
|
||||||
|
|
||||||
|
@ -123,7 +127,7 @@ class _TestMethodWrapper(object):
|
||||||
method yields it must use a decorator to consume the generator),
|
method yields it must use a decorator to consume the generator),
|
||||||
but will also detect other kinds of return values (these are not
|
but will also detect other kinds of return values (these are not
|
||||||
necessarily errors, but we alert anyway since there is no good
|
necessarily errors, but we alert anyway since there is no good
|
||||||
reason to return a value from a test.
|
reason to return a value from a test).
|
||||||
"""
|
"""
|
||||||
def __init__(self, orig_method):
|
def __init__(self, orig_method):
|
||||||
self.orig_method = orig_method
|
self.orig_method = orig_method
|
||||||
|
@ -208,8 +212,8 @@ class AsyncTestCase(unittest.TestCase):
|
||||||
self.assertIn("FriendFeed", response.body)
|
self.assertIn("FriendFeed", response.body)
|
||||||
self.stop()
|
self.stop()
|
||||||
"""
|
"""
|
||||||
def __init__(self, methodName='runTest', **kwargs):
|
def __init__(self, methodName='runTest'):
|
||||||
super(AsyncTestCase, self).__init__(methodName, **kwargs)
|
super(AsyncTestCase, self).__init__(methodName)
|
||||||
self.__stopped = False
|
self.__stopped = False
|
||||||
self.__running = False
|
self.__running = False
|
||||||
self.__failure = None
|
self.__failure = None
|
||||||
|
@ -547,7 +551,7 @@ def gen_test(func=None, timeout=None):
|
||||||
|
|
||||||
# Without this attribute, nosetests will try to run gen_test as a test
|
# Without this attribute, nosetests will try to run gen_test as a test
|
||||||
# anywhere it is imported.
|
# anywhere it is imported.
|
||||||
gen_test.__test__ = False
|
gen_test.__test__ = False # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class LogTrapTestCase(unittest.TestCase):
|
class LogTrapTestCase(unittest.TestCase):
|
||||||
|
@ -617,7 +621,7 @@ class ExpectLog(logging.Filter):
|
||||||
an empty string to watch the root logger.
|
an empty string to watch the root logger.
|
||||||
:param regex: Regular expression to match. Any log entries on
|
:param regex: Regular expression to match. Any log entries on
|
||||||
the specified logger that match this regex will be suppressed.
|
the specified logger that match this regex will be suppressed.
|
||||||
:param required: If true, an exeption will be raised if the end of
|
:param required: If true, an exception will be raised if the end of
|
||||||
the ``with`` statement is reached without matching any log entries.
|
the ``with`` statement is reached without matching any log entries.
|
||||||
"""
|
"""
|
||||||
if isinstance(logger, basestring_type):
|
if isinstance(logger, basestring_type):
|
||||||
|
|
|
@ -14,33 +14,70 @@ from __future__ import absolute_import, division, print_function, with_statement
|
||||||
|
|
||||||
import array
|
import array
|
||||||
import os
|
import os
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
import zlib
|
import zlib
|
||||||
|
|
||||||
|
PY3 = sys.version_info >= (3,)
|
||||||
|
|
||||||
try:
|
if PY3:
|
||||||
xrange # py2
|
xrange = range
|
||||||
except NameError:
|
|
||||||
xrange = range # py3
|
|
||||||
|
|
||||||
# inspect.getargspec() raises DeprecationWarnings in Python 3.5.
|
# inspect.getargspec() raises DeprecationWarnings in Python 3.5.
|
||||||
# The two functions have compatible interfaces for the parts we need.
|
# The two functions have compatible interfaces for the parts we need.
|
||||||
|
if PY3:
|
||||||
|
from inspect import getfullargspec as getargspec
|
||||||
|
else:
|
||||||
|
from inspect import getargspec
|
||||||
|
|
||||||
|
# Aliases for types that are spelled differently in different Python
|
||||||
|
# versions. bytes_type is deprecated and no longer used in Tornado
|
||||||
|
# itself but is left in case anyone outside Tornado is using it.
|
||||||
|
bytes_type = bytes
|
||||||
|
if PY3:
|
||||||
|
unicode_type = str
|
||||||
|
basestring_type = str
|
||||||
|
else:
|
||||||
|
# The names unicode and basestring don't exist in py3 so silence flake8.
|
||||||
|
unicode_type = unicode # noqa
|
||||||
|
basestring_type = basestring # noqa
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from inspect import getfullargspec as getargspec # py3
|
import typing # noqa
|
||||||
|
from typing import cast
|
||||||
|
|
||||||
|
_ObjectDictBase = typing.Dict[str, typing.Any]
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from inspect import getargspec # py2
|
_ObjectDictBase = dict
|
||||||
|
|
||||||
|
def cast(typ, x):
|
||||||
|
return x
|
||||||
|
else:
|
||||||
|
# More imports that are only needed in type comments.
|
||||||
|
import datetime # noqa
|
||||||
|
import types # noqa
|
||||||
|
from typing import Any, AnyStr, Union, Optional, Dict, Mapping # noqa
|
||||||
|
from typing import Tuple, Match, Callable # noqa
|
||||||
|
|
||||||
|
if PY3:
|
||||||
|
_BaseString = str
|
||||||
|
else:
|
||||||
|
_BaseString = Union[bytes, unicode_type]
|
||||||
|
|
||||||
|
|
||||||
class ObjectDict(dict):
|
class ObjectDict(_ObjectDictBase):
|
||||||
"""Makes a dictionary behave like an object, with attribute-style access.
|
"""Makes a dictionary behave like an object, with attribute-style access.
|
||||||
"""
|
"""
|
||||||
def __getattr__(self, name):
|
def __getattr__(self, name):
|
||||||
|
# type: (str) -> Any
|
||||||
try:
|
try:
|
||||||
return self[name]
|
return self[name]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise AttributeError(name)
|
raise AttributeError(name)
|
||||||
|
|
||||||
def __setattr__(self, name, value):
|
def __setattr__(self, name, value):
|
||||||
|
# type: (str, Any) -> None
|
||||||
self[name] = value
|
self[name] = value
|
||||||
|
|
||||||
|
|
||||||
|
@ -57,6 +94,7 @@ class GzipDecompressor(object):
|
||||||
self.decompressobj = zlib.decompressobj(16 + zlib.MAX_WBITS)
|
self.decompressobj = zlib.decompressobj(16 + zlib.MAX_WBITS)
|
||||||
|
|
||||||
def decompress(self, value, max_length=None):
|
def decompress(self, value, max_length=None):
|
||||||
|
# type: (bytes, Optional[int]) -> bytes
|
||||||
"""Decompress a chunk, returning newly-available data.
|
"""Decompress a chunk, returning newly-available data.
|
||||||
|
|
||||||
Some data may be buffered for later processing; `flush` must
|
Some data may be buffered for later processing; `flush` must
|
||||||
|
@ -71,11 +109,13 @@ class GzipDecompressor(object):
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unconsumed_tail(self):
|
def unconsumed_tail(self):
|
||||||
|
# type: () -> bytes
|
||||||
"""Returns the unconsumed portion left over
|
"""Returns the unconsumed portion left over
|
||||||
"""
|
"""
|
||||||
return self.decompressobj.unconsumed_tail
|
return self.decompressobj.unconsumed_tail
|
||||||
|
|
||||||
def flush(self):
|
def flush(self):
|
||||||
|
# type: () -> bytes
|
||||||
"""Return any remaining buffered data not yet returned by decompress.
|
"""Return any remaining buffered data not yet returned by decompress.
|
||||||
|
|
||||||
Also checks for errors such as truncated input.
|
Also checks for errors such as truncated input.
|
||||||
|
@ -84,17 +124,8 @@ class GzipDecompressor(object):
|
||||||
return self.decompressobj.flush()
|
return self.decompressobj.flush()
|
||||||
|
|
||||||
|
|
||||||
if not isinstance(b'', type('')):
|
|
||||||
unicode_type = str
|
|
||||||
basestring_type = str
|
|
||||||
else:
|
|
||||||
# These names don't exist in py3, so use noqa comments to disable
|
|
||||||
# warnings in flake8.
|
|
||||||
unicode_type = unicode # noqa
|
|
||||||
basestring_type = basestring # noqa
|
|
||||||
|
|
||||||
|
|
||||||
def import_object(name):
|
def import_object(name):
|
||||||
|
# type: (_BaseString) -> Any
|
||||||
"""Imports an object by name.
|
"""Imports an object by name.
|
||||||
|
|
||||||
import_object('x') is equivalent to 'import x'.
|
import_object('x') is equivalent to 'import x'.
|
||||||
|
@ -112,8 +143,8 @@ def import_object(name):
|
||||||
...
|
...
|
||||||
ImportError: No module named missing_module
|
ImportError: No module named missing_module
|
||||||
"""
|
"""
|
||||||
if isinstance(name, unicode_type) and str is not unicode_type:
|
if not isinstance(name, str):
|
||||||
# On python 2 a byte string is required.
|
# on python 2 a byte string is required.
|
||||||
name = name.encode('utf-8')
|
name = name.encode('utf-8')
|
||||||
if name.count('.') == 0:
|
if name.count('.') == 0:
|
||||||
return __import__(name, None, None)
|
return __import__(name, None, None)
|
||||||
|
@ -126,35 +157,35 @@ def import_object(name):
|
||||||
raise ImportError("No module named %s" % parts[-1])
|
raise ImportError("No module named %s" % parts[-1])
|
||||||
|
|
||||||
|
|
||||||
# Deprecated alias that was used before we dropped py25 support.
|
# Stubs to make mypy happy (and later for actual type-checking).
|
||||||
# Left here in case anyone outside Tornado is using it.
|
def raise_exc_info(exc_info):
|
||||||
bytes_type = bytes
|
# type: (Tuple[type, BaseException, types.TracebackType]) -> None
|
||||||
|
pass
|
||||||
|
|
||||||
if sys.version_info > (3,):
|
|
||||||
|
def exec_in(code, glob, loc=None):
|
||||||
|
# type: (Any, Dict[str, Any], Optional[Mapping[str, Any]]) -> Any
|
||||||
|
if isinstance(code, basestring_type):
|
||||||
|
# exec(string) inherits the caller's future imports; compile
|
||||||
|
# the string first to prevent that.
|
||||||
|
code = compile(code, '<string>', 'exec', dont_inherit=True)
|
||||||
|
exec(code, glob, loc)
|
||||||
|
|
||||||
|
|
||||||
|
if PY3:
|
||||||
exec("""
|
exec("""
|
||||||
def raise_exc_info(exc_info):
|
def raise_exc_info(exc_info):
|
||||||
raise exc_info[1].with_traceback(exc_info[2])
|
raise exc_info[1].with_traceback(exc_info[2])
|
||||||
|
|
||||||
def exec_in(code, glob, loc=None):
|
|
||||||
if isinstance(code, str):
|
|
||||||
code = compile(code, '<string>', 'exec', dont_inherit=True)
|
|
||||||
exec(code, glob, loc)
|
|
||||||
""")
|
""")
|
||||||
else:
|
else:
|
||||||
exec("""
|
exec("""
|
||||||
def raise_exc_info(exc_info):
|
def raise_exc_info(exc_info):
|
||||||
raise exc_info[0], exc_info[1], exc_info[2]
|
raise exc_info[0], exc_info[1], exc_info[2]
|
||||||
|
|
||||||
def exec_in(code, glob, loc=None):
|
|
||||||
if isinstance(code, basestring):
|
|
||||||
# exec(string) inherits the caller's future imports; compile
|
|
||||||
# the string first to prevent that.
|
|
||||||
code = compile(code, '<string>', 'exec', dont_inherit=True)
|
|
||||||
exec code in glob, loc
|
|
||||||
""")
|
""")
|
||||||
|
|
||||||
|
|
||||||
def errno_from_exception(e):
|
def errno_from_exception(e):
|
||||||
|
# type: (BaseException) -> Optional[int]
|
||||||
"""Provides the errno from an Exception object.
|
"""Provides the errno from an Exception object.
|
||||||
|
|
||||||
There are cases that the errno attribute was not set so we pull
|
There are cases that the errno attribute was not set so we pull
|
||||||
|
@ -165,13 +196,40 @@ def errno_from_exception(e):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if hasattr(e, 'errno'):
|
if hasattr(e, 'errno'):
|
||||||
return e.errno
|
return e.errno # type: ignore
|
||||||
elif e.args:
|
elif e.args:
|
||||||
return e.args[0]
|
return e.args[0]
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
_alphanum = frozenset(
|
||||||
|
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789")
|
||||||
|
|
||||||
|
|
||||||
|
def _re_unescape_replacement(match):
|
||||||
|
# type: (Match[str]) -> str
|
||||||
|
group = match.group(1)
|
||||||
|
if group[0] in _alphanum:
|
||||||
|
raise ValueError("cannot unescape '\\\\%s'" % group[0])
|
||||||
|
return group
|
||||||
|
|
||||||
|
_re_unescape_pattern = re.compile(r'\\(.)', re.DOTALL)
|
||||||
|
|
||||||
|
|
||||||
|
def re_unescape(s):
|
||||||
|
# type: (str) -> str
|
||||||
|
"""Unescape a string escaped by `re.escape`.
|
||||||
|
|
||||||
|
May raise ``ValueError`` for regular expressions which could not
|
||||||
|
have been produced by `re.escape` (for example, strings containing
|
||||||
|
``\d`` cannot be unescaped).
|
||||||
|
|
||||||
|
.. versionadded:: 4.4
|
||||||
|
"""
|
||||||
|
return _re_unescape_pattern.sub(_re_unescape_replacement, s)
|
||||||
|
|
||||||
|
|
||||||
class Configurable(object):
|
class Configurable(object):
|
||||||
"""Base class for configurable interfaces.
|
"""Base class for configurable interfaces.
|
||||||
|
|
||||||
|
@ -192,8 +250,8 @@ class Configurable(object):
|
||||||
`configurable_base` and `configurable_default`, and use the instance
|
`configurable_base` and `configurable_default`, and use the instance
|
||||||
method `initialize` instead of ``__init__``.
|
method `initialize` instead of ``__init__``.
|
||||||
"""
|
"""
|
||||||
__impl_class = None
|
__impl_class = None # type: type
|
||||||
__impl_kwargs = None
|
__impl_kwargs = None # type: Dict[str, Any]
|
||||||
|
|
||||||
def __new__(cls, *args, **kwargs):
|
def __new__(cls, *args, **kwargs):
|
||||||
base = cls.configurable_base()
|
base = cls.configurable_base()
|
||||||
|
@ -214,6 +272,9 @@ class Configurable(object):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def configurable_base(cls):
|
def configurable_base(cls):
|
||||||
|
# type: () -> Any
|
||||||
|
# TODO: This class needs https://github.com/python/typing/issues/107
|
||||||
|
# to be fully typeable.
|
||||||
"""Returns the base class of a configurable hierarchy.
|
"""Returns the base class of a configurable hierarchy.
|
||||||
|
|
||||||
This will normally return the class in which it is defined.
|
This will normally return the class in which it is defined.
|
||||||
|
@ -223,10 +284,12 @@ class Configurable(object):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def configurable_default(cls):
|
def configurable_default(cls):
|
||||||
|
# type: () -> type
|
||||||
"""Returns the implementation class to be used if none is configured."""
|
"""Returns the implementation class to be used if none is configured."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
|
# type: () -> None
|
||||||
"""Initialize a `Configurable` subclass instance.
|
"""Initialize a `Configurable` subclass instance.
|
||||||
|
|
||||||
Configurable classes should use `initialize` instead of ``__init__``.
|
Configurable classes should use `initialize` instead of ``__init__``.
|
||||||
|
@ -237,6 +300,7 @@ class Configurable(object):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def configure(cls, impl, **kwargs):
|
def configure(cls, impl, **kwargs):
|
||||||
|
# type: (Any, **Any) -> None
|
||||||
"""Sets the class to use when the base class is instantiated.
|
"""Sets the class to use when the base class is instantiated.
|
||||||
|
|
||||||
Keyword arguments will be saved and added to the arguments passed
|
Keyword arguments will be saved and added to the arguments passed
|
||||||
|
@ -244,7 +308,7 @@ class Configurable(object):
|
||||||
some parameters.
|
some parameters.
|
||||||
"""
|
"""
|
||||||
base = cls.configurable_base()
|
base = cls.configurable_base()
|
||||||
if isinstance(impl, (unicode_type, bytes)):
|
if isinstance(impl, (str, unicode_type)):
|
||||||
impl = import_object(impl)
|
impl = import_object(impl)
|
||||||
if impl is not None and not issubclass(impl, cls):
|
if impl is not None and not issubclass(impl, cls):
|
||||||
raise ValueError("Invalid subclass of %s" % cls)
|
raise ValueError("Invalid subclass of %s" % cls)
|
||||||
|
@ -253,6 +317,7 @@ class Configurable(object):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def configured_class(cls):
|
def configured_class(cls):
|
||||||
|
# type: () -> type
|
||||||
"""Returns the currently configured class."""
|
"""Returns the currently configured class."""
|
||||||
base = cls.configurable_base()
|
base = cls.configurable_base()
|
||||||
if cls.__impl_class is None:
|
if cls.__impl_class is None:
|
||||||
|
@ -261,11 +326,13 @@ class Configurable(object):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _save_configuration(cls):
|
def _save_configuration(cls):
|
||||||
|
# type: () -> Tuple[type, Dict[str, Any]]
|
||||||
base = cls.configurable_base()
|
base = cls.configurable_base()
|
||||||
return (base.__impl_class, base.__impl_kwargs)
|
return (base.__impl_class, base.__impl_kwargs)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _restore_configuration(cls, saved):
|
def _restore_configuration(cls, saved):
|
||||||
|
# type: (Tuple[type, Dict[str, Any]]) -> None
|
||||||
base = cls.configurable_base()
|
base = cls.configurable_base()
|
||||||
base.__impl_class = saved[0]
|
base.__impl_class = saved[0]
|
||||||
base.__impl_kwargs = saved[1]
|
base.__impl_kwargs = saved[1]
|
||||||
|
@ -279,6 +346,7 @@ class ArgReplacer(object):
|
||||||
and similar wrappers.
|
and similar wrappers.
|
||||||
"""
|
"""
|
||||||
def __init__(self, func, name):
|
def __init__(self, func, name):
|
||||||
|
# type: (Callable, str) -> None
|
||||||
self.name = name
|
self.name = name
|
||||||
try:
|
try:
|
||||||
self.arg_pos = self._getargnames(func).index(name)
|
self.arg_pos = self._getargnames(func).index(name)
|
||||||
|
@ -287,6 +355,7 @@ class ArgReplacer(object):
|
||||||
self.arg_pos = None
|
self.arg_pos = None
|
||||||
|
|
||||||
def _getargnames(self, func):
|
def _getargnames(self, func):
|
||||||
|
# type: (Callable) -> List[str]
|
||||||
try:
|
try:
|
||||||
return getargspec(func).args
|
return getargspec(func).args
|
||||||
except TypeError:
|
except TypeError:
|
||||||
|
@ -297,11 +366,12 @@ class ArgReplacer(object):
|
||||||
# getargspec that we need here. Note that for static
|
# getargspec that we need here. Note that for static
|
||||||
# functions the @cython.binding(True) decorator must
|
# functions the @cython.binding(True) decorator must
|
||||||
# be used (for methods it works out of the box).
|
# be used (for methods it works out of the box).
|
||||||
code = func.func_code
|
code = func.func_code # type: ignore
|
||||||
return code.co_varnames[:code.co_argcount]
|
return code.co_varnames[:code.co_argcount]
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def get_old_value(self, args, kwargs, default=None):
|
def get_old_value(self, args, kwargs, default=None):
|
||||||
|
# type: (List[Any], Dict[str, Any], Any) -> Any
|
||||||
"""Returns the old value of the named argument without replacing it.
|
"""Returns the old value of the named argument without replacing it.
|
||||||
|
|
||||||
Returns ``default`` if the argument is not present.
|
Returns ``default`` if the argument is not present.
|
||||||
|
@ -312,6 +382,7 @@ class ArgReplacer(object):
|
||||||
return kwargs.get(self.name, default)
|
return kwargs.get(self.name, default)
|
||||||
|
|
||||||
def replace(self, new_value, args, kwargs):
|
def replace(self, new_value, args, kwargs):
|
||||||
|
# type: (Any, List[Any], Dict[str, Any]) -> Tuple[Any, List[Any], Dict[str, Any]]
|
||||||
"""Replace the named argument in ``args, kwargs`` with ``new_value``.
|
"""Replace the named argument in ``args, kwargs`` with ``new_value``.
|
||||||
|
|
||||||
Returns ``(old_value, args, kwargs)``. The returned ``args`` and
|
Returns ``(old_value, args, kwargs)``. The returned ``args`` and
|
||||||
|
@ -334,11 +405,13 @@ class ArgReplacer(object):
|
||||||
|
|
||||||
|
|
||||||
def timedelta_to_seconds(td):
|
def timedelta_to_seconds(td):
|
||||||
|
# type: (datetime.timedelta) -> float
|
||||||
"""Equivalent to td.total_seconds() (introduced in python 2.7)."""
|
"""Equivalent to td.total_seconds() (introduced in python 2.7)."""
|
||||||
return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10 ** 6) / float(10 ** 6)
|
return (td.microseconds + (td.seconds + td.days * 24 * 3600) * 10 ** 6) / float(10 ** 6)
|
||||||
|
|
||||||
|
|
||||||
def _websocket_mask_python(mask, data):
|
def _websocket_mask_python(mask, data):
|
||||||
|
# type: (bytes, bytes) -> bytes
|
||||||
"""Websocket masking function.
|
"""Websocket masking function.
|
||||||
|
|
||||||
`mask` is a `bytes` object of length 4; `data` is a `bytes` object of any length.
|
`mask` is a `bytes` object of length 4; `data` is a `bytes` object of any length.
|
||||||
|
@ -347,17 +420,17 @@ def _websocket_mask_python(mask, data):
|
||||||
|
|
||||||
This pure-python implementation may be replaced by an optimized version when available.
|
This pure-python implementation may be replaced by an optimized version when available.
|
||||||
"""
|
"""
|
||||||
mask = array.array("B", mask)
|
mask_arr = array.array("B", mask)
|
||||||
unmasked = array.array("B", data)
|
unmasked_arr = array.array("B", data)
|
||||||
for i in xrange(len(data)):
|
for i in xrange(len(data)):
|
||||||
unmasked[i] = unmasked[i] ^ mask[i % 4]
|
unmasked_arr[i] = unmasked_arr[i] ^ mask_arr[i % 4]
|
||||||
if hasattr(unmasked, 'tobytes'):
|
if PY3:
|
||||||
# tostring was deprecated in py32. It hasn't been removed,
|
# tostring was deprecated in py32. It hasn't been removed,
|
||||||
# but since we turn on deprecation warnings in our tests
|
# but since we turn on deprecation warnings in our tests
|
||||||
# we need to use the right one.
|
# we need to use the right one.
|
||||||
return unmasked.tobytes()
|
return unmasked_arr.tobytes()
|
||||||
else:
|
else:
|
||||||
return unmasked.tostring()
|
return unmasked_arr.tostring()
|
||||||
|
|
||||||
if (os.environ.get('TORNADO_NO_EXTENSION') or
|
if (os.environ.get('TORNADO_NO_EXTENSION') or
|
||||||
os.environ.get('TORNADO_EXTENSION') == '0'):
|
os.environ.get('TORNADO_EXTENSION') == '0'):
|
||||||
|
|
|
@ -90,24 +90,27 @@ from tornado import stack_context
|
||||||
from tornado import template
|
from tornado import template
|
||||||
from tornado.escape import utf8, _unicode
|
from tornado.escape import utf8, _unicode
|
||||||
from tornado.util import (import_object, ObjectDict, raise_exc_info,
|
from tornado.util import (import_object, ObjectDict, raise_exc_info,
|
||||||
unicode_type, _websocket_mask)
|
unicode_type, _websocket_mask, re_unescape, PY3)
|
||||||
from tornado.httputil import split_host_and_port
|
from tornado.httputil import split_host_and_port
|
||||||
|
|
||||||
|
if PY3:
|
||||||
|
import http.cookies as Cookie
|
||||||
|
import urllib.parse as urlparse
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
else:
|
||||||
|
import Cookie
|
||||||
|
import urlparse
|
||||||
|
from urllib import urlencode
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import Cookie # py2
|
import typing # noqa
|
||||||
except ImportError:
|
|
||||||
import http.cookies as Cookie # py3
|
|
||||||
|
|
||||||
try:
|
# The following types are accepted by RequestHandler.set_header
|
||||||
import urlparse # py2
|
# and related methods.
|
||||||
|
_HeaderTypes = typing.Union[bytes, unicode_type,
|
||||||
|
numbers.Integral, datetime.datetime]
|
||||||
except ImportError:
|
except ImportError:
|
||||||
import urllib.parse as urlparse # py3
|
pass
|
||||||
|
|
||||||
try:
|
|
||||||
from urllib import urlencode # py2
|
|
||||||
except ImportError:
|
|
||||||
from urllib.parse import urlencode # py3
|
|
||||||
|
|
||||||
|
|
||||||
MIN_SUPPORTED_SIGNED_VALUE_VERSION = 1
|
MIN_SUPPORTED_SIGNED_VALUE_VERSION = 1
|
||||||
|
@ -152,7 +155,7 @@ class RequestHandler(object):
|
||||||
SUPPORTED_METHODS = ("GET", "HEAD", "POST", "DELETE", "PATCH", "PUT",
|
SUPPORTED_METHODS = ("GET", "HEAD", "POST", "DELETE", "PATCH", "PUT",
|
||||||
"OPTIONS")
|
"OPTIONS")
|
||||||
|
|
||||||
_template_loaders = {} # {path: template.BaseLoader}
|
_template_loaders = {} # type: typing.Dict[str, template.BaseLoader]
|
||||||
_template_loader_lock = threading.Lock()
|
_template_loader_lock = threading.Lock()
|
||||||
_remove_control_chars_regex = re.compile(r"[\x00-\x08\x0e-\x1f]")
|
_remove_control_chars_regex = re.compile(r"[\x00-\x08\x0e-\x1f]")
|
||||||
|
|
||||||
|
@ -166,6 +169,7 @@ class RequestHandler(object):
|
||||||
self._auto_finish = True
|
self._auto_finish = True
|
||||||
self._transforms = None # will be set in _execute
|
self._transforms = None # will be set in _execute
|
||||||
self._prepared_future = None
|
self._prepared_future = None
|
||||||
|
self._headers = None # type: httputil.HTTPHeaders
|
||||||
self.path_args = None
|
self.path_args = None
|
||||||
self.path_kwargs = None
|
self.path_kwargs = None
|
||||||
self.ui = ObjectDict((n, self._ui_method(m)) for n, m in
|
self.ui = ObjectDict((n, self._ui_method(m)) for n, m in
|
||||||
|
@ -184,7 +188,7 @@ class RequestHandler(object):
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
"""Hook for subclass initialization. Called for each request.
|
"""Hook for subclass initialization. Called for each request.
|
||||||
|
|
||||||
A dictionary passed as the third argument of a url spec will be
|
A dictionary passed as the third argument of a url spec will be
|
||||||
supplied as keyword arguments to initialize().
|
supplied as keyword arguments to initialize().
|
||||||
|
|
||||||
|
@ -313,13 +317,14 @@ class RequestHandler(object):
|
||||||
try:
|
try:
|
||||||
self._reason = httputil.responses[status_code]
|
self._reason = httputil.responses[status_code]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise ValueError("unknown status code %d", status_code)
|
raise ValueError("unknown status code %d" % status_code)
|
||||||
|
|
||||||
def get_status(self):
|
def get_status(self):
|
||||||
"""Returns the status code for our response."""
|
"""Returns the status code for our response."""
|
||||||
return self._status_code
|
return self._status_code
|
||||||
|
|
||||||
def set_header(self, name, value):
|
def set_header(self, name, value):
|
||||||
|
# type: (str, _HeaderTypes) -> None
|
||||||
"""Sets the given response header name and value.
|
"""Sets the given response header name and value.
|
||||||
|
|
||||||
If a datetime is given, we automatically format it according to the
|
If a datetime is given, we automatically format it according to the
|
||||||
|
@ -329,6 +334,7 @@ class RequestHandler(object):
|
||||||
self._headers[name] = self._convert_header_value(value)
|
self._headers[name] = self._convert_header_value(value)
|
||||||
|
|
||||||
def add_header(self, name, value):
|
def add_header(self, name, value):
|
||||||
|
# type: (str, _HeaderTypes) -> None
|
||||||
"""Adds the given response header and value.
|
"""Adds the given response header and value.
|
||||||
|
|
||||||
Unlike `set_header`, `add_header` may be called multiple times
|
Unlike `set_header`, `add_header` may be called multiple times
|
||||||
|
@ -345,13 +351,25 @@ class RequestHandler(object):
|
||||||
if name in self._headers:
|
if name in self._headers:
|
||||||
del self._headers[name]
|
del self._headers[name]
|
||||||
|
|
||||||
_INVALID_HEADER_CHAR_RE = re.compile(br"[\x00-\x1f]")
|
_INVALID_HEADER_CHAR_RE = re.compile(r"[\x00-\x1f]")
|
||||||
|
|
||||||
def _convert_header_value(self, value):
|
def _convert_header_value(self, value):
|
||||||
if isinstance(value, bytes):
|
# type: (_HeaderTypes) -> str
|
||||||
pass
|
|
||||||
elif isinstance(value, unicode_type):
|
# Convert the input value to a str. This type check is a bit
|
||||||
value = value.encode('utf-8')
|
# subtle: The bytes case only executes on python 3, and the
|
||||||
|
# unicode case only executes on python 2, because the other
|
||||||
|
# cases are covered by the first match for str.
|
||||||
|
if isinstance(value, str):
|
||||||
|
retval = value
|
||||||
|
elif isinstance(value, bytes): # py3
|
||||||
|
# Non-ascii characters in headers are not well supported,
|
||||||
|
# but if you pass bytes, use latin1 so they pass through as-is.
|
||||||
|
retval = value.decode('latin1')
|
||||||
|
elif isinstance(value, unicode_type): # py2
|
||||||
|
# TODO: This is inconsistent with the use of latin1 above,
|
||||||
|
# but it's been that way for a long time. Should it change?
|
||||||
|
retval = escape.utf8(value)
|
||||||
elif isinstance(value, numbers.Integral):
|
elif isinstance(value, numbers.Integral):
|
||||||
# return immediately since we know the converted value will be safe
|
# return immediately since we know the converted value will be safe
|
||||||
return str(value)
|
return str(value)
|
||||||
|
@ -361,11 +379,11 @@ class RequestHandler(object):
|
||||||
raise TypeError("Unsupported header value %r" % value)
|
raise TypeError("Unsupported header value %r" % value)
|
||||||
# If \n is allowed into the header, it is possible to inject
|
# If \n is allowed into the header, it is possible to inject
|
||||||
# additional headers or split the request.
|
# additional headers or split the request.
|
||||||
if RequestHandler._INVALID_HEADER_CHAR_RE.search(value):
|
if RequestHandler._INVALID_HEADER_CHAR_RE.search(retval):
|
||||||
raise ValueError("Unsafe header value %r", value)
|
raise ValueError("Unsafe header value %r", retval)
|
||||||
return value
|
return retval
|
||||||
|
|
||||||
_ARG_DEFAULT = []
|
_ARG_DEFAULT = object()
|
||||||
|
|
||||||
def get_argument(self, name, default=_ARG_DEFAULT, strip=True):
|
def get_argument(self, name, default=_ARG_DEFAULT, strip=True):
|
||||||
"""Returns the value of the argument with the given name.
|
"""Returns the value of the argument with the given name.
|
||||||
|
@ -509,7 +527,7 @@ class RequestHandler(object):
|
||||||
|
|
||||||
Additional keyword arguments are set on the Cookie.Morsel
|
Additional keyword arguments are set on the Cookie.Morsel
|
||||||
directly.
|
directly.
|
||||||
See http://docs.python.org/library/cookie.html#morsel-objects
|
See https://docs.python.org/2/library/cookie.html#Cookie.Morsel
|
||||||
for available attributes.
|
for available attributes.
|
||||||
"""
|
"""
|
||||||
# The cookie library only accepts type str, in both python 2 and 3
|
# The cookie library only accepts type str, in both python 2 and 3
|
||||||
|
@ -696,6 +714,8 @@ class RequestHandler(object):
|
||||||
|
|
||||||
def render(self, template_name, **kwargs):
|
def render(self, template_name, **kwargs):
|
||||||
"""Renders the template with the given arguments as the response."""
|
"""Renders the template with the given arguments as the response."""
|
||||||
|
if self._finished:
|
||||||
|
raise RuntimeError("Cannot render() after finish()")
|
||||||
html = self.render_string(template_name, **kwargs)
|
html = self.render_string(template_name, **kwargs)
|
||||||
|
|
||||||
# Insert the additional JS and CSS added by the modules on the page
|
# Insert the additional JS and CSS added by the modules on the page
|
||||||
|
@ -915,8 +935,8 @@ class RequestHandler(object):
|
||||||
if self.check_etag_header():
|
if self.check_etag_header():
|
||||||
self._write_buffer = []
|
self._write_buffer = []
|
||||||
self.set_status(304)
|
self.set_status(304)
|
||||||
if self._status_code == 304:
|
if self._status_code in (204, 304):
|
||||||
assert not self._write_buffer, "Cannot send body with 304"
|
assert not self._write_buffer, "Cannot send body with %s" % self._status_code
|
||||||
self._clear_headers_for_304()
|
self._clear_headers_for_304()
|
||||||
elif "Content-Length" not in self._headers:
|
elif "Content-Length" not in self._headers:
|
||||||
content_length = sum(len(part) for part in self._write_buffer)
|
content_length = sum(len(part) for part in self._write_buffer)
|
||||||
|
@ -1072,8 +1092,8 @@ class RequestHandler(object):
|
||||||
|
|
||||||
def get_current_user(self):
|
def get_current_user(self):
|
||||||
user_cookie = self.get_secure_cookie("user")
|
user_cookie = self.get_secure_cookie("user")
|
||||||
if user_cookie:
|
if user_cookie:
|
||||||
return json.loads(user_cookie)
|
return json.loads(user_cookie)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
* It may be set as a normal variable, typically from an overridden
|
* It may be set as a normal variable, typically from an overridden
|
||||||
|
@ -1089,7 +1109,7 @@ class RequestHandler(object):
|
||||||
may not, so the latter form is necessary if loading the user requires
|
may not, so the latter form is necessary if loading the user requires
|
||||||
asynchronous operations.
|
asynchronous operations.
|
||||||
|
|
||||||
The user object may any type of the application's choosing.
|
The user object may be any type of the application's choosing.
|
||||||
"""
|
"""
|
||||||
if not hasattr(self, "_current_user"):
|
if not hasattr(self, "_current_user"):
|
||||||
self._current_user = self.get_current_user()
|
self._current_user = self.get_current_user()
|
||||||
|
@ -1265,6 +1285,8 @@ class RequestHandler(object):
|
||||||
raise HTTPError(403, "'_xsrf' argument missing from POST")
|
raise HTTPError(403, "'_xsrf' argument missing from POST")
|
||||||
_, token, _ = self._decode_xsrf_token(token)
|
_, token, _ = self._decode_xsrf_token(token)
|
||||||
_, expected_token, _ = self._get_raw_xsrf_token()
|
_, expected_token, _ = self._get_raw_xsrf_token()
|
||||||
|
if not token:
|
||||||
|
raise HTTPError(403, "'_xsrf' argument has invalid format")
|
||||||
if not _time_independent_equals(utf8(token), utf8(expected_token)):
|
if not _time_independent_equals(utf8(token), utf8(expected_token)):
|
||||||
raise HTTPError(403, "XSRF cookie does not match POST argument")
|
raise HTTPError(403, "XSRF cookie does not match POST argument")
|
||||||
|
|
||||||
|
@ -1385,7 +1407,9 @@ class RequestHandler(object):
|
||||||
match = True
|
match = True
|
||||||
else:
|
else:
|
||||||
# Use a weak comparison when comparing entity-tags.
|
# Use a weak comparison when comparing entity-tags.
|
||||||
val = lambda x: x[2:] if x.startswith(b'W/') else x
|
def val(x):
|
||||||
|
return x[2:] if x.startswith(b'W/') else x
|
||||||
|
|
||||||
for etag in etags:
|
for etag in etags:
|
||||||
if val(etag) == val(computed_etag):
|
if val(etag) == val(computed_etag):
|
||||||
match = True
|
match = True
|
||||||
|
@ -1603,6 +1627,7 @@ def asynchronous(method):
|
||||||
result = method(self, *args, **kwargs)
|
result = method(self, *args, **kwargs)
|
||||||
if result is not None:
|
if result is not None:
|
||||||
result = gen.convert_yielded(result)
|
result = gen.convert_yielded(result)
|
||||||
|
|
||||||
# If @asynchronous is used with @gen.coroutine, (but
|
# If @asynchronous is used with @gen.coroutine, (but
|
||||||
# not @gen.engine), we can automatically finish the
|
# not @gen.engine), we can automatically finish the
|
||||||
# request when the future resolves. Additionally,
|
# request when the future resolves. Additionally,
|
||||||
|
@ -2240,7 +2265,7 @@ class StaticFileHandler(RequestHandler):
|
||||||
"""
|
"""
|
||||||
CACHE_MAX_AGE = 86400 * 365 * 10 # 10 years
|
CACHE_MAX_AGE = 86400 * 365 * 10 # 10 years
|
||||||
|
|
||||||
_static_hashes = {}
|
_static_hashes = {} # type: typing.Dict
|
||||||
_lock = threading.Lock() # protects _static_hashes
|
_lock = threading.Lock() # protects _static_hashes
|
||||||
|
|
||||||
def initialize(self, path, default_filename=None):
|
def initialize(self, path, default_filename=None):
|
||||||
|
@ -2693,6 +2718,7 @@ class OutputTransform(object):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def transform_first_chunk(self, status_code, headers, chunk, finishing):
|
def transform_first_chunk(self, status_code, headers, chunk, finishing):
|
||||||
|
# type: (int, httputil.HTTPHeaders, bytes, bool) -> typing.Tuple[int, httputil.HTTPHeaders, bytes]
|
||||||
return status_code, headers, chunk
|
return status_code, headers, chunk
|
||||||
|
|
||||||
def transform_chunk(self, chunk, finishing):
|
def transform_chunk(self, chunk, finishing):
|
||||||
|
@ -2713,7 +2739,8 @@ class GZipContentEncoding(OutputTransform):
|
||||||
# beginning with "text/").
|
# beginning with "text/").
|
||||||
CONTENT_TYPES = set(["application/javascript", "application/x-javascript",
|
CONTENT_TYPES = set(["application/javascript", "application/x-javascript",
|
||||||
"application/xml", "application/atom+xml",
|
"application/xml", "application/atom+xml",
|
||||||
"application/json", "application/xhtml+xml"])
|
"application/json", "application/xhtml+xml",
|
||||||
|
"image/svg+xml"])
|
||||||
# Python's GzipFile defaults to level 9, while most other gzip
|
# Python's GzipFile defaults to level 9, while most other gzip
|
||||||
# tools (including gzip itself) default to 6, which is probably a
|
# tools (including gzip itself) default to 6, which is probably a
|
||||||
# better CPU/size tradeoff.
|
# better CPU/size tradeoff.
|
||||||
|
@ -2732,10 +2759,12 @@ class GZipContentEncoding(OutputTransform):
|
||||||
return ctype.startswith('text/') or ctype in self.CONTENT_TYPES
|
return ctype.startswith('text/') or ctype in self.CONTENT_TYPES
|
||||||
|
|
||||||
def transform_first_chunk(self, status_code, headers, chunk, finishing):
|
def transform_first_chunk(self, status_code, headers, chunk, finishing):
|
||||||
|
# type: (int, httputil.HTTPHeaders, bytes, bool) -> typing.Tuple[int, httputil.HTTPHeaders, bytes]
|
||||||
|
# TODO: can/should this type be inherited from the superclass?
|
||||||
if 'Vary' in headers:
|
if 'Vary' in headers:
|
||||||
headers['Vary'] += b', Accept-Encoding'
|
headers['Vary'] += ', Accept-Encoding'
|
||||||
else:
|
else:
|
||||||
headers['Vary'] = b'Accept-Encoding'
|
headers['Vary'] = 'Accept-Encoding'
|
||||||
if self._gzipping:
|
if self._gzipping:
|
||||||
ctype = _unicode(headers.get("Content-Type", "")).split(";")[0]
|
ctype = _unicode(headers.get("Content-Type", "")).split(";")[0]
|
||||||
self._gzipping = self._compressible_type(ctype) and \
|
self._gzipping = self._compressible_type(ctype) and \
|
||||||
|
@ -2966,9 +2995,11 @@ class URLSpec(object):
|
||||||
def __init__(self, pattern, handler, kwargs=None, name=None):
|
def __init__(self, pattern, handler, kwargs=None, name=None):
|
||||||
"""Parameters:
|
"""Parameters:
|
||||||
|
|
||||||
* ``pattern``: Regular expression to be matched. Any groups
|
* ``pattern``: Regular expression to be matched. Any capturing
|
||||||
in the regex will be passed in to the handler's get/post/etc
|
groups in the regex will be passed in to the handler's
|
||||||
methods as arguments.
|
get/post/etc methods as arguments (by keyword if named, by
|
||||||
|
position if unnamed. Named and unnamed capturing groups may
|
||||||
|
may not be mixed in the same rule).
|
||||||
|
|
||||||
* ``handler``: `RequestHandler` subclass to be invoked.
|
* ``handler``: `RequestHandler` subclass to be invoked.
|
||||||
|
|
||||||
|
@ -2977,6 +3008,7 @@ class URLSpec(object):
|
||||||
|
|
||||||
* ``name`` (optional): A name for this handler. Used by
|
* ``name`` (optional): A name for this handler. Used by
|
||||||
`Application.reverse_url`.
|
`Application.reverse_url`.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if not pattern.endswith('$'):
|
if not pattern.endswith('$'):
|
||||||
pattern += '$'
|
pattern += '$'
|
||||||
|
@ -3024,13 +3056,19 @@ class URLSpec(object):
|
||||||
if paren_loc >= 0:
|
if paren_loc >= 0:
|
||||||
pieces.append('%s' + fragment[paren_loc + 1:])
|
pieces.append('%s' + fragment[paren_loc + 1:])
|
||||||
else:
|
else:
|
||||||
pieces.append(fragment)
|
try:
|
||||||
|
unescaped_fragment = re_unescape(fragment)
|
||||||
|
except ValueError as exc:
|
||||||
|
# If we can't unescape part of it, we can't
|
||||||
|
# reverse this url.
|
||||||
|
return (None, None)
|
||||||
|
pieces.append(unescaped_fragment)
|
||||||
|
|
||||||
return (''.join(pieces), self.regex.groups)
|
return (''.join(pieces), self.regex.groups)
|
||||||
|
|
||||||
def reverse(self, *args):
|
def reverse(self, *args):
|
||||||
assert self._path is not None, \
|
if self._path is None:
|
||||||
"Cannot reverse url regex " + self.regex.pattern
|
raise ValueError("Cannot reverse url regex " + self.regex.pattern)
|
||||||
assert len(args) == self._group_count, "required number of arguments "\
|
assert len(args) == self._group_count, "required number of arguments "\
|
||||||
"not found"
|
"not found"
|
||||||
if not len(args):
|
if not len(args):
|
||||||
|
@ -3268,7 +3306,7 @@ def _create_signature_v2(secret, s):
|
||||||
|
|
||||||
|
|
||||||
def _unquote_or_none(s):
|
def _unquote_or_none(s):
|
||||||
"""None-safe wrapper around url_unescape to handle unamteched optional
|
"""None-safe wrapper around url_unescape to handle unmatched optional
|
||||||
groups correctly.
|
groups correctly.
|
||||||
|
|
||||||
Note that args are passed as bytes so the handler can decide what
|
Note that args are passed as bytes so the handler can decide what
|
||||||
|
|
|
@ -36,18 +36,14 @@ from tornado.iostream import StreamClosedError
|
||||||
from tornado.log import gen_log, app_log
|
from tornado.log import gen_log, app_log
|
||||||
from tornado import simple_httpclient
|
from tornado import simple_httpclient
|
||||||
from tornado.tcpclient import TCPClient
|
from tornado.tcpclient import TCPClient
|
||||||
from tornado.util import _websocket_mask
|
from tornado.util import _websocket_mask, PY3
|
||||||
|
|
||||||
try:
|
if PY3:
|
||||||
from urllib.parse import urlparse # py2
|
from urllib.parse import urlparse # py2
|
||||||
except ImportError:
|
xrange = range
|
||||||
|
else:
|
||||||
from urlparse import urlparse # py3
|
from urlparse import urlparse # py3
|
||||||
|
|
||||||
try:
|
|
||||||
xrange # py2
|
|
||||||
except NameError:
|
|
||||||
xrange = range # py3
|
|
||||||
|
|
||||||
|
|
||||||
class WebSocketError(Exception):
|
class WebSocketError(Exception):
|
||||||
pass
|
pass
|
||||||
|
@ -319,6 +315,19 @@ class WebSocketHandler(tornado.web.RequestHandler):
|
||||||
browsers, since WebSockets are allowed to bypass the usual same-origin
|
browsers, since WebSockets are allowed to bypass the usual same-origin
|
||||||
policies and don't use CORS headers.
|
policies and don't use CORS headers.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
This is an important security measure; don't disable it
|
||||||
|
without understanding the security implications. In
|
||||||
|
particular, if your authenticatino is cookie-based, you
|
||||||
|
must either restrict the origins allowed by
|
||||||
|
``check_origin()`` or implement your own XSRF-like
|
||||||
|
protection for websocket connections. See `these
|
||||||
|
<https://www.christian-schneider.net/CrossSiteWebSocketHijacking.html>`_
|
||||||
|
`articles
|
||||||
|
<https://devcenter.heroku.com/articles/websocket-security>`_
|
||||||
|
for more.
|
||||||
|
|
||||||
To accept all cross-origin traffic (which was the default prior to
|
To accept all cross-origin traffic (which was the default prior to
|
||||||
Tornado 4.0), simply override this method to always return true::
|
Tornado 4.0), simply override this method to always return true::
|
||||||
|
|
||||||
|
@ -333,6 +342,7 @@ class WebSocketHandler(tornado.web.RequestHandler):
|
||||||
return parsed_origin.netloc.endswith(".mydomain.com")
|
return parsed_origin.netloc.endswith(".mydomain.com")
|
||||||
|
|
||||||
.. versionadded:: 4.0
|
.. versionadded:: 4.0
|
||||||
|
|
||||||
"""
|
"""
|
||||||
parsed_origin = urlparse(origin)
|
parsed_origin = urlparse(origin)
|
||||||
origin = parsed_origin.netloc
|
origin = parsed_origin.netloc
|
||||||
|
|
|
@ -41,12 +41,12 @@ from tornado import httputil
|
||||||
from tornado.log import access_log
|
from tornado.log import access_log
|
||||||
from tornado import web
|
from tornado import web
|
||||||
from tornado.escape import native_str
|
from tornado.escape import native_str
|
||||||
from tornado.util import unicode_type
|
from tornado.util import unicode_type, PY3
|
||||||
|
|
||||||
|
|
||||||
try:
|
if PY3:
|
||||||
import urllib.parse as urllib_parse # py3
|
import urllib.parse as urllib_parse # py3
|
||||||
except ImportError:
|
else:
|
||||||
import urllib as urllib_parse
|
import urllib as urllib_parse
|
||||||
|
|
||||||
# PEP 3333 specifies that WSGI on python 3 generally deals with byte strings
|
# PEP 3333 specifies that WSGI on python 3 generally deals with byte strings
|
||||||
|
|
Loading…
Reference in a new issue