Update tornado
This commit is contained in:
Regular → Executable
+147
-72
@@ -49,7 +49,7 @@ threads it is important to use IOLoop.add_callback to transfer control
|
||||
back to the main thread before finishing the request.
|
||||
"""
|
||||
|
||||
from __future__ import with_statement
|
||||
from __future__ import absolute_import, division, with_statement
|
||||
|
||||
import Cookie
|
||||
import base64
|
||||
@@ -83,13 +83,14 @@ from tornado import locale
|
||||
from tornado import stack_context
|
||||
from tornado import template
|
||||
from tornado.escape import utf8, _unicode
|
||||
from tornado.util import b, bytes_type, import_object, ObjectDict
|
||||
from tornado.util import b, bytes_type, import_object, ObjectDict, raise_exc_info
|
||||
|
||||
try:
|
||||
from io import BytesIO # python 3
|
||||
except ImportError:
|
||||
from cStringIO import StringIO as BytesIO # python 2
|
||||
|
||||
|
||||
class RequestHandler(object):
|
||||
"""Subclass this class and define get() or post() to make a handler.
|
||||
|
||||
@@ -97,7 +98,8 @@ class RequestHandler(object):
|
||||
should override the class variable SUPPORTED_METHODS in your
|
||||
RequestHandler class.
|
||||
"""
|
||||
SUPPORTED_METHODS = ("GET", "HEAD", "POST", "DELETE", "PUT", "OPTIONS")
|
||||
SUPPORTED_METHODS = ("GET", "HEAD", "POST", "DELETE", "PATCH", "PUT",
|
||||
"OPTIONS")
|
||||
|
||||
_template_loaders = {} # {path: template.BaseLoader}
|
||||
_template_loader_lock = threading.Lock()
|
||||
@@ -121,7 +123,7 @@ class RequestHandler(object):
|
||||
self.ui["modules"] = self.ui["_modules"]
|
||||
self.clear()
|
||||
# Check since connection is not available in WSGI
|
||||
if hasattr(self.request, "connection"):
|
||||
if getattr(self.request, "connection", None):
|
||||
self.request.connection.stream.set_close_callback(
|
||||
self.on_connection_close)
|
||||
self.initialize(**kwargs)
|
||||
@@ -164,6 +166,9 @@ class RequestHandler(object):
|
||||
def delete(self, *args, **kwargs):
|
||||
raise HTTPError(405)
|
||||
|
||||
def patch(self, *args, **kwargs):
|
||||
raise HTTPError(405)
|
||||
|
||||
def put(self, *args, **kwargs):
|
||||
raise HTTPError(405)
|
||||
|
||||
@@ -208,7 +213,7 @@ class RequestHandler(object):
|
||||
"""Resets all headers and content for this response."""
|
||||
# The performance cost of tornado.httputil.HTTPHeaders is significant
|
||||
# (slowing down a benchmark with a trivial handler by more than 10%),
|
||||
# and its case-normalization is not generally necessary for
|
||||
# and its case-normalization is not generally necessary for
|
||||
# headers we generate on the server side, so use a plain dict
|
||||
# and list instead.
|
||||
self._headers = {
|
||||
@@ -259,6 +264,15 @@ class RequestHandler(object):
|
||||
"""
|
||||
self._list_headers.append((name, self._convert_header_value(value)))
|
||||
|
||||
def clear_header(self, name):
|
||||
"""Clears an outgoing header, undoing a previous `set_header` call.
|
||||
|
||||
Note that this method does not apply to multi-valued headers
|
||||
set by `add_header`.
|
||||
"""
|
||||
if name in self._headers:
|
||||
del self._headers[name]
|
||||
|
||||
def _convert_header_value(self, value):
|
||||
if isinstance(value, bytes_type):
|
||||
pass
|
||||
@@ -279,8 +293,8 @@ class RequestHandler(object):
|
||||
raise ValueError("Unsafe header value %r", value)
|
||||
return value
|
||||
|
||||
|
||||
_ARG_DEFAULT = []
|
||||
|
||||
def get_argument(self, name, default=_ARG_DEFAULT, strip=True):
|
||||
"""Returns the value of the argument with the given name.
|
||||
|
||||
@@ -358,25 +372,27 @@ class RequestHandler(object):
|
||||
if re.search(r"[\x00-\x20]", name + value):
|
||||
# Don't let us accidentally inject bad stuff
|
||||
raise ValueError("Invalid cookie %r: %r" % (name, value))
|
||||
if not hasattr(self, "_new_cookies"):
|
||||
self._new_cookies = []
|
||||
new_cookie = Cookie.SimpleCookie()
|
||||
self._new_cookies.append(new_cookie)
|
||||
new_cookie[name] = value
|
||||
if not hasattr(self, "_new_cookie"):
|
||||
self._new_cookie = Cookie.SimpleCookie()
|
||||
if name in self._new_cookie:
|
||||
del self._new_cookie[name]
|
||||
self._new_cookie[name] = value
|
||||
morsel = self._new_cookie[name]
|
||||
if domain:
|
||||
new_cookie[name]["domain"] = domain
|
||||
morsel["domain"] = domain
|
||||
if expires_days is not None and not expires:
|
||||
expires = datetime.datetime.utcnow() + datetime.timedelta(
|
||||
days=expires_days)
|
||||
if expires:
|
||||
timestamp = calendar.timegm(expires.utctimetuple())
|
||||
new_cookie[name]["expires"] = email.utils.formatdate(
|
||||
morsel["expires"] = email.utils.formatdate(
|
||||
timestamp, localtime=False, usegmt=True)
|
||||
if path:
|
||||
new_cookie[name]["path"] = path
|
||||
morsel["path"] = path
|
||||
for k, v in kwargs.iteritems():
|
||||
if k == 'max_age': k = 'max-age'
|
||||
new_cookie[name][k] = v
|
||||
if k == 'max_age':
|
||||
k = 'max-age'
|
||||
morsel[k] = v
|
||||
|
||||
def clear_cookie(self, name, path="/", domain=None):
|
||||
"""Deletes the cookie with the given name."""
|
||||
@@ -401,6 +417,9 @@ class RequestHandler(object):
|
||||
Note that the ``expires_days`` parameter sets the lifetime of the
|
||||
cookie in the browser, but is independent of the ``max_age_days``
|
||||
parameter to `get_secure_cookie`.
|
||||
|
||||
Secure cookies may contain arbitrary byte values, not just unicode
|
||||
strings (unlike regular cookies)
|
||||
"""
|
||||
self.set_cookie(name, self.create_signed_value(name, value),
|
||||
expires_days=expires_days, **kwargs)
|
||||
@@ -417,9 +436,14 @@ class RequestHandler(object):
|
||||
name, value)
|
||||
|
||||
def get_secure_cookie(self, name, value=None, max_age_days=31):
|
||||
"""Returns the given signed cookie if it validates, or None."""
|
||||
"""Returns the given signed cookie if it validates, or None.
|
||||
|
||||
The decoded cookie value is returned as a byte string (unlike
|
||||
`get_cookie`).
|
||||
"""
|
||||
self.require_setting("cookie_secret", "secure cookies")
|
||||
if value is None: value = self.get_cookie(name)
|
||||
if value is None:
|
||||
value = self.get_cookie(name)
|
||||
return decode_signed_value(self.application.settings["cookie_secret"],
|
||||
name, value, max_age_days=max_age_days)
|
||||
|
||||
@@ -482,7 +506,8 @@ class RequestHandler(object):
|
||||
html_bodies = []
|
||||
for module in getattr(self, "_active_modules", {}).itervalues():
|
||||
embed_part = module.embedded_javascript()
|
||||
if embed_part: js_embed.append(utf8(embed_part))
|
||||
if embed_part:
|
||||
js_embed.append(utf8(embed_part))
|
||||
file_part = module.javascript_files()
|
||||
if file_part:
|
||||
if isinstance(file_part, (unicode, bytes_type)):
|
||||
@@ -490,7 +515,8 @@ class RequestHandler(object):
|
||||
else:
|
||||
js_files.extend(file_part)
|
||||
embed_part = module.embedded_css()
|
||||
if embed_part: css_embed.append(utf8(embed_part))
|
||||
if embed_part:
|
||||
css_embed.append(utf8(embed_part))
|
||||
file_part = module.css_files()
|
||||
if file_part:
|
||||
if isinstance(file_part, (unicode, bytes_type)):
|
||||
@@ -498,9 +524,12 @@ class RequestHandler(object):
|
||||
else:
|
||||
css_files.extend(file_part)
|
||||
head_part = module.html_head()
|
||||
if head_part: html_heads.append(utf8(head_part))
|
||||
if head_part:
|
||||
html_heads.append(utf8(head_part))
|
||||
body_part = module.html_body()
|
||||
if body_part: html_bodies.append(utf8(body_part))
|
||||
if body_part:
|
||||
html_bodies.append(utf8(body_part))
|
||||
|
||||
def is_absolute(path):
|
||||
return any(path.startswith(x) for x in ["/", "http:", "https:"])
|
||||
if js_files:
|
||||
@@ -579,7 +608,7 @@ class RequestHandler(object):
|
||||
_=self.locale.translate,
|
||||
static_url=self.static_url,
|
||||
xsrf_form_html=self.xsrf_form_html,
|
||||
reverse_url=self.application.reverse_url
|
||||
reverse_url=self.reverse_url
|
||||
)
|
||||
args.update(self.ui)
|
||||
args.update(kwargs)
|
||||
@@ -596,10 +625,9 @@ class RequestHandler(object):
|
||||
kwargs["autoescape"] = settings["autoescape"]
|
||||
return template.Loader(template_path, **kwargs)
|
||||
|
||||
|
||||
def flush(self, include_footers=False, callback=None):
|
||||
"""Flushes the current output buffer to the network.
|
||||
|
||||
|
||||
The ``callback`` argument, if given, can be used for flow control:
|
||||
it will be run when all flushed data has been written to the socket.
|
||||
Note that only one flush callback can be outstanding at a time;
|
||||
@@ -614,8 +642,9 @@ class RequestHandler(object):
|
||||
if not self._headers_written:
|
||||
self._headers_written = True
|
||||
for transform in self._transforms:
|
||||
self._headers, chunk = transform.transform_first_chunk(
|
||||
self._headers, chunk, include_footers)
|
||||
self._status_code, self._headers, chunk = \
|
||||
transform.transform_first_chunk(
|
||||
self._status_code, self._headers, chunk, include_footers)
|
||||
headers = self._generate_headers()
|
||||
else:
|
||||
for transform in self._transforms:
|
||||
@@ -624,11 +653,11 @@ class RequestHandler(object):
|
||||
|
||||
# Ignore the chunk and only write the headers for HEAD requests
|
||||
if self.request.method == "HEAD":
|
||||
if headers: self.request.write(headers, callback=callback)
|
||||
if headers:
|
||||
self.request.write(headers, callback=callback)
|
||||
return
|
||||
|
||||
if headers or chunk:
|
||||
self.request.write(headers + chunk, callback=callback)
|
||||
self.request.write(headers + chunk, callback=callback)
|
||||
|
||||
def finish(self, chunk=None):
|
||||
"""Finishes this response, ending the HTTP request."""
|
||||
@@ -637,7 +666,8 @@ class RequestHandler(object):
|
||||
"by using async operations without the "
|
||||
"@asynchronous decorator.")
|
||||
|
||||
if chunk is not None: self.write(chunk)
|
||||
if chunk is not None:
|
||||
self.write(chunk)
|
||||
|
||||
# Automatically support ETags and add the Content-Length header if
|
||||
# we have not flushed any content yet.
|
||||
@@ -647,13 +677,15 @@ class RequestHandler(object):
|
||||
"Etag" not in self._headers):
|
||||
etag = self.compute_etag()
|
||||
if etag is not None:
|
||||
self.set_header("Etag", etag)
|
||||
inm = self.request.headers.get("If-None-Match")
|
||||
if inm and inm.find(etag) != -1:
|
||||
self._write_buffer = []
|
||||
self.set_status(304)
|
||||
else:
|
||||
self.set_header("Etag", etag)
|
||||
if "Content-Length" not in self._headers:
|
||||
if self._status_code == 304:
|
||||
assert not self._write_buffer, "Cannot send body with 304"
|
||||
self._clear_headers_for_304()
|
||||
elif "Content-Length" not in self._headers:
|
||||
content_length = sum(len(part) for part in self._write_buffer)
|
||||
self.set_header("Content-Length", content_length)
|
||||
|
||||
@@ -720,7 +752,7 @@ class RequestHandler(object):
|
||||
kwargs['exception'] = exc_info[1]
|
||||
try:
|
||||
# Put the traceback into sys.exc_info()
|
||||
raise exc_info[0], exc_info[1], exc_info[2]
|
||||
raise_exc_info(exc_info)
|
||||
except Exception:
|
||||
self.finish(self.get_error_html(status_code, **kwargs))
|
||||
else:
|
||||
@@ -733,7 +765,7 @@ class RequestHandler(object):
|
||||
self.write(line)
|
||||
self.finish()
|
||||
else:
|
||||
self.finish("<html><title>%(code)d: %(message)s</title>"
|
||||
self.finish("<html><title>%(code)d: %(message)s</title>"
|
||||
"<body>%(code)d: %(message)s</body></html>" % {
|
||||
"code": status_code,
|
||||
"message": httplib.responses[status_code],
|
||||
@@ -926,6 +958,7 @@ class RequestHandler(object):
|
||||
return None
|
||||
if args or kwargs:
|
||||
callback = functools.partial(callback, *args, **kwargs)
|
||||
|
||||
def wrapper(*args, **kwargs):
|
||||
try:
|
||||
return callback(*args, **kwargs)
|
||||
@@ -964,7 +997,7 @@ class RequestHandler(object):
|
||||
# the exception value instead of the full triple,
|
||||
# so re-raise the exception to ensure that it's in
|
||||
# sys.exc_info()
|
||||
raise type, value, traceback
|
||||
raise_exc_info((type, value, traceback))
|
||||
except Exception:
|
||||
self._handle_request_exception(value)
|
||||
return True
|
||||
@@ -984,7 +1017,7 @@ class RequestHandler(object):
|
||||
if not self._finished:
|
||||
args = [self.decode_argument(arg) for arg in args]
|
||||
kwargs = dict((k, self.decode_argument(v, name=k))
|
||||
for (k,v) in kwargs.iteritems())
|
||||
for (k, v) in kwargs.iteritems())
|
||||
getattr(self, self.request.method.lower())(*args, **kwargs)
|
||||
if self._auto_finish and not self._finished:
|
||||
self.finish()
|
||||
@@ -995,10 +1028,10 @@ class RequestHandler(object):
|
||||
lines = [utf8(self.request.version + " " +
|
||||
str(self._status_code) +
|
||||
" " + httplib.responses[self._status_code])]
|
||||
lines.extend([(utf8(n) + b(": ") + utf8(v)) for n, v in
|
||||
lines.extend([(utf8(n) + b(": ") + utf8(v)) for n, v in
|
||||
itertools.chain(self._headers.iteritems(), self._list_headers)])
|
||||
for cookie_dict in getattr(self, "_new_cookies", []):
|
||||
for cookie in cookie_dict.values():
|
||||
if hasattr(self, "_new_cookie"):
|
||||
for cookie in self._new_cookie.values():
|
||||
lines.append(utf8("Set-Cookie: " + cookie.OutputString(None)))
|
||||
return b("\r\n").join(lines) + b("\r\n\r\n")
|
||||
|
||||
@@ -1044,6 +1077,17 @@ class RequestHandler(object):
|
||||
def _ui_method(self, method):
|
||||
return lambda *args, **kwargs: method(self, *args, **kwargs)
|
||||
|
||||
def _clear_headers_for_304(self):
|
||||
# 304 responses should not contain entity headers (defined in
|
||||
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.1)
|
||||
# not explicitly allowed by
|
||||
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
|
||||
headers = ["Allow", "Content-Encoding", "Content-Language",
|
||||
"Content-Length", "Content-MD5", "Content-Range",
|
||||
"Content-Type", "Last-Modified"]
|
||||
for h in headers:
|
||||
self.clear_header(h)
|
||||
|
||||
|
||||
def asynchronous(method):
|
||||
"""Wrap request handler methods with this if they are asynchronous.
|
||||
@@ -1088,8 +1132,9 @@ def removeslash(method):
|
||||
if self.request.method in ("GET", "HEAD"):
|
||||
uri = self.request.path.rstrip("/")
|
||||
if uri: # don't try to redirect '/' to ''
|
||||
if self.request.query: uri += "?" + self.request.query
|
||||
self.redirect(uri)
|
||||
if self.request.query:
|
||||
uri += "?" + self.request.query
|
||||
self.redirect(uri, permanent=True)
|
||||
return
|
||||
else:
|
||||
raise HTTPError(404)
|
||||
@@ -1109,8 +1154,9 @@ def addslash(method):
|
||||
if not self.request.path.endswith("/"):
|
||||
if self.request.method in ("GET", "HEAD"):
|
||||
uri = self.request.path + "/"
|
||||
if self.request.query: uri += "?" + self.request.query
|
||||
self.redirect(uri)
|
||||
if self.request.query:
|
||||
uri += "?" + self.request.query
|
||||
self.redirect(uri, permanent=True)
|
||||
return
|
||||
raise HTTPError(404)
|
||||
return method(self, *args, **kwargs)
|
||||
@@ -1162,9 +1208,15 @@ class Application(object):
|
||||
|
||||
.. attribute:: settings
|
||||
|
||||
Additonal keyword arguments passed to the constructor are saved in the
|
||||
Additional keyword arguments passed to the constructor are saved in the
|
||||
`settings` dictionary, and are often referred to in documentation as
|
||||
"application settings".
|
||||
|
||||
.. attribute:: debug
|
||||
|
||||
If `True` the application runs in debug mode, described in
|
||||
:ref:`debug-mode`. This is an application setting in the `settings`
|
||||
dictionary, so handlers can access it.
|
||||
"""
|
||||
def __init__(self, handlers=None, default_host="", transforms=None,
|
||||
wsgi=False, **settings):
|
||||
@@ -1200,7 +1252,8 @@ class Application(object):
|
||||
r"/(favicon\.ico)", r"/(robots\.txt)"]:
|
||||
handlers.insert(0, (pattern, static_handler_class,
|
||||
static_handler_args))
|
||||
if handlers: self.add_handlers(".*$", handlers)
|
||||
if handlers:
|
||||
self.add_handlers(".*$", handlers)
|
||||
|
||||
# Automatically reload modified modules
|
||||
if self.settings.get("debug") and not wsgi:
|
||||
@@ -1292,7 +1345,8 @@ class Application(object):
|
||||
self._load_ui_methods(dict((n, getattr(methods, n))
|
||||
for n in dir(methods)))
|
||||
elif isinstance(methods, list):
|
||||
for m in methods: self._load_ui_methods(m)
|
||||
for m in methods:
|
||||
self._load_ui_methods(m)
|
||||
else:
|
||||
for name, fn in methods.iteritems():
|
||||
if not name.startswith("_") and hasattr(fn, "__call__") \
|
||||
@@ -1304,7 +1358,8 @@ class Application(object):
|
||||
self._load_ui_modules(dict((n, getattr(modules, n))
|
||||
for n in dir(modules)))
|
||||
elif isinstance(modules, list):
|
||||
for m in modules: self._load_ui_modules(m)
|
||||
for m in modules:
|
||||
self._load_ui_modules(m)
|
||||
else:
|
||||
assert isinstance(modules, dict)
|
||||
for name, cls in modules.iteritems():
|
||||
@@ -1333,7 +1388,8 @@ class Application(object):
|
||||
# None-safe wrapper around url_unescape to handle
|
||||
# unmatched optional groups correctly
|
||||
def unquote(s):
|
||||
if s is None: return s
|
||||
if s is None:
|
||||
return s
|
||||
return escape.url_unescape(s, encoding=None)
|
||||
# Pass matched groups to the handler. Since
|
||||
# match.groups() includes both named and unnamed groups,
|
||||
@@ -1343,7 +1399,7 @@ class Application(object):
|
||||
|
||||
if spec.regex.groupindex:
|
||||
kwargs = dict(
|
||||
(k, unquote(v))
|
||||
(str(k), unquote(v))
|
||||
for (k, v) in match.groupdict().iteritems())
|
||||
else:
|
||||
args = [unquote(s) for s in match.groups()]
|
||||
@@ -1365,7 +1421,11 @@ class Application(object):
|
||||
def reverse_url(self, name, *args):
|
||||
"""Returns a URL path for handler named `name`
|
||||
|
||||
The handler must be added to the application as a named URLSpec
|
||||
The handler must be added to the application as a named URLSpec.
|
||||
|
||||
Args will be substituted for capturing groups in the URLSpec regex.
|
||||
They will be converted to strings if necessary, encoded as utf8,
|
||||
and url-escaped.
|
||||
"""
|
||||
if name in self.named_handlers:
|
||||
return self.named_handlers[name].reverse(*args)
|
||||
@@ -1393,7 +1453,6 @@ class Application(object):
|
||||
handler._request_summary(), request_time)
|
||||
|
||||
|
||||
|
||||
class HTTPError(Exception):
|
||||
"""An exception that will turn into an HTTP error response."""
|
||||
def __init__(self, status_code, log_message=None, *args):
|
||||
@@ -1455,7 +1514,7 @@ class StaticFileHandler(RequestHandler):
|
||||
/static/images/myimage.png?v=xxx. Override ``get_cache_time`` method for
|
||||
more fine-grained cache control.
|
||||
"""
|
||||
CACHE_MAX_AGE = 86400*365*10 #10 years
|
||||
CACHE_MAX_AGE = 86400 * 365 * 10 # 10 years
|
||||
|
||||
_static_hashes = {}
|
||||
_lock = threading.Lock() # protects _static_hashes
|
||||
@@ -1554,7 +1613,7 @@ class StaticFileHandler(RequestHandler):
|
||||
|
||||
This method may be overridden in subclasses (but note that it is
|
||||
a class method rather than an instance method).
|
||||
|
||||
|
||||
``settings`` is the `Application.settings` dictionary. ``path``
|
||||
is the static path being requested. The url returned should be
|
||||
relative to the current host.
|
||||
@@ -1639,8 +1698,8 @@ class OutputTransform(object):
|
||||
def __init__(self, request):
|
||||
pass
|
||||
|
||||
def transform_first_chunk(self, headers, chunk, finishing):
|
||||
return headers, chunk
|
||||
def transform_first_chunk(self, status_code, headers, chunk, finishing):
|
||||
return status_code, headers, chunk
|
||||
|
||||
def transform_chunk(self, chunk, finishing):
|
||||
return chunk
|
||||
@@ -1652,7 +1711,7 @@ class GZipContentEncoding(OutputTransform):
|
||||
See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
|
||||
"""
|
||||
CONTENT_TYPES = set([
|
||||
"text/plain", "text/html", "text/css", "text/xml", "application/javascript",
|
||||
"text/plain", "text/html", "text/css", "text/xml", "application/javascript",
|
||||
"application/x-javascript", "application/xml", "application/atom+xml",
|
||||
"text/javascript", "application/json", "application/xhtml+xml"])
|
||||
MIN_LENGTH = 5
|
||||
@@ -1661,7 +1720,7 @@ class GZipContentEncoding(OutputTransform):
|
||||
self._gzipping = request.supports_http_1_1() and \
|
||||
"gzip" in request.headers.get("Accept-Encoding", "")
|
||||
|
||||
def transform_first_chunk(self, headers, chunk, finishing):
|
||||
def transform_first_chunk(self, status_code, headers, chunk, finishing):
|
||||
if self._gzipping:
|
||||
ctype = _unicode(headers.get("Content-Type", "")).split(";")[0]
|
||||
self._gzipping = (ctype in self.CONTENT_TYPES) and \
|
||||
@@ -1675,7 +1734,7 @@ class GZipContentEncoding(OutputTransform):
|
||||
chunk = self.transform_chunk(chunk, finishing)
|
||||
if "Content-Length" in headers:
|
||||
headers["Content-Length"] = str(len(chunk))
|
||||
return headers, chunk
|
||||
return status_code, headers, chunk
|
||||
|
||||
def transform_chunk(self, chunk, finishing):
|
||||
if self._gzipping:
|
||||
@@ -1698,15 +1757,17 @@ class ChunkedTransferEncoding(OutputTransform):
|
||||
def __init__(self, request):
|
||||
self._chunking = request.supports_http_1_1()
|
||||
|
||||
def transform_first_chunk(self, headers, chunk, finishing):
|
||||
if self._chunking:
|
||||
def transform_first_chunk(self, status_code, headers, chunk, finishing):
|
||||
# 304 responses have no body (not even a zero-length body), and so
|
||||
# should not have either Content-Length or Transfer-Encoding headers.
|
||||
if self._chunking and status_code != 304:
|
||||
# No need to chunk the output if a Content-Length is specified
|
||||
if "Content-Length" in headers or "Transfer-Encoding" in headers:
|
||||
self._chunking = False
|
||||
else:
|
||||
headers["Transfer-Encoding"] = "chunked"
|
||||
chunk = self.transform_chunk(chunk, finishing)
|
||||
return headers, chunk
|
||||
return status_code, headers, chunk
|
||||
|
||||
def transform_chunk(self, block, finishing):
|
||||
if self._chunking:
|
||||
@@ -1786,14 +1847,17 @@ class UIModule(object):
|
||||
"""Renders a template and returns it as a string."""
|
||||
return self.handler.render_string(path, **kwargs)
|
||||
|
||||
|
||||
class _linkify(UIModule):
|
||||
def render(self, text, **kwargs):
|
||||
return escape.linkify(text, **kwargs)
|
||||
|
||||
|
||||
class _xsrf_form_html(UIModule):
|
||||
def render(self):
|
||||
return self.handler.xsrf_form_html()
|
||||
|
||||
|
||||
class TemplateModule(UIModule):
|
||||
"""UIModule that simply renders the given template.
|
||||
|
||||
@@ -1806,7 +1870,7 @@ class TemplateModule(UIModule):
|
||||
inside the template and give it keyword arguments corresponding to
|
||||
the methods on UIModule: {{ set_resources(js_files=static_url("my.js")) }}
|
||||
Note that these resources are output once per template file, not once
|
||||
per instantiation of the template, so they must not depend on
|
||||
per instantiation of the template, so they must not depend on
|
||||
any arguments to the template.
|
||||
"""
|
||||
def __init__(self, handler):
|
||||
@@ -1862,10 +1926,9 @@ class TemplateModule(UIModule):
|
||||
return "".join(self._get_resources("html_body"))
|
||||
|
||||
|
||||
|
||||
class URLSpec(object):
|
||||
"""Specifies mappings between URLs and handlers."""
|
||||
def __init__(self, pattern, handler_class, kwargs={}, name=None):
|
||||
def __init__(self, pattern, handler_class, kwargs=None, name=None):
|
||||
"""Creates a URLSpec.
|
||||
|
||||
Parameters:
|
||||
@@ -1889,7 +1952,7 @@ class URLSpec(object):
|
||||
("groups in url regexes must either be all named or all "
|
||||
"positional: %r" % self.regex.pattern)
|
||||
self.handler_class = handler_class
|
||||
self.kwargs = kwargs
|
||||
self.kwargs = kwargs or {}
|
||||
self.name = name
|
||||
self._path, self._group_count = self._find_groups()
|
||||
|
||||
@@ -1928,7 +1991,12 @@ class URLSpec(object):
|
||||
"not found"
|
||||
if not len(args):
|
||||
return self._path
|
||||
return self._path % tuple([str(a) for a in args])
|
||||
converted_args = []
|
||||
for a in args:
|
||||
if not isinstance(a, (unicode, bytes_type)):
|
||||
a = str(a)
|
||||
converted_args.append(escape.url_escape(utf8(a)))
|
||||
return self._path % tuple(converted_args)
|
||||
|
||||
url = URLSpec
|
||||
|
||||
@@ -1938,13 +2006,14 @@ def _time_independent_equals(a, b):
|
||||
return False
|
||||
result = 0
|
||||
if type(a[0]) is int: # python3 byte strings
|
||||
for x, y in zip(a,b):
|
||||
for x, y in zip(a, b):
|
||||
result |= x ^ y
|
||||
else: # python2
|
||||
for x, y in zip(a, b):
|
||||
result |= ord(x) ^ ord(y)
|
||||
return result == 0
|
||||
|
||||
|
||||
def create_signed_value(secret, name, value):
|
||||
timestamp = utf8(str(int(time.time())))
|
||||
value = base64.b64encode(utf8(value))
|
||||
@@ -1952,10 +2021,13 @@ def create_signed_value(secret, name, value):
|
||||
value = b("|").join([value, timestamp, signature])
|
||||
return value
|
||||
|
||||
|
||||
def decode_signed_value(secret, name, value, max_age_days=31):
|
||||
if not value: return None
|
||||
if not value:
|
||||
return None
|
||||
parts = utf8(value).split(b("|"))
|
||||
if len(parts) != 3: return None
|
||||
if len(parts) != 3:
|
||||
return None
|
||||
signature = _create_signature(secret, name, parts[0], parts[1])
|
||||
if not _time_independent_equals(parts[2], signature):
|
||||
logging.warning("Invalid cookie signature %r", value)
|
||||
@@ -1974,12 +2046,15 @@ def decode_signed_value(secret, name, value, max_age_days=31):
|
||||
return None
|
||||
if parts[1].startswith(b("0")):
|
||||
logging.warning("Tampered cookie %r", value)
|
||||
return None
|
||||
try:
|
||||
return base64.b64decode(parts[0])
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def _create_signature(secret, *parts):
|
||||
hash = hmac.new(utf8(secret), digestmod=hashlib.sha1)
|
||||
for part in parts: hash.update(utf8(part))
|
||||
for part in parts:
|
||||
hash.update(utf8(part))
|
||||
return utf8(hash.hexdigest())
|
||||
|
||||
Reference in New Issue
Block a user