-
-
diff --git a/demos/twitter/twitterdemo.py b/demos/twitter/twitterdemo.py
deleted file mode 100755
index 4bd3022531..0000000000
--- a/demos/twitter/twitterdemo.py
+++ /dev/null
@@ -1,105 +0,0 @@
-#!/usr/bin/env python
-"""A simplistic Twitter viewer to demonstrate the use of TwitterMixin.
-
-To run this app, you must first register an application with Twitter:
- 1) Go to https://dev.twitter.com/apps and create an application.
- Your application must have a callback URL registered with Twitter.
- It doesn't matter what it is, but it has to be there (Twitter won't
- let you use localhost in a registered callback URL, but that won't stop
- you from running this demo on localhost).
- 2) Create a file called "secrets.cfg" and put your consumer key and
- secret (which Twitter gives you when you register an app) in it:
- twitter_consumer_key = 'asdf1234'
- twitter_consumer_secret = 'qwer5678'
- (you could also generate a random value for "cookie_secret" and put it
- in the same file, although it's not necessary to run this demo)
- 3) Run this program and go to http://localhost:8888 (by default) in your
- browser.
-"""
-
-import logging
-
-from tornado.auth import TwitterMixin
-from tornado.escape import json_decode, json_encode
-from tornado.ioloop import IOLoop
-from tornado import gen
-from tornado.options import define, options, parse_command_line, parse_config_file
-from tornado.web import Application, RequestHandler, authenticated
-
-define("port", default=8888, help="port to listen on")
-define(
- "config_file", default="secrets.cfg", help="filename for additional configuration"
-)
-
-define(
- "debug",
- default=False,
- group="application",
- help="run in debug mode (with automatic reloading)",
-)
-# The following settings should probably be defined in secrets.cfg
-define("twitter_consumer_key", type=str, group="application")
-define("twitter_consumer_secret", type=str, group="application")
-define(
- "cookie_secret",
- type=str,
- group="application",
- default="__TODO:_GENERATE_YOUR_OWN_RANDOM_VALUE__",
- help="signing key for secure cookies",
-)
-
-
-class BaseHandler(RequestHandler):
- COOKIE_NAME = "twitterdemo_user"
-
- def get_current_user(self):
- user_json = self.get_secure_cookie(self.COOKIE_NAME)
- if not user_json:
- return None
- return json_decode(user_json)
-
-
-class MainHandler(BaseHandler, TwitterMixin):
- @authenticated
- @gen.coroutine
- def get(self):
- timeline = yield self.twitter_request(
- "/statuses/home_timeline", access_token=self.current_user["access_token"]
- )
- self.render("home.html", timeline=timeline)
-
-
-class LoginHandler(BaseHandler, TwitterMixin):
- @gen.coroutine
- def get(self):
- if self.get_argument("oauth_token", None):
- user = yield self.get_authenticated_user()
- del user["description"]
- self.set_secure_cookie(self.COOKIE_NAME, json_encode(user))
- self.redirect(self.get_argument("next", "/"))
- else:
- yield self.authorize_redirect(callback_uri=self.request.full_url())
-
-
-class LogoutHandler(BaseHandler):
- def get(self):
- self.clear_cookie(self.COOKIE_NAME)
-
-
-def main():
- parse_command_line(final=False)
- parse_config_file(options.config_file)
-
- app = Application(
- [("/", MainHandler), ("/login", LoginHandler), ("/logout", LogoutHandler)],
- login_url="/login",
- **options.group_dict("application")
- )
- app.listen(options.port)
-
- logging.info("Listening on http://localhost:%d" % options.port)
- IOLoop.current().start()
-
-
-if __name__ == "__main__":
- main()
diff --git a/demos/websocket/chatdemo.py b/demos/websocket/chatdemo.py
index 1a7a3042c3..05781c757e 100755
--- a/demos/websocket/chatdemo.py
+++ b/demos/websocket/chatdemo.py
@@ -18,12 +18,9 @@
Authentication, error handling, etc are left as an exercise for the reader :)
"""
+import asyncio
import logging
-import tornado.escape
-import tornado.ioloop
-import tornado.options
-import tornado.web
-import tornado.websocket
+import tornado
import os.path
import uuid
@@ -91,12 +88,12 @@ def on_message(self, message):
ChatSocketHandler.send_updates(chat)
-def main():
+async def main():
tornado.options.parse_command_line()
app = Application()
app.listen(options.port)
- tornado.ioloop.IOLoop.current().start()
+ await asyncio.Event().wait()
if __name__ == "__main__":
- main()
+ asyncio.run(main())
diff --git a/demos/webspider/webspider.py b/demos/webspider/webspider.py
index 16c3840fa7..d7757917f4 100755
--- a/demos/webspider/webspider.py
+++ b/demos/webspider/webspider.py
@@ -1,12 +1,13 @@
#!/usr/bin/env python3
+import asyncio
import time
from datetime import timedelta
from html.parser import HTMLParser
from urllib.parse import urljoin, urldefrag
-from tornado import gen, httpclient, ioloop, queues
+from tornado import gen, httpclient, queues
base_url = "http://www.tornadoweb.org/en/stable/"
concurrency = 10
@@ -85,7 +86,7 @@ async def worker():
await q.join(timeout=timedelta(seconds=300))
assert fetching == (fetched | dead)
print("Done in %d seconds, fetched %s URLs." % (time.time() - start, len(fetched)))
- print("Unable to fetch %s URLS." % len(dead))
+ print("Unable to fetch %s URLs." % len(dead))
# Signal all the workers to exit.
for _ in range(concurrency):
@@ -94,5 +95,4 @@ async def worker():
if __name__ == "__main__":
- io_loop = ioloop.IOLoop.current()
- io_loop.run_sync(main)
+ asyncio.run(main())
diff --git a/docs/auth.rst b/docs/auth.rst
index dfbe3ed984..5033948155 100644
--- a/docs/auth.rst
+++ b/docs/auth.rst
@@ -3,7 +3,7 @@
.. testsetup::
- import tornado.auth, tornado.gen, tornado.web
+ import tornado
.. automodule:: tornado.auth
diff --git a/docs/caresresolver.rst b/docs/caresresolver.rst
index b5d6ddd101..4e0058eac0 100644
--- a/docs/caresresolver.rst
+++ b/docs/caresresolver.rst
@@ -18,3 +18,7 @@ wrapper ``pycares``).
so it is only recommended for use in ``AF_INET`` (i.e. IPv4). This is
the default for ``tornado.simple_httpclient``, but other libraries
may default to ``AF_UNSPEC``.
+
+ .. deprecated:: 6.2
+ This class is deprecated and will be removed in Tornado 7.0. Use the default
+ thread-based resolver instead.
diff --git a/docs/conf.py b/docs/conf.py
index f0cfa9c292..424d844ee6 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -1,7 +1,10 @@
-# Ensure we get the local copy of tornado instead of what's on the standard path
import os
+import sphinx.errors
import sys
+import sphinx_rtd_theme
+
+# Ensure we get the local copy of tornado instead of what's on the standard path
sys.path.insert(0, os.path.abspath(".."))
import tornado
@@ -81,15 +84,63 @@
)
]
-intersphinx_mapping = {"python": ("https://docs.python.org/3.6/", None)}
-
-on_rtd = os.environ.get("READTHEDOCS", None) == "True"
-
-# On RTD we can't import sphinx_rtd_theme, but it will be applied by
-# default anyway. This block will use the same theme when building locally
-# as on RTD.
-if not on_rtd:
- import sphinx_rtd_theme
-
- html_theme = "sphinx_rtd_theme"
- html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
+intersphinx_mapping = {"python": ("https://docs.python.org/3/", None)}
+
+html_theme = "sphinx_rtd_theme"
+html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
+
+# Suppress warnings about "class reference target not found" for these types.
+# In most cases these types come from type annotations and are for mypy's use.
+missing_references = {
+ # Generic type variables; nothing to link to.
+ "_IOStreamType",
+ "_S",
+ "_T",
+ # Standard library types which are defined in one module and documented
+ # in another. We could probably remap them to their proper location if
+ # there's not an upstream fix in python and/or sphinx.
+ "_asyncio.Future",
+ "_io.BytesIO",
+ "asyncio.AbstractEventLoop.run_forever",
+ "asyncio.events.AbstractEventLoop",
+ "concurrent.futures._base.Executor",
+ "concurrent.futures._base.Future",
+ "futures.Future",
+ "socket.socket",
+ "TextIO",
+ # Other stuff. I'm not sure why some of these are showing up, but
+ # I'm just listing everything here to avoid blocking the upgrade of sphinx.
+ "Future",
+ "httputil.HTTPServerConnectionDelegate",
+ "httputil.HTTPServerRequest",
+ "OutputTransform",
+ "Pattern",
+ "RAISE",
+ "Rule",
+ "socket.AddressFamily",
+ "tornado.concurrent._T",
+ "tornado.gen._T",
+ "tornado.ioloop._S",
+ "tornado.ioloop._T",
+ "tornado.ioloop._Selectable",
+ "tornado.iostream._IOStreamType",
+ "tornado.locks._ReleasingContextManager",
+ "tornado.queues._T",
+ "tornado.options._Mockable",
+ "tornado.web._ArgDefaultMarker",
+ "tornado.web._HandlerDelegate",
+ "tornado.web._RequestHandlerType",
+ "_RequestHandlerType",
+ "traceback",
+ "WSGIAppType",
+ "Yieldable",
+}
+
+
+def missing_reference_handler(app, env, node, contnode):
+ if node["reftarget"] in missing_references:
+ raise sphinx.errors.NoUri
+
+
+def setup(app):
+ app.connect("missing-reference", missing_reference_handler)
diff --git a/docs/guide/coroutines.rst b/docs/guide/coroutines.rst
index aa8f62896d..811c084f1a 100644
--- a/docs/guide/coroutines.rst
+++ b/docs/guide/coroutines.rst
@@ -6,7 +6,7 @@ Coroutines
from tornado import gen
**Coroutines** are the recommended way to write asynchronous code in
-Tornado. Coroutines use the Python ``await`` or ``yield`` keyword to
+Tornado. Coroutines use the Python ``await`` keyword to
suspend and resume execution instead of a chain of callbacks
(cooperative lightweight threads as seen in frameworks like `gevent
`_ are sometimes called coroutines as well, but
@@ -232,11 +232,11 @@ immediately, so you can start another operation before waiting.
# This is equivalent to asyncio.ensure_future() (both work in Tornado).
fetch_future = convert_yielded(self.fetch_next_chunk())
while True:
- chunk = yield fetch_future
+ chunk = await fetch_future
if chunk is None: break
self.write(chunk)
fetch_future = convert_yielded(self.fetch_next_chunk())
- yield self.flush()
+ await self.flush()
.. testoutput::
:hide:
@@ -281,7 +281,7 @@ loop condition from accessing the results, as in this example from
Running in the background
^^^^^^^^^^^^^^^^^^^^^^^^^
-`.PeriodicCallback` is not normally used with coroutines. Instead, a
+As an alternative to `.PeriodicCallback`, a
coroutine can contain a ``while True:`` loop and use
`tornado.gen.sleep`::
diff --git a/docs/guide/intro.rst b/docs/guide/intro.rst
index 8d87ba62b2..2684c3890e 100644
--- a/docs/guide/intro.rst
+++ b/docs/guide/intro.rst
@@ -9,7 +9,7 @@ can scale to tens of thousands of open connections, making it ideal for
`WebSockets `_, and other
applications that require a long-lived connection to each user.
-Tornado can be roughly divided into four major components:
+Tornado can be roughly divided into three major components:
* A web framework (including `.RequestHandler` which is subclassed to
create web applications, and various supporting classes).
@@ -18,11 +18,6 @@ Tornado can be roughly divided into four major components:
* An asynchronous networking library including the classes `.IOLoop`
and `.IOStream`, which serve as the building blocks for the HTTP
components and can also be used to implement other protocols.
-* A coroutine library (`tornado.gen`) which allows asynchronous
- code to be written in a more straightforward way than chaining
- callbacks. This is similar to the native coroutine feature introduced
- in Python 3.5 (``async def``). Native coroutines are recommended
- in place of the `tornado.gen` module when available.
The Tornado web framework and HTTP server together offer a full-stack
alternative to `WSGI `_.
diff --git a/docs/guide/queues.rst b/docs/guide/queues.rst
index eb2f73f749..c8684e500a 100644
--- a/docs/guide/queues.rst
+++ b/docs/guide/queues.rst
@@ -3,9 +3,10 @@
.. currentmodule:: tornado.queues
-Tornado's `tornado.queues` module implements an asynchronous producer /
-consumer pattern for coroutines, analogous to the pattern implemented for
-threads by the Python standard library's `queue` module.
+Tornado's `tornado.queues` module (and the very similar ``Queue`` classes in
+`asyncio`) implements an asynchronous producer / consumer pattern for
+coroutines, analogous to the pattern implemented for threads by the Python
+standard library's `queue` module.
A coroutine that yields `Queue.get` pauses until there is an item in the queue.
If the queue has a maximum size set, a coroutine that yields `Queue.put` pauses
diff --git a/docs/guide/running.rst b/docs/guide/running.rst
index 8cf34f0502..99d18275a3 100644
--- a/docs/guide/running.rst
+++ b/docs/guide/running.rst
@@ -8,13 +8,15 @@ configuring a WSGI container to find your application, you write a
.. testcode::
- def main():
+ import asyncio
+
+ async def main():
app = make_app()
app.listen(8888)
- IOLoop.current().start()
+ await asyncio.Event().wait()
if __name__ == '__main__':
- main()
+ asyncio.run(main())
.. testoutput::
:hide:
@@ -33,24 +35,28 @@ Due to the Python GIL (Global Interpreter Lock), it is necessary to run
multiple Python processes to take full advantage of multi-CPU machines.
Typically it is best to run one process per CPU.
-Tornado includes a built-in multi-process mode to start several
-processes at once (note that multi-process mode does not work on
-Windows). This requires a slight alteration to the standard main
-function:
+The simplest way to do this is to add ``reuse_port=True`` to your ``listen()``
+calls and then simply run multiple copies of your application.
+
+Tornado also has the ability to start multiple processes from a single parent
+process (note that this does not work on Windows). This requires some
+alterations to application startup.
.. testcode::
def main():
- app = make_app()
- server = tornado.httpserver.HTTPServer(app)
- server.bind(8888)
- server.start(0) # forks one process per cpu
- IOLoop.current().start()
+ sockets = bind_sockets(8888)
+ tornado.process.fork_processes(0)
+ async def post_fork_main():
+ server = TCPServer()
+ server.add_sockets(sockets)
+ await asyncio.Event().wait()
+ asyncio.run(post_fork_main())
.. testoutput::
:hide:
-This is the easiest way to start multiple processes and have them all
+This is another way to start multiple processes and have them all
share the same port, although it has some limitations. First, each
child process will have its own ``IOLoop``, so it is important that
nothing touches the global ``IOLoop`` instance (even indirectly) before the
diff --git a/docs/guide/security.rst b/docs/guide/security.rst
index b65cd3f370..ee33141ee0 100644
--- a/docs/guide/security.rst
+++ b/docs/guide/security.rst
@@ -3,10 +3,9 @@ Authentication and security
.. testsetup::
- import tornado.auth
- import tornado.web
+ import tornado
-Cookies and secure cookies
+Cookies and signed cookies
~~~~~~~~~~~~~~~~~~~~~~~~~~
You can set cookies in the user's browser with the ``set_cookie``
@@ -28,8 +27,8 @@ method:
Cookies are not secure and can easily be modified by clients. If you
need to set cookies to, e.g., identify the currently logged in user,
you need to sign your cookies to prevent forgery. Tornado supports
-signed cookies with the `~.RequestHandler.set_secure_cookie` and
-`~.RequestHandler.get_secure_cookie` methods. To use these methods,
+signed cookies with the `~.RequestHandler.set_signed_cookie` and
+`~.RequestHandler.get_signed_cookie` methods. To use these methods,
you need to specify a secret key named ``cookie_secret`` when you
create your application. You can pass in application settings as
keyword arguments to your application:
@@ -46,15 +45,15 @@ keyword arguments to your application:
Signed cookies contain the encoded value of the cookie in addition to a
timestamp and an `HMAC `_ signature.
If the cookie is old or if the signature doesn't match,
-``get_secure_cookie`` will return ``None`` just as if the cookie isn't
+``get_signed_cookie`` will return ``None`` just as if the cookie isn't
set. The secure version of the example above:
.. testcode::
class MainHandler(tornado.web.RequestHandler):
def get(self):
- if not self.get_secure_cookie("mycookie"):
- self.set_secure_cookie("mycookie", "myvalue")
+ if not self.get_signed_cookie("mycookie"):
+ self.set_signed_cookie("mycookie", "myvalue")
self.write("Your cookie was not set yet!")
else:
self.write("Your cookie was set!")
@@ -62,15 +61,15 @@ set. The secure version of the example above:
.. testoutput::
:hide:
-Tornado's secure cookies guarantee integrity but not confidentiality.
+Tornado's signed cookies guarantee integrity but not confidentiality.
That is, the cookie cannot be modified but its contents can be seen by the
user. The ``cookie_secret`` is a symmetric key and must be kept secret --
anyone who obtains the value of this key could produce their own signed
cookies.
-By default, Tornado's secure cookies expire after 30 days. To change this,
-use the ``expires_days`` keyword argument to ``set_secure_cookie`` *and* the
-``max_age_days`` argument to ``get_secure_cookie``. These two values are
+By default, Tornado's signed cookies expire after 30 days. To change this,
+use the ``expires_days`` keyword argument to ``set_signed_cookie`` *and* the
+``max_age_days`` argument to ``get_signed_cookie``. These two values are
passed separately so that you may e.g. have a cookie that is valid for 30 days
for most purposes, but for certain sensitive actions (such as changing billing
information) you use a smaller ``max_age_days`` when reading the cookie.
@@ -82,7 +81,7 @@ signing key must then be set as ``key_version`` application setting
but all other keys in the dict are allowed for cookie signature validation,
if the correct key version is set in the cookie.
To implement cookie updates, the current signing key version can be
-queried via `~.RequestHandler.get_secure_cookie_key_version`.
+queried via `~.RequestHandler.get_signed_cookie_key_version`.
.. _user-authentication:
@@ -104,7 +103,7 @@ specifying a nickname, which is then saved in a cookie:
class BaseHandler(tornado.web.RequestHandler):
def get_current_user(self):
- return self.get_secure_cookie("user")
+ return self.get_signed_cookie("user")
class MainHandler(BaseHandler):
def get(self):
@@ -122,7 +121,7 @@ specifying a nickname, which is then saved in a cookie:
'