From aa653fc56bcad1fc5173451e334664f542e57ae4 Mon Sep 17 00:00:00 2001 From: Rob Browning Date: Thu, 26 Dec 2013 14:01:12 -0600 Subject: [PATCH] Remove ancient lib/tornado in favor of an external dependency. Our lib/tornado dates from over three years ago -- drop it in favor of an external dependency, and update the install instructions accordingly. Anyone trying to run "bup web" without tornado installed will see this: error: cannot find the python "tornado" module; please install it Signed-off-by: Rob Browning --- LICENSE | 3 - Makefile | 5 +- README.md | 21 +- lib/tornado/README | 27 - lib/tornado/__init__.py | 17 - lib/tornado/auth.py | 880 ------------------ lib/tornado/autoreload.py | 101 --- lib/tornado/database.py | 180 ---- lib/tornado/epoll.c | 112 --- lib/tornado/escape.py | 123 --- lib/tornado/httpclient.py | 750 ---------------- lib/tornado/httpserver.py | 439 --------- lib/tornado/httputil.py | 140 --- lib/tornado/ioloop.py | 523 ----------- lib/tornado/iostream.py | 242 ----- lib/tornado/locale.py | 453 ---------- lib/tornado/options.py | 386 -------- lib/tornado/s3server.py | 255 ------ lib/tornado/template.py | 574 ------------ lib/tornado/test/README | 4 - lib/tornado/test/test_ioloop.py | 38 - lib/tornado/web.py | 1488 ------------------------------- lib/tornado/websocket.py | 139 --- lib/tornado/win32_support.py | 123 --- lib/tornado/wsgi.py | 296 ------ 25 files changed, 15 insertions(+), 7304 deletions(-) delete mode 100644 lib/tornado/README delete mode 100644 lib/tornado/__init__.py delete mode 100644 lib/tornado/auth.py delete mode 100644 lib/tornado/autoreload.py delete mode 100644 lib/tornado/database.py delete mode 100644 lib/tornado/epoll.c delete mode 100644 lib/tornado/escape.py delete mode 100644 lib/tornado/httpclient.py delete mode 100644 lib/tornado/httpserver.py delete mode 100755 lib/tornado/httputil.py delete mode 100644 lib/tornado/ioloop.py delete mode 100644 lib/tornado/iostream.py delete mode 100644 lib/tornado/locale.py delete mode 100644 lib/tornado/options.py delete mode 100644 lib/tornado/s3server.py delete mode 100644 lib/tornado/template.py delete mode 100644 lib/tornado/test/README delete mode 100755 lib/tornado/test/test_ioloop.py delete mode 100644 lib/tornado/web.py delete mode 100644 lib/tornado/websocket.py delete mode 100644 lib/tornado/win32_support.py delete mode 100644 lib/tornado/wsgi.py diff --git a/LICENSE b/LICENSE index d13420c..f16ccb5 100644 --- a/LICENSE +++ b/LICENSE @@ -7,9 +7,6 @@ In addition, bupsplit.c, bupsplit.h, and options.py may be redistributed according to the separate (BSD-style) license written inside those files. -The files in lib/tornado are covered by the license described in -lib/tornado/README. - The definition of the relpath function was taken from CPython (tag v2.6, file Lib/posixpath.py, hg-commit 95fff5a6a276) and is covered under the terms of the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2. diff --git a/Makefile b/Makefile index 9c85fba..7f063b3 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ BINDIR=$(DESTDIR)$(PREFIX)/bin LIBDIR=$(DESTDIR)$(PREFIX)/lib/bup install: all $(INSTALL) -d $(MANDIR)/man1 $(DOCDIR) $(BINDIR) \ - $(LIBDIR)/bup $(LIBDIR)/cmd $(LIBDIR)/tornado \ + $(LIBDIR)/bup $(LIBDIR)/cmd \ $(LIBDIR)/web $(LIBDIR)/web/static [ ! -e Documentation/.docs-available ] || \ $(INSTALL) -m 0644 \ @@ -50,9 +50,6 @@ install: all $(INSTALL) -pm 0755 \ lib/bup/*$(SOEXT) \ $(LIBDIR)/bup - $(INSTALL) -pm 0644 \ - lib/tornado/*.py \ - $(LIBDIR)/tornado $(INSTALL) -pm 0644 \ lib/web/static/* \ $(LIBDIR)/web/static/ diff --git a/README.md b/README.md index 74d7324..871f779 100644 --- a/README.md +++ b/README.md @@ -92,19 +92,21 @@ From source git clone git://github.com/bup/bup - - Install the needed python libraries (including the development + - Install the required python libraries (including the development libraries). - On Debian/Ubuntu this is usually sufficient (run as root): + On very recent Debian/Ubuntu versions, this may be sufficient (run + as root): + + apt-get build-dep bup + + Otherwise try this (substitute python2.5-dev if you have an older + system): apt-get install python2.6-dev python-fuse apt-get install python-pyxattr python-pylibacl apt-get install linux-libc-dev - - Substitute python2.5-dev if you have an older system. Alternately, - on newer Debian/Ubuntu versions, you can try this: - - apt-get build-dep bup + apt-get install python-tornado # optional On CentOS (for CentOS 6, at least), this should be sufficient (run as root): @@ -119,6 +121,11 @@ From source On Cygwin, install python, make, rsync, and gcc4. + If you would like to use the optional bup web server on systems + without a tornado package, you may want to try this: + + pip install tornado + - Build the python module and symlinks: make diff --git a/lib/tornado/README b/lib/tornado/README deleted file mode 100644 index d504022..0000000 --- a/lib/tornado/README +++ /dev/null @@ -1,27 +0,0 @@ -Tornado -======= -Tornado is an open source version of the scalable, non-blocking web server -and and tools that power FriendFeed. Documentation and downloads are -available at http://www.tornadoweb.org/ - -Tornado is licensed under the Apache Licence, Version 2.0 -(http://www.apache.org/licenses/LICENSE-2.0.html). - -Installation -============ -To install: - - python setup.py build - sudo python setup.py install - -Tornado has been tested on Python 2.5 and 2.6. To use all of the features -of Tornado, you need to have PycURL and a JSON library like simplejson -installed. - -On Mac OS X, you can install the packages with: - - sudo easy_install setuptools pycurl==7.16.2.1 simplejson - -On Ubuntu Linux, you can install the packages with: - - sudo apt-get install python-pycurl python-simplejson diff --git a/lib/tornado/__init__.py b/lib/tornado/__init__.py deleted file mode 100644 index 8f73764..0000000 --- a/lib/tornado/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""The Tornado web server and tools.""" diff --git a/lib/tornado/auth.py b/lib/tornado/auth.py deleted file mode 100644 index 4575119..0000000 --- a/lib/tornado/auth.py +++ /dev/null @@ -1,880 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Implementations of various third-party authentication schemes. - -All the classes in this file are class Mixins designed to be used with -web.py RequestHandler classes. The primary methods for each service are -authenticate_redirect(), authorize_redirect(), and get_authenticated_user(). -The former should be called to redirect the user to, e.g., the OpenID -authentication page on the third party service, and the latter should -be called upon return to get the user data from the data returned by -the third party service. - -They all take slightly different arguments due to the fact all these -services implement authentication and authorization slightly differently. -See the individual service classes below for complete documentation. - -Example usage for Google OpenID: - -class GoogleHandler(tornado.web.RequestHandler, tornado.auth.GoogleMixin): - @tornado.web.asynchronous - def get(self): - if self.get_argument("openid.mode", None): - self.get_authenticated_user(self.async_callback(self._on_auth)) - return - self.authenticate_redirect() - - def _on_auth(self, user): - if not user: - raise tornado.web.HTTPError(500, "Google auth failed") - # Save the user with, e.g., set_secure_cookie() - -""" - -import binascii -import cgi -import hashlib -import hmac -import httpclient -import escape -import logging -import time -import urllib -import urlparse -import uuid - -class OpenIdMixin(object): - """Abstract implementation of OpenID and Attribute Exchange. - - See GoogleMixin below for example implementations. - """ - def authenticate_redirect(self, callback_uri=None, - ax_attrs=["name","email","language","username"]): - """Returns the authentication URL for this service. - - After authentication, the service will redirect back to the given - callback URI. - - We request the given attributes for the authenticated user by - default (name, email, language, and username). If you don't need - all those attributes for your app, you can request fewer with - the ax_attrs keyword argument. - """ - callback_uri = callback_uri or self.request.path - args = self._openid_args(callback_uri, ax_attrs=ax_attrs) - self.redirect(self._OPENID_ENDPOINT + "?" + urllib.urlencode(args)) - - def get_authenticated_user(self, callback): - """Fetches the authenticated user data upon redirect. - - This method should be called by the handler that receives the - redirect from the authenticate_redirect() or authorize_redirect() - methods. - """ - # Verify the OpenID response via direct request to the OP - args = dict((k, v[-1]) for k, v in self.request.arguments.iteritems()) - args["openid.mode"] = u"check_authentication" - url = self._OPENID_ENDPOINT + "?" + urllib.urlencode(args) - http = httpclient.AsyncHTTPClient() - http.fetch(url, self.async_callback( - self._on_authentication_verified, callback)) - - def _openid_args(self, callback_uri, ax_attrs=[], oauth_scope=None): - url = urlparse.urljoin(self.request.full_url(), callback_uri) - args = { - "openid.ns": "http://specs.openid.net/auth/2.0", - "openid.claimed_id": - "http://specs.openid.net/auth/2.0/identifier_select", - "openid.identity": - "http://specs.openid.net/auth/2.0/identifier_select", - "openid.return_to": url, - "openid.realm": self.request.protocol + "://" + self.request.host + "/", - "openid.mode": "checkid_setup", - } - if ax_attrs: - args.update({ - "openid.ns.ax": "http://openid.net/srv/ax/1.0", - "openid.ax.mode": "fetch_request", - }) - ax_attrs = set(ax_attrs) - required = [] - if "name" in ax_attrs: - ax_attrs -= set(["name", "firstname", "fullname", "lastname"]) - required += ["firstname", "fullname", "lastname"] - args.update({ - "openid.ax.type.firstname": - "http://axschema.org/namePerson/first", - "openid.ax.type.fullname": - "http://axschema.org/namePerson", - "openid.ax.type.lastname": - "http://axschema.org/namePerson/last", - }) - known_attrs = { - "email": "http://axschema.org/contact/email", - "language": "http://axschema.org/pref/language", - "username": "http://axschema.org/namePerson/friendly", - } - for name in ax_attrs: - args["openid.ax.type." + name] = known_attrs[name] - required.append(name) - args["openid.ax.required"] = ",".join(required) - if oauth_scope: - args.update({ - "openid.ns.oauth": - "http://specs.openid.net/extensions/oauth/1.0", - "openid.oauth.consumer": self.request.host.split(":")[0], - "openid.oauth.scope": oauth_scope, - }) - return args - - def _on_authentication_verified(self, callback, response): - if response.error or u"is_valid:true" not in response.body: - logging.warning("Invalid OpenID response: %s", response.error or - response.body) - callback(None) - return - - # Make sure we got back at least an email from attribute exchange - ax_ns = None - for name, values in self.request.arguments.iteritems(): - if name.startswith("openid.ns.") and \ - values[-1] == u"http://openid.net/srv/ax/1.0": - ax_ns = name[10:] - break - def get_ax_arg(uri): - if not ax_ns: return u"" - prefix = "openid." + ax_ns + ".type." - ax_name = None - for name, values in self.request.arguments.iteritems(): - if values[-1] == uri and name.startswith(prefix): - part = name[len(prefix):] - ax_name = "openid." + ax_ns + ".value." + part - break - if not ax_name: return u"" - return self.get_argument(ax_name, u"") - - email = get_ax_arg("http://axschema.org/contact/email") - name = get_ax_arg("http://axschema.org/namePerson") - first_name = get_ax_arg("http://axschema.org/namePerson/first") - last_name = get_ax_arg("http://axschema.org/namePerson/last") - username = get_ax_arg("http://axschema.org/namePerson/friendly") - locale = get_ax_arg("http://axschema.org/pref/language").lower() - user = dict() - name_parts = [] - if first_name: - user["first_name"] = first_name - name_parts.append(first_name) - if last_name: - user["last_name"] = last_name - name_parts.append(last_name) - if name: - user["name"] = name - elif name_parts: - user["name"] = u" ".join(name_parts) - elif email: - user["name"] = email.split("@")[0] - if email: user["email"] = email - if locale: user["locale"] = locale - if username: user["username"] = username - callback(user) - - -class OAuthMixin(object): - """Abstract implementation of OAuth. - - See TwitterMixin and FriendFeedMixin below for example implementations. - """ - def authorize_redirect(self, callback_uri=None): - """Redirects the user to obtain OAuth authorization for this service. - - Twitter and FriendFeed both require that you register a Callback - URL with your application. You should call this method to log the - user in, and then call get_authenticated_user() in the handler - you registered as your Callback URL to complete the authorization - process. - - This method sets a cookie called _oauth_request_token which is - subsequently used (and cleared) in get_authenticated_user for - security purposes. - """ - if callback_uri and getattr(self, "_OAUTH_NO_CALLBACKS", False): - raise Exception("This service does not support oauth_callback") - http = httpclient.AsyncHTTPClient() - http.fetch(self._oauth_request_token_url(), self.async_callback( - self._on_request_token, self._OAUTH_AUTHORIZE_URL, callback_uri)) - - def get_authenticated_user(self, callback): - """Gets the OAuth authorized user and access token on callback. - - This method should be called from the handler for your registered - OAuth Callback URL to complete the registration process. We call - callback with the authenticated user, which in addition to standard - attributes like 'name' includes the 'access_key' attribute, which - contains the OAuth access you can use to make authorized requests - to this service on behalf of the user. - """ - request_key = self.get_argument("oauth_token") - request_cookie = self.get_cookie("_oauth_request_token") - if not request_cookie: - logging.warning("Missing OAuth request token cookie") - callback(None) - return - cookie_key, cookie_secret = request_cookie.split("|") - if cookie_key != request_key: - logging.warning("Request token does not match cookie") - callback(None) - return - token = dict(key=cookie_key, secret=cookie_secret) - http = httpclient.AsyncHTTPClient() - http.fetch(self._oauth_access_token_url(token), self.async_callback( - self._on_access_token, callback)) - - def _oauth_request_token_url(self): - consumer_token = self._oauth_consumer_token() - url = self._OAUTH_REQUEST_TOKEN_URL - args = dict( - oauth_consumer_key=consumer_token["key"], - oauth_signature_method="HMAC-SHA1", - oauth_timestamp=str(int(time.time())), - oauth_nonce=binascii.b2a_hex(uuid.uuid4().bytes), - oauth_version="1.0", - ) - signature = _oauth_signature(consumer_token, "GET", url, args) - args["oauth_signature"] = signature - return url + "?" + urllib.urlencode(args) - - def _on_request_token(self, authorize_url, callback_uri, response): - if response.error: - raise Exception("Could not get request token") - request_token = _oauth_parse_response(response.body) - data = "|".join([request_token["key"], request_token["secret"]]) - self.set_cookie("_oauth_request_token", data) - args = dict(oauth_token=request_token["key"]) - if callback_uri: - args["oauth_callback"] = urlparse.urljoin( - self.request.full_url(), callback_uri) - self.redirect(authorize_url + "?" + urllib.urlencode(args)) - - def _oauth_access_token_url(self, request_token): - consumer_token = self._oauth_consumer_token() - url = self._OAUTH_ACCESS_TOKEN_URL - args = dict( - oauth_consumer_key=consumer_token["key"], - oauth_token=request_token["key"], - oauth_signature_method="HMAC-SHA1", - oauth_timestamp=str(int(time.time())), - oauth_nonce=binascii.b2a_hex(uuid.uuid4().bytes), - oauth_version="1.0", - ) - signature = _oauth_signature(consumer_token, "GET", url, args, - request_token) - args["oauth_signature"] = signature - return url + "?" + urllib.urlencode(args) - - def _on_access_token(self, callback, response): - if response.error: - logging.warning("Could not fetch access token") - callback(None) - return - access_token = _oauth_parse_response(response.body) - user = self._oauth_get_user(access_token, self.async_callback( - self._on_oauth_get_user, access_token, callback)) - - def _oauth_get_user(self, access_token, callback): - raise NotImplementedError() - - def _on_oauth_get_user(self, access_token, callback, user): - if not user: - callback(None) - return - user["access_token"] = access_token - callback(user) - - def _oauth_request_parameters(self, url, access_token, parameters={}, - method="GET"): - """Returns the OAuth parameters as a dict for the given request. - - parameters should include all POST arguments and query string arguments - that will be sent with the request. - """ - consumer_token = self._oauth_consumer_token() - base_args = dict( - oauth_consumer_key=consumer_token["key"], - oauth_token=access_token["key"], - oauth_signature_method="HMAC-SHA1", - oauth_timestamp=str(int(time.time())), - oauth_nonce=binascii.b2a_hex(uuid.uuid4().bytes), - oauth_version="1.0", - ) - args = {} - args.update(base_args) - args.update(parameters) - signature = _oauth_signature(consumer_token, method, url, args, - access_token) - base_args["oauth_signature"] = signature - return base_args - - -class TwitterMixin(OAuthMixin): - """Twitter OAuth authentication. - - To authenticate with Twitter, register your application with - Twitter at http://twitter.com/apps. Then copy your Consumer Key and - Consumer Secret to the application settings 'twitter_consumer_key' and - 'twitter_consumer_secret'. Use this Mixin on the handler for the URL - you registered as your application's Callback URL. - - When your application is set up, you can use this Mixin like this - to authenticate the user with Twitter and get access to their stream: - - class TwitterHandler(tornado.web.RequestHandler, - tornado.auth.TwitterMixin): - @tornado.web.asynchronous - def get(self): - if self.get_argument("oauth_token", None): - self.get_authenticated_user(self.async_callback(self._on_auth)) - return - self.authorize_redirect() - - def _on_auth(self, user): - if not user: - raise tornado.web.HTTPError(500, "Twitter auth failed") - # Save the user using, e.g., set_secure_cookie() - - The user object returned by get_authenticated_user() includes the - attributes 'username', 'name', and all of the custom Twitter user - attributes describe at - http://apiwiki.twitter.com/Twitter-REST-API-Method%3A-users%C2%A0show - in addition to 'access_token'. You should save the access token with - the user; it is required to make requests on behalf of the user later - with twitter_request(). - """ - _OAUTH_REQUEST_TOKEN_URL = "http://api.twitter.com/oauth/request_token" - _OAUTH_ACCESS_TOKEN_URL = "http://api.twitter.com/oauth/access_token" - _OAUTH_AUTHORIZE_URL = "http://api.twitter.com/oauth/authorize" - _OAUTH_AUTHENTICATE_URL = "http://api.twitter.com/oauth/authenticate" - _OAUTH_NO_CALLBACKS = True - - def authenticate_redirect(self): - """Just like authorize_redirect(), but auto-redirects if authorized. - - This is generally the right interface to use if you are using - Twitter for single-sign on. - """ - http = httpclient.AsyncHTTPClient() - http.fetch(self._oauth_request_token_url(), self.async_callback( - self._on_request_token, self._OAUTH_AUTHENTICATE_URL, None)) - - def twitter_request(self, path, callback, access_token=None, - post_args=None, **args): - """Fetches the given API path, e.g., "/statuses/user_timeline/btaylor" - - The path should not include the format (we automatically append - ".json" and parse the JSON output). - - If the request is a POST, post_args should be provided. Query - string arguments should be given as keyword arguments. - - All the Twitter methods are documented at - http://apiwiki.twitter.com/Twitter-API-Documentation. - - Many methods require an OAuth access token which you can obtain - through authorize_redirect() and get_authenticated_user(). The - user returned through that process includes an 'access_token' - attribute that can be used to make authenticated requests via - this method. Example usage: - - class MainHandler(tornado.web.RequestHandler, - tornado.auth.TwitterMixin): - @tornado.web.authenticated - @tornado.web.asynchronous - def get(self): - self.twitter_request( - "/statuses/update", - post_args={"status": "Testing Tornado Web Server"}, - access_token=user["access_token"], - callback=self.async_callback(self._on_post)) - - def _on_post(self, new_entry): - if not new_entry: - # Call failed; perhaps missing permission? - self.authorize_redirect() - return - self.finish("Posted a message!") - - """ - # Add the OAuth resource request signature if we have credentials - url = "http://api.twitter.com/1" + path + ".json" - if access_token: - all_args = {} - all_args.update(args) - all_args.update(post_args or {}) - consumer_token = self._oauth_consumer_token() - method = "POST" if post_args is not None else "GET" - oauth = self._oauth_request_parameters( - url, access_token, all_args, method=method) - args.update(oauth) - if args: url += "?" + urllib.urlencode(args) - callback = self.async_callback(self._on_twitter_request, callback) - http = httpclient.AsyncHTTPClient() - if post_args is not None: - http.fetch(url, method="POST", body=urllib.urlencode(post_args), - callback=callback) - else: - http.fetch(url, callback=callback) - - def _on_twitter_request(self, callback, response): - if response.error: - logging.warning("Error response %s fetching %s", response.error, - response.request.url) - callback(None) - return - callback(escape.json_decode(response.body)) - - def _oauth_consumer_token(self): - self.require_setting("twitter_consumer_key", "Twitter OAuth") - self.require_setting("twitter_consumer_secret", "Twitter OAuth") - return dict( - key=self.settings["twitter_consumer_key"], - secret=self.settings["twitter_consumer_secret"]) - - def _oauth_get_user(self, access_token, callback): - callback = self.async_callback(self._parse_user_response, callback) - self.twitter_request( - "/users/show/" + access_token["screen_name"], - access_token=access_token, callback=callback) - - def _parse_user_response(self, callback, user): - if user: - user["username"] = user["screen_name"] - callback(user) - - -class FriendFeedMixin(OAuthMixin): - """FriendFeed OAuth authentication. - - To authenticate with FriendFeed, register your application with - FriendFeed at http://friendfeed.com/api/applications. Then - copy your Consumer Key and Consumer Secret to the application settings - 'friendfeed_consumer_key' and 'friendfeed_consumer_secret'. Use - this Mixin on the handler for the URL you registered as your - application's Callback URL. - - When your application is set up, you can use this Mixin like this - to authenticate the user with FriendFeed and get access to their feed: - - class FriendFeedHandler(tornado.web.RequestHandler, - tornado.auth.FriendFeedMixin): - @tornado.web.asynchronous - def get(self): - if self.get_argument("oauth_token", None): - self.get_authenticated_user(self.async_callback(self._on_auth)) - return - self.authorize_redirect() - - def _on_auth(self, user): - if not user: - raise tornado.web.HTTPError(500, "FriendFeed auth failed") - # Save the user using, e.g., set_secure_cookie() - - The user object returned by get_authenticated_user() includes the - attributes 'username', 'name', and 'description' in addition to - 'access_token'. You should save the access token with the user; - it is required to make requests on behalf of the user later with - friendfeed_request(). - """ - _OAUTH_REQUEST_TOKEN_URL = "https://friendfeed.com/account/oauth/request_token" - _OAUTH_ACCESS_TOKEN_URL = "https://friendfeed.com/account/oauth/access_token" - _OAUTH_AUTHORIZE_URL = "https://friendfeed.com/account/oauth/authorize" - _OAUTH_NO_CALLBACKS = True - - def friendfeed_request(self, path, callback, access_token=None, - post_args=None, **args): - """Fetches the given relative API path, e.g., "/bret/friends" - - If the request is a POST, post_args should be provided. Query - string arguments should be given as keyword arguments. - - All the FriendFeed methods are documented at - http://friendfeed.com/api/documentation. - - Many methods require an OAuth access token which you can obtain - through authorize_redirect() and get_authenticated_user(). The - user returned through that process includes an 'access_token' - attribute that can be used to make authenticated requests via - this method. Example usage: - - class MainHandler(tornado.web.RequestHandler, - tornado.auth.FriendFeedMixin): - @tornado.web.authenticated - @tornado.web.asynchronous - def get(self): - self.friendfeed_request( - "/entry", - post_args={"body": "Testing Tornado Web Server"}, - access_token=self.current_user["access_token"], - callback=self.async_callback(self._on_post)) - - def _on_post(self, new_entry): - if not new_entry: - # Call failed; perhaps missing permission? - self.authorize_redirect() - return - self.finish("Posted a message!") - - """ - # Add the OAuth resource request signature if we have credentials - url = "http://friendfeed-api.com/v2" + path - if access_token: - all_args = {} - all_args.update(args) - all_args.update(post_args or {}) - consumer_token = self._oauth_consumer_token() - method = "POST" if post_args is not None else "GET" - oauth = self._oauth_request_parameters( - url, access_token, all_args, method=method) - args.update(oauth) - if args: url += "?" + urllib.urlencode(args) - callback = self.async_callback(self._on_friendfeed_request, callback) - http = httpclient.AsyncHTTPClient() - if post_args is not None: - http.fetch(url, method="POST", body=urllib.urlencode(post_args), - callback=callback) - else: - http.fetch(url, callback=callback) - - def _on_friendfeed_request(self, callback, response): - if response.error: - logging.warning("Error response %s fetching %s", response.error, - response.request.url) - callback(None) - return - callback(escape.json_decode(response.body)) - - def _oauth_consumer_token(self): - self.require_setting("friendfeed_consumer_key", "FriendFeed OAuth") - self.require_setting("friendfeed_consumer_secret", "FriendFeed OAuth") - return dict( - key=self.settings["friendfeed_consumer_key"], - secret=self.settings["friendfeed_consumer_secret"]) - - def _oauth_get_user(self, access_token, callback): - callback = self.async_callback(self._parse_user_response, callback) - self.friendfeed_request( - "/feedinfo/" + access_token["username"], - include="id,name,description", access_token=access_token, - callback=callback) - - def _parse_user_response(self, callback, user): - if user: - user["username"] = user["id"] - callback(user) - - -class GoogleMixin(OpenIdMixin, OAuthMixin): - """Google Open ID / OAuth authentication. - - No application registration is necessary to use Google for authentication - or to access Google resources on behalf of a user. To authenticate with - Google, redirect with authenticate_redirect(). On return, parse the - response with get_authenticated_user(). We send a dict containing the - values for the user, including 'email', 'name', and 'locale'. - Example usage: - - class GoogleHandler(tornado.web.RequestHandler, tornado.auth.GoogleMixin): - @tornado.web.asynchronous - def get(self): - if self.get_argument("openid.mode", None): - self.get_authenticated_user(self.async_callback(self._on_auth)) - return - self.authenticate_redirect() - - def _on_auth(self, user): - if not user: - raise tornado.web.HTTPError(500, "Google auth failed") - # Save the user with, e.g., set_secure_cookie() - - """ - _OPENID_ENDPOINT = "https://www.google.com/accounts/o8/ud" - _OAUTH_ACCESS_TOKEN_URL = "https://www.google.com/accounts/OAuthGetAccessToken" - - def authorize_redirect(self, oauth_scope, callback_uri=None, - ax_attrs=["name","email","language","username"]): - """Authenticates and authorizes for the given Google resource. - - Some of the available resources are: - - Gmail Contacts - http://www.google.com/m8/feeds/ - Calendar - http://www.google.com/calendar/feeds/ - Finance - http://finance.google.com/finance/feeds/ - - You can authorize multiple resources by separating the resource - URLs with a space. - """ - callback_uri = callback_uri or self.request.path - args = self._openid_args(callback_uri, ax_attrs=ax_attrs, - oauth_scope=oauth_scope) - self.redirect(self._OPENID_ENDPOINT + "?" + urllib.urlencode(args)) - - def get_authenticated_user(self, callback): - """Fetches the authenticated user data upon redirect.""" - # Look to see if we are doing combined OpenID/OAuth - oauth_ns = "" - for name, values in self.request.arguments.iteritems(): - if name.startswith("openid.ns.") and \ - values[-1] == u"http://specs.openid.net/extensions/oauth/1.0": - oauth_ns = name[10:] - break - token = self.get_argument("openid." + oauth_ns + ".request_token", "") - if token: - http = httpclient.AsyncHTTPClient() - token = dict(key=token, secret="") - http.fetch(self._oauth_access_token_url(token), - self.async_callback(self._on_access_token, callback)) - else: - OpenIdMixin.get_authenticated_user(self, callback) - - def _oauth_consumer_token(self): - self.require_setting("google_consumer_key", "Google OAuth") - self.require_setting("google_consumer_secret", "Google OAuth") - return dict( - key=self.settings["google_consumer_key"], - secret=self.settings["google_consumer_secret"]) - - def _oauth_get_user(self, access_token, callback): - OpenIdMixin.get_authenticated_user(self, callback) - - -class FacebookMixin(object): - """Facebook Connect authentication. - - To authenticate with Facebook, register your application with - Facebook at http://www.facebook.com/developers/apps.php. Then - copy your API Key and Application Secret to the application settings - 'facebook_api_key' and 'facebook_secret'. - - When your application is set up, you can use this Mixin like this - to authenticate the user with Facebook: - - class FacebookHandler(tornado.web.RequestHandler, - tornado.auth.FacebookMixin): - @tornado.web.asynchronous - def get(self): - if self.get_argument("session", None): - self.get_authenticated_user(self.async_callback(self._on_auth)) - return - self.authenticate_redirect() - - def _on_auth(self, user): - if not user: - raise tornado.web.HTTPError(500, "Facebook auth failed") - # Save the user using, e.g., set_secure_cookie() - - The user object returned by get_authenticated_user() includes the - attributes 'facebook_uid' and 'name' in addition to session attributes - like 'session_key'. You should save the session key with the user; it is - required to make requests on behalf of the user later with - facebook_request(). - """ - def authenticate_redirect(self, callback_uri=None, cancel_uri=None, - extended_permissions=None): - """Authenticates/installs this app for the current user.""" - self.require_setting("facebook_api_key", "Facebook Connect") - callback_uri = callback_uri or self.request.path - args = { - "api_key": self.settings["facebook_api_key"], - "v": "1.0", - "fbconnect": "true", - "display": "page", - "next": urlparse.urljoin(self.request.full_url(), callback_uri), - "return_session": "true", - } - if cancel_uri: - args["cancel_url"] = urlparse.urljoin( - self.request.full_url(), cancel_uri) - if extended_permissions: - if isinstance(extended_permissions, basestring): - extended_permissions = [extended_permissions] - args["req_perms"] = ",".join(extended_permissions) - self.redirect("http://www.facebook.com/login.php?" + - urllib.urlencode(args)) - - def authorize_redirect(self, extended_permissions, callback_uri=None, - cancel_uri=None): - """Redirects to an authorization request for the given FB resource. - - The available resource names are listed at - http://wiki.developers.facebook.com/index.php/Extended_permission. - The most common resource types include: - - publish_stream - read_stream - email - sms - - extended_permissions can be a single permission name or a list of - names. To get the session secret and session key, call - get_authenticated_user() just as you would with - authenticate_redirect(). - """ - self.authenticate_redirect(callback_uri, cancel_uri, - extended_permissions) - - def get_authenticated_user(self, callback): - """Fetches the authenticated Facebook user. - - The authenticated user includes the special Facebook attributes - 'session_key' and 'facebook_uid' in addition to the standard - user attributes like 'name'. - """ - self.require_setting("facebook_api_key", "Facebook Connect") - session = escape.json_decode(self.get_argument("session")) - self.facebook_request( - method="facebook.users.getInfo", - callback=self.async_callback( - self._on_get_user_info, callback, session), - session_key=session["session_key"], - uids=session["uid"], - fields="uid,first_name,last_name,name,locale,pic_square," \ - "profile_url,username") - - def facebook_request(self, method, callback, **args): - """Makes a Facebook API REST request. - - We automatically include the Facebook API key and signature, but - it is the callers responsibility to include 'session_key' and any - other required arguments to the method. - - The available Facebook methods are documented here: - http://wiki.developers.facebook.com/index.php/API - - Here is an example for the stream.get() method: - - class MainHandler(tornado.web.RequestHandler, - tornado.auth.FacebookMixin): - @tornado.web.authenticated - @tornado.web.asynchronous - def get(self): - self.facebook_request( - method="stream.get", - callback=self.async_callback(self._on_stream), - session_key=self.current_user["session_key"]) - - def _on_stream(self, stream): - if stream is None: - # Not authorized to read the stream yet? - self.redirect(self.authorize_redirect("read_stream")) - return - self.render("stream.html", stream=stream) - - """ - self.require_setting("facebook_api_key", "Facebook Connect") - self.require_setting("facebook_secret", "Facebook Connect") - if not method.startswith("facebook."): - method = "facebook." + method - args["api_key"] = self.settings["facebook_api_key"] - args["v"] = "1.0" - args["method"] = method - args["call_id"] = str(long(time.time() * 1e6)) - args["format"] = "json" - args["sig"] = self._signature(args) - url = "http://api.facebook.com/restserver.php?" + \ - urllib.urlencode(args) - http = httpclient.AsyncHTTPClient() - http.fetch(url, callback=self.async_callback( - self._parse_response, callback)) - - def _on_get_user_info(self, callback, session, users): - if users is None: - callback(None) - return - callback({ - "name": users[0]["name"], - "first_name": users[0]["first_name"], - "last_name": users[0]["last_name"], - "uid": users[0]["uid"], - "locale": users[0]["locale"], - "pic_square": users[0]["pic_square"], - "profile_url": users[0]["profile_url"], - "username": users[0].get("username"), - "session_key": session["session_key"], - "session_expires": session.get("expires"), - }) - - def _parse_response(self, callback, response): - if response.error: - logging.warning("HTTP error from Facebook: %s", response.error) - callback(None) - return - try: - json = escape.json_decode(response.body) - except: - logging.warning("Invalid JSON from Facebook: %r", response.body) - callback(None) - return - if isinstance(json, dict) and json.get("error_code"): - logging.warning("Facebook error: %d: %r", json["error_code"], - json.get("error_msg")) - callback(None) - return - callback(json) - - def _signature(self, args): - parts = ["%s=%s" % (n, args[n]) for n in sorted(args.keys())] - body = "".join(parts) + self.settings["facebook_secret"] - if isinstance(body, unicode): body = body.encode("utf-8") - return hashlib.md5(body).hexdigest() - - -def _oauth_signature(consumer_token, method, url, parameters={}, token=None): - """Calculates the HMAC-SHA1 OAuth signature for the given request. - - See http://oauth.net/core/1.0/#signing_process - """ - parts = urlparse.urlparse(url) - scheme, netloc, path = parts[:3] - normalized_url = scheme.lower() + "://" + netloc.lower() + path - - base_elems = [] - base_elems.append(method.upper()) - base_elems.append(normalized_url) - base_elems.append("&".join("%s=%s" % (k, _oauth_escape(str(v))) - for k, v in sorted(parameters.items()))) - base_string = "&".join(_oauth_escape(e) for e in base_elems) - - key_elems = [consumer_token["secret"]] - key_elems.append(token["secret"] if token else "") - key = "&".join(key_elems) - - hash = hmac.new(key, base_string, hashlib.sha1) - return binascii.b2a_base64(hash.digest())[:-1] - - -def _oauth_escape(val): - if isinstance(val, unicode): - val = val.encode("utf-8") - return urllib.quote(val, safe="~") - - -def _oauth_parse_response(body): - p = cgi.parse_qs(body, keep_blank_values=False) - token = dict(key=p["oauth_token"][0], secret=p["oauth_token_secret"][0]) - - # Add the extra parameters the Provider included to the token - special = ("oauth_token", "oauth_token_secret") - token.update((k, p[k][0]) for k in p if k not in special) - return token diff --git a/lib/tornado/autoreload.py b/lib/tornado/autoreload.py deleted file mode 100644 index 876c76d..0000000 --- a/lib/tornado/autoreload.py +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""A module to automatically restart the server when a module is modified. - -This module depends on IOLoop, so it will not work in WSGI applications -and Google AppEngine. -""" - -import functools -import ioloop -import logging -import os -import sys -import types - -try: - import signal -except ImportError: - signal = None - -def start(io_loop=None, check_time=500): - """Restarts the process automatically when a module is modified. - - We run on the I/O loop, and restarting is a destructive operation, - so will terminate any pending requests. - """ - io_loop = io_loop or ioloop.IOLoop.instance() - modify_times = {} - callback = functools.partial(_reload_on_update, io_loop, modify_times) - scheduler = ioloop.PeriodicCallback(callback, check_time, io_loop=io_loop) - scheduler.start() - - -_reload_attempted = False - -def _reload_on_update(io_loop, modify_times): - global _reload_attempted - if _reload_attempted: - # We already tried to reload and it didn't work, so don't try again. - return - for module in sys.modules.values(): - # Some modules play games with sys.modules (e.g. email/__init__.py - # in the standard library), and occasionally this can cause strange - # failures in getattr. Just ignore anything that's not an ordinary - # module. - if not isinstance(module, types.ModuleType): continue - path = getattr(module, "__file__", None) - if not path: continue - if path.endswith(".pyc") or path.endswith(".pyo"): - path = path[:-1] - try: - modified = os.stat(path).st_mtime - except: - continue - if path not in modify_times: - modify_times[path] = modified - continue - if modify_times[path] != modified: - logging.info("%s modified; restarting server", path) - _reload_attempted = True - for fd in io_loop._handlers.keys(): - try: - os.close(fd) - except: - pass - if hasattr(signal, "setitimer"): - # Clear the alarm signal set by - # ioloop.set_blocking_log_threshold so it doesn't fire - # after the exec. - signal.setitimer(signal.ITIMER_REAL, 0, 0) - try: - os.execv(sys.executable, [sys.executable] + sys.argv) - except OSError: - # Mac OS X versions prior to 10.6 do not support execv in - # a process that contains multiple threads. Instead of - # re-executing in the current process, start a new one - # and cause the current process to exit. This isn't - # ideal since the new process is detached from the parent - # terminal and thus cannot easily be killed with ctrl-C, - # but it's better than not being able to autoreload at - # all. - # Unfortunately the errno returned in this case does not - # appear to be consistent, so we can't easily check for - # this error specifically. - os.spawnv(os.P_NOWAIT, sys.executable, - [sys.executable] + sys.argv) - sys.exit(0) diff --git a/lib/tornado/database.py b/lib/tornado/database.py deleted file mode 100644 index 0787021..0000000 --- a/lib/tornado/database.py +++ /dev/null @@ -1,180 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""A lightweight wrapper around MySQLdb.""" - -import copy -import MySQLdb.constants -import MySQLdb.converters -import MySQLdb.cursors -import itertools -import logging - -class Connection(object): - """A lightweight wrapper around MySQLdb DB-API connections. - - The main value we provide is wrapping rows in a dict/object so that - columns can be accessed by name. Typical usage: - - db = database.Connection("localhost", "mydatabase") - for article in db.query("SELECT * FROM articles"): - print article.title - - Cursors are hidden by the implementation, but other than that, the methods - are very similar to the DB-API. - - We explicitly set the timezone to UTC and the character encoding to - UTF-8 on all connections to avoid time zone and encoding errors. - """ - def __init__(self, host, database, user=None, password=None): - self.host = host - self.database = database - - args = dict(conv=CONVERSIONS, use_unicode=True, charset="utf8", - db=database, init_command='SET time_zone = "+0:00"', - sql_mode="TRADITIONAL") - if user is not None: - args["user"] = user - if password is not None: - args["passwd"] = password - - # We accept a path to a MySQL socket file or a host(:port) string - if "/" in host: - args["unix_socket"] = host - else: - self.socket = None - pair = host.split(":") - if len(pair) == 2: - args["host"] = pair[0] - args["port"] = int(pair[1]) - else: - args["host"] = host - args["port"] = 3306 - - self._db = None - self._db_args = args - try: - self.reconnect() - except: - logging.error("Cannot connect to MySQL on %s", self.host, - exc_info=True) - - def __del__(self): - self.close() - - def close(self): - """Closes this database connection.""" - if getattr(self, "_db", None) is not None: - self._db.close() - self._db = None - - def reconnect(self): - """Closes the existing database connection and re-opens it.""" - self.close() - self._db = MySQLdb.connect(**self._db_args) - self._db.autocommit(True) - - def iter(self, query, *parameters): - """Returns an iterator for the given query and parameters.""" - if self._db is None: self.reconnect() - cursor = MySQLdb.cursors.SSCursor(self._db) - try: - self._execute(cursor, query, parameters) - column_names = [d[0] for d in cursor.description] - for row in cursor: - yield Row(zip(column_names, row)) - finally: - cursor.close() - - def query(self, query, *parameters): - """Returns a row list for the given query and parameters.""" - cursor = self._cursor() - try: - self._execute(cursor, query, parameters) - column_names = [d[0] for d in cursor.description] - return [Row(itertools.izip(column_names, row)) for row in cursor] - finally: - cursor.close() - - def get(self, query, *parameters): - """Returns the first row returned for the given query.""" - rows = self.query(query, *parameters) - if not rows: - return None - elif len(rows) > 1: - raise Exception("Multiple rows returned for Database.get() query") - else: - return rows[0] - - def execute(self, query, *parameters): - """Executes the given query, returning the lastrowid from the query.""" - cursor = self._cursor() - try: - self._execute(cursor, query, parameters) - return cursor.lastrowid - finally: - cursor.close() - - def executemany(self, query, parameters): - """Executes the given query against all the given param sequences. - - We return the lastrowid from the query. - """ - cursor = self._cursor() - try: - cursor.executemany(query, parameters) - return cursor.lastrowid - finally: - cursor.close() - - def _cursor(self): - if self._db is None: self.reconnect() - return self._db.cursor() - - def _execute(self, cursor, query, parameters): - try: - return cursor.execute(query, parameters) - except OperationalError: - logging.error("Error connecting to MySQL on %s", self.host) - self.close() - raise - - -class Row(dict): - """A dict that allows for object-like property access syntax.""" - def __getattr__(self, name): - try: - return self[name] - except KeyError: - raise AttributeError(name) - - -# Fix the access conversions to properly recognize unicode/binary -FIELD_TYPE = MySQLdb.constants.FIELD_TYPE -FLAG = MySQLdb.constants.FLAG -CONVERSIONS = copy.deepcopy(MySQLdb.converters.conversions) - -field_types = [FIELD_TYPE.BLOB, FIELD_TYPE.STRING, FIELD_TYPE.VAR_STRING] -if 'VARCHAR' in vars(FIELD_TYPE): - field_types.append(FIELD_TYPE.VARCHAR) - -for field_type in field_types: - CONVERSIONS[field_type].insert(0, (FLAG.BINARY, str)) - - -# Alias some common MySQL exceptions -IntegrityError = MySQLdb.IntegrityError -OperationalError = MySQLdb.OperationalError diff --git a/lib/tornado/epoll.c b/lib/tornado/epoll.c deleted file mode 100644 index 9a2e3a3..0000000 --- a/lib/tornado/epoll.c +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright 2009 Facebook - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. You may obtain - * a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - */ - -#include "Python.h" -#include -#include - -#define MAX_EVENTS 24 - -/* - * Simple wrapper around epoll_create. - */ -static PyObject* _epoll_create(void) { - int fd = epoll_create(MAX_EVENTS); - if (fd == -1) { - PyErr_SetFromErrno(PyExc_Exception); - return NULL; - } - - return PyInt_FromLong(fd); -} - -/* - * Simple wrapper around epoll_ctl. We throw an exception if the call fails - * rather than returning the error code since it is an infrequent (and likely - * catastrophic) event when it does happen. - */ -static PyObject* _epoll_ctl(PyObject* self, PyObject* args) { - int epfd, op, fd, events; - struct epoll_event event; - - if (!PyArg_ParseTuple(args, "iiiI", &epfd, &op, &fd, &events)) { - return NULL; - } - - memset(&event, 0, sizeof(event)); - event.events = events; - event.data.fd = fd; - if (epoll_ctl(epfd, op, fd, &event) == -1) { - PyErr_SetFromErrno(PyExc_OSError); - return NULL; - } - - Py_INCREF(Py_None); - return Py_None; -} - -/* - * Simple wrapper around epoll_wait. We return None if the call times out and - * throw an exception if an error occurs. Otherwise, we return a list of - * (fd, event) tuples. - */ -static PyObject* _epoll_wait(PyObject* self, PyObject* args) { - struct epoll_event events[MAX_EVENTS]; - int epfd, timeout, num_events, i; - PyObject* list; - PyObject* tuple; - - if (!PyArg_ParseTuple(args, "ii", &epfd, &timeout)) { - return NULL; - } - - Py_BEGIN_ALLOW_THREADS - num_events = epoll_wait(epfd, events, MAX_EVENTS, timeout); - Py_END_ALLOW_THREADS - if (num_events == -1) { - PyErr_SetFromErrno(PyExc_Exception); - return NULL; - } - - list = PyList_New(num_events); - for (i = 0; i < num_events; i++) { - tuple = PyTuple_New(2); - PyTuple_SET_ITEM(tuple, 0, PyInt_FromLong(events[i].data.fd)); - PyTuple_SET_ITEM(tuple, 1, PyInt_FromLong(events[i].events)); - PyList_SET_ITEM(list, i, tuple); - } - return list; -} - -/* - * Our method declararations - */ -static PyMethodDef kEpollMethods[] = { - {"epoll_create", (PyCFunction)_epoll_create, METH_NOARGS, - "Create an epoll file descriptor"}, - {"epoll_ctl", _epoll_ctl, METH_VARARGS, - "Control an epoll file descriptor"}, - {"epoll_wait", _epoll_wait, METH_VARARGS, - "Wait for events on an epoll file descriptor"}, - {NULL, NULL, 0, NULL} -}; - -/* - * Module initialization - */ -PyMODINIT_FUNC initepoll(void) { - Py_InitModule("epoll", kEpollMethods); -} diff --git a/lib/tornado/escape.py b/lib/tornado/escape.py deleted file mode 100644 index 8852ca2..0000000 --- a/lib/tornado/escape.py +++ /dev/null @@ -1,123 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Escaping/unescaping methods for HTML, JSON, URLs, and others.""" - -import htmlentitydefs -import re -import xml.sax.saxutils -import urllib - -# json module is in the standard library as of python 2.6; fall back to -# simplejson if present for older versions. -try: - import json - assert hasattr(json, "loads") and hasattr(json, "dumps") - _json_decode = lambda s: json.loads(s) - _json_encode = lambda v: json.dumps(v) -except: - try: - import simplejson - _json_decode = lambda s: simplejson.loads(_unicode(s)) - _json_encode = lambda v: simplejson.dumps(v) - except ImportError: - try: - # For Google AppEngine - from django.utils import simplejson - _json_decode = lambda s: simplejson.loads(_unicode(s)) - _json_encode = lambda v: simplejson.dumps(v) - except ImportError: - def _json_decode(s): - raise NotImplementedError( - "A JSON parser is required, e.g., simplejson at " - "http://pypi.python.org/pypi/simplejson/") - _json_encode = _json_decode - - -def xhtml_escape(value): - """Escapes a string so it is valid within XML or XHTML.""" - return utf8(xml.sax.saxutils.escape(value, {'"': """})) - - -def xhtml_unescape(value): - """Un-escapes an XML-escaped string.""" - return re.sub(r"&(#?)(\w+?);", _convert_entity, _unicode(value)) - - -def json_encode(value): - """JSON-encodes the given Python object.""" - # JSON permits but does not require forward slashes to be escaped. - # This is useful when json data is emitted in a tags from prematurely terminating - # the javscript. Some json libraries do this escaping by default, - # although python's standard library does not, so we do it here. - # http://stackoverflow.com/questions/1580647/json-why-are-forward-slashes-escaped - return _json_encode(value).replace("= 300: - raise HTTPError(code, response=response) - return response - except pycurl.error, e: - buffer.close() - raise CurlError(*e) - - -class AsyncHTTPClient(object): - """An non-blocking HTTP client backed with pycurl. - - Example usage: - - import ioloop - - def handle_request(response): - if response.error: - print "Error:", response.error - else: - print response.body - ioloop.IOLoop.instance().stop() - - http_client = httpclient.AsyncHTTPClient() - http_client.fetch("http://www.google.com/", handle_request) - ioloop.IOLoop.instance().start() - - fetch() can take a string URL or an HTTPRequest instance, which offers - more options, like executing POST/PUT/DELETE requests. - - The keyword argument max_clients to the AsyncHTTPClient constructor - determines the maximum number of simultaneous fetch() operations that - can execute in parallel on each IOLoop. - """ - _ASYNC_CLIENTS = weakref.WeakKeyDictionary() - - def __new__(cls, io_loop=None, max_clients=10, - max_simultaneous_connections=None): - # There is one client per IOLoop since they share curl instances - io_loop = io_loop or ioloop.IOLoop.instance() - if io_loop in cls._ASYNC_CLIENTS: - return cls._ASYNC_CLIENTS[io_loop] - else: - instance = super(AsyncHTTPClient, cls).__new__(cls) - instance.io_loop = io_loop - instance._multi = pycurl.CurlMulti() - instance._curls = [_curl_create(max_simultaneous_connections) - for i in xrange(max_clients)] - instance._free_list = instance._curls[:] - instance._requests = collections.deque() - instance._fds = {} - instance._events = {} - instance._added_perform_callback = False - instance._timeout = None - instance._closed = False - cls._ASYNC_CLIENTS[io_loop] = instance - return instance - - def close(self): - """Destroys this http client, freeing any file descriptors used. - Not needed in normal use, but may be helpful in unittests that - create and destroy http clients. No other methods may be called - on the AsyncHTTPClient after close(). - """ - del AsyncHTTPClient._ASYNC_CLIENTS[self.io_loop] - for curl in self._curls: - curl.close() - self._multi.close() - self._closed = True - - def fetch(self, request, callback, **kwargs): - """Executes an HTTPRequest, calling callback with an HTTPResponse. - - If an error occurs during the fetch, the HTTPResponse given to the - callback has a non-None error attribute that contains the exception - encountered during the request. You can call response.reraise() to - throw the exception (if any) in the callback. - """ - if not isinstance(request, HTTPRequest): - request = HTTPRequest(url=request, **kwargs) - self._requests.append((request, callback)) - self._add_perform_callback() - - def _add_perform_callback(self): - if not self._added_perform_callback: - self.io_loop.add_callback(self._perform) - self._added_perform_callback = True - - def _handle_events(self, fd, events): - self._events[fd] = events - self._add_perform_callback() - - def _handle_timeout(self): - self._timeout = None - self._perform() - - def _perform(self): - self._added_perform_callback = False - - if self._closed: - return - - while True: - while True: - ret, num_handles = self._multi.perform() - if ret != pycurl.E_CALL_MULTI_PERFORM: - break - - # Update the set of active file descriptors. It is important - # that this happen immediately after perform() because - # fds that have been removed from fdset are free to be reused - # in user callbacks. - fds = {} - (readable, writable, exceptable) = self._multi.fdset() - for fd in readable: - fds[fd] = fds.get(fd, 0) | 0x1 | 0x2 - for fd in writable: - fds[fd] = fds.get(fd, 0) | 0x4 - for fd in exceptable: - fds[fd] = fds.get(fd, 0) | 0x8 | 0x10 - - if fds and max(fds.iterkeys()) > 900: - # Libcurl has a bug in which it behaves unpredictably with - # file descriptors greater than 1024. (This is because - # even though it uses poll() instead of select(), it still - # uses FD_SET internally) Since curl opens its own file - # descriptors we can't catch this problem when it happens, - # and the best we can do is detect that it's about to - # happen. Exiting is a lousy way to handle this error, - # but there's not much we can do at this point. Exiting - # (and getting restarted by whatever monitoring process - # is handling crashed tornado processes) will at least - # get things working again and hopefully bring the issue - # to someone's attention. - # If you run into this issue, you either have a file descriptor - # leak or need to run more tornado processes (so that none - # of them are handling more than 1000 simultaneous connections) - print >> sys.stderr, "ERROR: File descriptor too high for libcurl. Exiting." - logging.error("File descriptor too high for libcurl. Exiting.") - sys.exit(1) - - for fd in self._fds: - if fd not in fds: - try: - self.io_loop.remove_handler(fd) - except (OSError, IOError), e: - if e[0] != errno.ENOENT: - raise - - for fd, events in fds.iteritems(): - old_events = self._fds.get(fd, None) - if old_events is None: - self.io_loop.add_handler(fd, self._handle_events, events) - elif old_events != events: - try: - self.io_loop.update_handler(fd, events) - except (OSError, IOError), e: - if e[0] == errno.ENOENT: - self.io_loop.add_handler(fd, self._handle_events, - events) - else: - raise - self._fds = fds - - - # Handle completed fetches - completed = 0 - while True: - num_q, ok_list, err_list = self._multi.info_read() - for curl in ok_list: - self._finish(curl) - completed += 1 - for curl, errnum, errmsg in err_list: - self._finish(curl, errnum, errmsg) - completed += 1 - if num_q == 0: - break - - # Start fetching new URLs - started = 0 - while self._free_list and self._requests: - started += 1 - curl = self._free_list.pop() - (request, callback) = self._requests.popleft() - curl.info = { - "headers": httputil.HTTPHeaders(), - "buffer": cStringIO.StringIO(), - "request": request, - "callback": callback, - "start_time": time.time(), - } - _curl_setup_request(curl, request, curl.info["buffer"], - curl.info["headers"]) - self._multi.add_handle(curl) - - if not started and not completed: - break - - if self._timeout is not None: - self.io_loop.remove_timeout(self._timeout) - self._timeout = None - - if num_handles: - self._timeout = self.io_loop.add_timeout( - time.time() + 0.2, self._handle_timeout) - - - def _finish(self, curl, curl_error=None, curl_message=None): - info = curl.info - curl.info = None - self._multi.remove_handle(curl) - self._free_list.append(curl) - buffer = info["buffer"] - if curl_error: - error = CurlError(curl_error, curl_message) - code = error.code - body = None - effective_url = None - buffer.close() - buffer = None - else: - error = None - code = curl.getinfo(pycurl.HTTP_CODE) - effective_url = curl.getinfo(pycurl.EFFECTIVE_URL) - buffer.seek(0) - try: - info["callback"](HTTPResponse( - request=info["request"], code=code, headers=info["headers"], - buffer=buffer, effective_url=effective_url, error=error, - request_time=time.time() - info["start_time"])) - except (KeyboardInterrupt, SystemExit): - raise - except: - logging.error("Exception in callback %r", info["callback"], - exc_info=True) - - -class AsyncHTTPClient2(object): - """Alternate implementation of AsyncHTTPClient. - - This class has the same interface as AsyncHTTPClient (so see that class - for usage documentation) but is implemented with a different set of - libcurl APIs (curl_multi_socket_action instead of fdset/perform). - This implementation will likely become the default in the future, but - for now should be considered somewhat experimental. - - The main advantage of this class over the original implementation is - that it is immune to the fd > 1024 bug, so applications with a large - number of simultaneous requests (e.g. long-polling) may prefer this - version. - - Known bugs: - * Timeouts connecting to localhost - In some situations, this implementation will return a connection - timeout when the old implementation would be able to connect. This - has only been observed when connecting to localhost when using - the kqueue-based IOLoop (mac/bsd), but it may also occur on epoll (linux) - and, in principle, for non-localhost sites. - While the bug is unrelated to IPv6, disabling IPv6 will avoid the - most common manifestations of the bug, so this class disables IPv6 when - it detects an affected version of libcurl. - The underlying cause is a libcurl bug in versions up to and including - 7.21.0 (it will be fixed in the not-yet-released 7.21.1) - http://sourceforge.net/tracker/?func=detail&aid=3017819&group_id=976&atid=100976 - """ - _ASYNC_CLIENTS = weakref.WeakKeyDictionary() - - def __new__(cls, io_loop=None, max_clients=10, - max_simultaneous_connections=None): - # There is one client per IOLoop since they share curl instances - io_loop = io_loop or ioloop.IOLoop.instance() - if io_loop in cls._ASYNC_CLIENTS: - return cls._ASYNC_CLIENTS[io_loop] - else: - instance = super(AsyncHTTPClient2, cls).__new__(cls) - instance.io_loop = io_loop - instance._multi = pycurl.CurlMulti() - instance._multi.setopt(pycurl.M_TIMERFUNCTION, - instance._set_timeout) - instance._multi.setopt(pycurl.M_SOCKETFUNCTION, - instance._handle_socket) - instance._curls = [_curl_create(max_simultaneous_connections) - for i in xrange(max_clients)] - instance._free_list = instance._curls[:] - instance._requests = collections.deque() - instance._fds = {} - instance._timeout = None - cls._ASYNC_CLIENTS[io_loop] = instance - return instance - - def close(self): - """Destroys this http client, freeing any file descriptors used. - Not needed in normal use, but may be helpful in unittests that - create and destroy http clients. No other methods may be called - on the AsyncHTTPClient after close(). - """ - del AsyncHTTPClient2._ASYNC_CLIENTS[self.io_loop] - for curl in self._curls: - curl.close() - self._multi.close() - self._closed = True - - def fetch(self, request, callback, **kwargs): - """Executes an HTTPRequest, calling callback with an HTTPResponse. - - If an error occurs during the fetch, the HTTPResponse given to the - callback has a non-None error attribute that contains the exception - encountered during the request. You can call response.reraise() to - throw the exception (if any) in the callback. - """ - if not isinstance(request, HTTPRequest): - request = HTTPRequest(url=request, **kwargs) - self._requests.append((request, callback)) - self._process_queue() - self._set_timeout(0) - - def _handle_socket(self, event, fd, multi, data): - """Called by libcurl when it wants to change the file descriptors - it cares about. - """ - event_map = { - pycurl.POLL_NONE: ioloop.IOLoop.NONE, - pycurl.POLL_IN: ioloop.IOLoop.READ, - pycurl.POLL_OUT: ioloop.IOLoop.WRITE, - pycurl.POLL_INOUT: ioloop.IOLoop.READ | ioloop.IOLoop.WRITE - } - if event == pycurl.POLL_REMOVE: - self.io_loop.remove_handler(fd) - del self._fds[fd] - else: - ioloop_event = event_map[event] - if fd not in self._fds: - self._fds[fd] = ioloop_event - self.io_loop.add_handler(fd, self._handle_events, - ioloop_event) - else: - self._fds[fd] = ioloop_event - self.io_loop.update_handler(fd, ioloop_event) - - def _set_timeout(self, msecs): - """Called by libcurl to schedule a timeout.""" - if self._timeout is not None: - self.io_loop.remove_timeout(self._timeout) - self._timeout = self.io_loop.add_timeout( - time.time() + msecs/1000.0, self._handle_timeout) - - def _handle_events(self, fd, events): - """Called by IOLoop when there is activity on one of our - file descriptors. - """ - action = 0 - if events & ioloop.IOLoop.READ: action |= pycurl.CSELECT_IN - if events & ioloop.IOLoop.WRITE: action |= pycurl.CSELECT_OUT - while True: - try: - ret, num_handles = self._multi.socket_action(fd, action) - except Exception, e: - ret = e[0] - if ret != pycurl.E_CALL_MULTI_PERFORM: - break - self._finish_pending_requests() - - def _handle_timeout(self): - """Called by IOLoop when the requested timeout has passed.""" - self._timeout = None - while True: - try: - ret, num_handles = self._multi.socket_action( - pycurl.SOCKET_TIMEOUT, 0) - except Exception, e: - ret = e[0] - if ret != pycurl.E_CALL_MULTI_PERFORM: - break - self._finish_pending_requests() - - # In theory, we shouldn't have to do this because curl will - # call _set_timeout whenever the timeout changes. However, - # sometimes after _handle_timeout we will need to reschedule - # immediately even though nothing has changed from curl's - # perspective. This is because when socket_action is - # called with SOCKET_TIMEOUT, libcurl decides internally which - # timeouts need to be processed by using a monotonic clock - # (where available) while tornado uses python's time.time() - # to decide when timeouts have occurred. When those clocks - # disagree on elapsed time (as they will whenever there is an - # NTP adjustment), tornado might call _handle_timeout before - # libcurl is ready. After each timeout, resync the scheduled - # timeout with libcurl's current state. - new_timeout = self._multi.timeout() - if new_timeout != -1: - self._set_timeout(new_timeout) - - def _finish_pending_requests(self): - """Process any requests that were completed by the last - call to multi.socket_action. - """ - while True: - num_q, ok_list, err_list = self._multi.info_read() - for curl in ok_list: - self._finish(curl) - for curl, errnum, errmsg in err_list: - self._finish(curl, errnum, errmsg) - if num_q == 0: - break - self._process_queue() - - def _process_queue(self): - while True: - started = 0 - while self._free_list and self._requests: - started += 1 - curl = self._free_list.pop() - (request, callback) = self._requests.popleft() - curl.info = { - "headers": httputil.HTTPHeaders(), - "buffer": cStringIO.StringIO(), - "request": request, - "callback": callback, - "start_time": time.time(), - } - # Disable IPv6 to mitigate the effects of this bug - # on curl versions <= 7.21.0 - # http://sourceforge.net/tracker/?func=detail&aid=3017819&group_id=976&atid=100976 - if pycurl.version_info()[2] <= 0x71500: # 7.21.0 - curl.setopt(pycurl.IPRESOLVE, pycurl.IPRESOLVE_V4) - _curl_setup_request(curl, request, curl.info["buffer"], - curl.info["headers"]) - self._multi.add_handle(curl) - - if not started: - break - - def _finish(self, curl, curl_error=None, curl_message=None): - info = curl.info - curl.info = None - self._multi.remove_handle(curl) - self._free_list.append(curl) - buffer = info["buffer"] - if curl_error: - error = CurlError(curl_error, curl_message) - code = error.code - effective_url = None - buffer.close() - buffer = None - else: - error = None - code = curl.getinfo(pycurl.HTTP_CODE) - effective_url = curl.getinfo(pycurl.EFFECTIVE_URL) - buffer.seek(0) - try: - info["callback"](HTTPResponse( - request=info["request"], code=code, headers=info["headers"], - buffer=buffer, effective_url=effective_url, error=error, - request_time=time.time() - info["start_time"])) - except (KeyboardInterrupt, SystemExit): - raise - except: - logging.error("Exception in callback %r", info["callback"], - exc_info=True) - - -class HTTPRequest(object): - def __init__(self, url, method="GET", headers=None, body=None, - auth_username=None, auth_password=None, - connect_timeout=20.0, request_timeout=20.0, - if_modified_since=None, follow_redirects=True, - max_redirects=5, user_agent=None, use_gzip=True, - network_interface=None, streaming_callback=None, - header_callback=None, prepare_curl_callback=None, - allow_nonstandard_methods=False): - if headers is None: - headers = httputil.HTTPHeaders() - if if_modified_since: - timestamp = calendar.timegm(if_modified_since.utctimetuple()) - headers["If-Modified-Since"] = email.utils.formatdate( - timestamp, localtime=False, usegmt=True) - if "Pragma" not in headers: - headers["Pragma"] = "" - self.url = _utf8(url) - self.method = method - self.headers = headers - self.body = body - self.auth_username = _utf8(auth_username) - self.auth_password = _utf8(auth_password) - self.connect_timeout = connect_timeout - self.request_timeout = request_timeout - self.follow_redirects = follow_redirects - self.max_redirects = max_redirects - self.user_agent = user_agent - self.use_gzip = use_gzip - self.network_interface = network_interface - self.streaming_callback = streaming_callback - self.header_callback = header_callback - self.prepare_curl_callback = prepare_curl_callback - self.allow_nonstandard_methods = allow_nonstandard_methods - - -class HTTPResponse(object): - def __init__(self, request, code, headers={}, buffer=None, effective_url=None, - error=None, request_time=None): - self.request = request - self.code = code - self.headers = headers - self.buffer = buffer - self._body = None - if effective_url is None: - self.effective_url = request.url - else: - self.effective_url = effective_url - if error is None: - if self.code < 200 or self.code >= 300: - self.error = HTTPError(self.code, response=self) - else: - self.error = None - else: - self.error = error - self.request_time = request_time - - def _get_body(self): - if self.buffer is None: - return None - elif self._body is None: - self._body = self.buffer.getvalue() - - return self._body - - body = property(_get_body) - - def rethrow(self): - if self.error: - raise self.error - - def __repr__(self): - args = ",".join("%s=%r" % i for i in self.__dict__.iteritems()) - return "%s(%s)" % (self.__class__.__name__, args) - - def __del__(self): - if self.buffer is not None: - self.buffer.close() - - -class HTTPError(Exception): - """Exception thrown for an unsuccessful HTTP request. - - Attributes: - code - HTTP error integer error code, e.g. 404. Error code 599 is - used when no HTTP response was received, e.g. for a timeout. - response - HTTPResponse object, if any. - - Note that if follow_redirects is False, redirects become HTTPErrors, - and you can look at error.response.headers['Location'] to see the - destination of the redirect. - """ - def __init__(self, code, message=None, response=None): - self.code = code - message = message or httplib.responses.get(code, "Unknown") - self.response = response - Exception.__init__(self, "HTTP %d: %s" % (self.code, message)) - - -class CurlError(HTTPError): - def __init__(self, errno, message): - HTTPError.__init__(self, 599, message) - self.errno = errno - - -def _curl_create(max_simultaneous_connections=None): - curl = pycurl.Curl() - if logging.getLogger().isEnabledFor(logging.DEBUG): - curl.setopt(pycurl.VERBOSE, 1) - curl.setopt(pycurl.DEBUGFUNCTION, _curl_debug) - curl.setopt(pycurl.MAXCONNECTS, max_simultaneous_connections or 5) - return curl - - -def _curl_setup_request(curl, request, buffer, headers): - curl.setopt(pycurl.URL, request.url) - # Request headers may be either a regular dict or HTTPHeaders object - if isinstance(request.headers, httputil.HTTPHeaders): - curl.setopt(pycurl.HTTPHEADER, - [_utf8("%s: %s" % i) for i in request.headers.get_all()]) - else: - curl.setopt(pycurl.HTTPHEADER, - [_utf8("%s: %s" % i) for i in request.headers.iteritems()]) - if request.header_callback: - curl.setopt(pycurl.HEADERFUNCTION, request.header_callback) - else: - curl.setopt(pycurl.HEADERFUNCTION, - lambda line: _curl_header_callback(headers, line)) - if request.streaming_callback: - curl.setopt(pycurl.WRITEFUNCTION, request.streaming_callback) - else: - curl.setopt(pycurl.WRITEFUNCTION, buffer.write) - curl.setopt(pycurl.FOLLOWLOCATION, request.follow_redirects) - curl.setopt(pycurl.MAXREDIRS, request.max_redirects) - curl.setopt(pycurl.CONNECTTIMEOUT, int(request.connect_timeout)) - curl.setopt(pycurl.TIMEOUT, int(request.request_timeout)) - if request.user_agent: - curl.setopt(pycurl.USERAGENT, _utf8(request.user_agent)) - else: - curl.setopt(pycurl.USERAGENT, "Mozilla/5.0 (compatible; pycurl)") - if request.network_interface: - curl.setopt(pycurl.INTERFACE, request.network_interface) - if request.use_gzip: - curl.setopt(pycurl.ENCODING, "gzip,deflate") - else: - curl.setopt(pycurl.ENCODING, "none") - - # Set the request method through curl's retarded interface which makes - # up names for almost every single method - curl_options = { - "GET": pycurl.HTTPGET, - "POST": pycurl.POST, - "PUT": pycurl.UPLOAD, - "HEAD": pycurl.NOBODY, - } - custom_methods = set(["DELETE"]) - for o in curl_options.values(): - curl.setopt(o, False) - if request.method in curl_options: - curl.unsetopt(pycurl.CUSTOMREQUEST) - curl.setopt(curl_options[request.method], True) - elif request.allow_nonstandard_methods or request.method in custom_methods: - curl.setopt(pycurl.CUSTOMREQUEST, request.method) - else: - raise KeyError('unknown method ' + request.method) - - # Handle curl's cryptic options for every individual HTTP method - if request.method in ("POST", "PUT"): - request_buffer = cStringIO.StringIO(escape.utf8(request.body)) - curl.setopt(pycurl.READFUNCTION, request_buffer.read) - if request.method == "POST": - def ioctl(cmd): - if cmd == curl.IOCMD_RESTARTREAD: - request_buffer.seek(0) - curl.setopt(pycurl.IOCTLFUNCTION, ioctl) - curl.setopt(pycurl.POSTFIELDSIZE, len(request.body)) - else: - curl.setopt(pycurl.INFILESIZE, len(request.body)) - - if request.auth_username and request.auth_password: - userpwd = "%s:%s" % (request.auth_username, request.auth_password) - curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC) - curl.setopt(pycurl.USERPWD, userpwd) - logging.info("%s %s (username: %r)", request.method, request.url, - request.auth_username) - else: - curl.unsetopt(pycurl.USERPWD) - logging.info("%s %s", request.method, request.url) - if request.prepare_curl_callback is not None: - request.prepare_curl_callback(curl) - - -def _curl_header_callback(headers, header_line): - if header_line.startswith("HTTP/"): - headers.clear() - return - if header_line == "\r\n": - return - headers.parse_line(header_line) - -def _curl_debug(debug_type, debug_msg): - debug_types = ('I', '<', '>', '<', '>') - if debug_type == 0: - logging.debug('%s', debug_msg.strip()) - elif debug_type in (1, 2): - for line in debug_msg.splitlines(): - logging.debug('%s %s', debug_types[debug_type], line) - elif debug_type == 4: - logging.debug('%s %r', debug_types[debug_type], debug_msg) - - -def _utf8(value): - if value is None: - return value - if isinstance(value, unicode): - return value.encode("utf-8") - assert isinstance(value, str) - return value diff --git a/lib/tornado/httpserver.py b/lib/tornado/httpserver.py deleted file mode 100644 index 267960a..0000000 --- a/lib/tornado/httpserver.py +++ /dev/null @@ -1,439 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""A non-blocking, single-threaded HTTP server.""" - -import cgi -import errno -import httputil -import ioloop -import iostream -import logging -import os -import socket -import time -import urlparse - -try: - import fcntl -except ImportError: - if os.name == 'nt': - import win32_support as fcntl - else: - raise - -try: - import ssl # Python 2.6+ -except ImportError: - ssl = None - -class HTTPServer(object): - """A non-blocking, single-threaded HTTP server. - - A server is defined by a request callback that takes an HTTPRequest - instance as an argument and writes a valid HTTP response with - request.write(). request.finish() finishes the request (but does not - necessarily close the connection in the case of HTTP/1.1 keep-alive - requests). A simple example server that echoes back the URI you - requested: - - import httpserver - import ioloop - - def handle_request(request): - message = "You requested %s\n" % request.uri - request.write("HTTP/1.1 200 OK\r\nContent-Length: %d\r\n\r\n%s" % ( - len(message), message)) - request.finish() - - http_server = httpserver.HTTPServer(handle_request) - http_server.listen(8888) - ioloop.IOLoop.instance().start() - - HTTPServer is a very basic connection handler. Beyond parsing the - HTTP request body and headers, the only HTTP semantics implemented - in HTTPServer is HTTP/1.1 keep-alive connections. We do not, however, - implement chunked encoding, so the request callback must provide a - Content-Length header or implement chunked encoding for HTTP/1.1 - requests for the server to run correctly for HTTP/1.1 clients. If - the request handler is unable to do this, you can provide the - no_keep_alive argument to the HTTPServer constructor, which will - ensure the connection is closed on every request no matter what HTTP - version the client is using. - - If xheaders is True, we support the X-Real-Ip and X-Scheme headers, - which override the remote IP and HTTP scheme for all requests. These - headers are useful when running Tornado behind a reverse proxy or - load balancer. - - HTTPServer can serve HTTPS (SSL) traffic with Python 2.6+ and OpenSSL. - To make this server serve SSL traffic, send the ssl_options dictionary - argument with the arguments required for the ssl.wrap_socket() method, - including "certfile" and "keyfile": - - HTTPServer(applicaton, ssl_options={ - "certfile": os.path.join(data_dir, "mydomain.crt"), - "keyfile": os.path.join(data_dir, "mydomain.key"), - }) - - By default, listen() runs in a single thread in a single process. You - can utilize all available CPUs on this machine by calling bind() and - start() instead of listen(): - - http_server = httpserver.HTTPServer(handle_request) - http_server.bind(8888) - http_server.start() # Forks multiple sub-processes - ioloop.IOLoop.instance().start() - - start() detects the number of CPUs on this machine and "pre-forks" that - number of child processes so that we have one Tornado process per CPU, - all with their own IOLoop. You can also pass in the specific number of - child processes you want to run with if you want to override this - auto-detection. - """ - def __init__(self, request_callback, no_keep_alive=False, io_loop=None, - xheaders=False, ssl_options=None): - """Initializes the server with the given request callback. - - If you use pre-forking/start() instead of the listen() method to - start your server, you should not pass an IOLoop instance to this - constructor. Each pre-forked child process will create its own - IOLoop instance after the forking process. - """ - self.request_callback = request_callback - self.no_keep_alive = no_keep_alive - self.io_loop = io_loop - self.xheaders = xheaders - self.ssl_options = ssl_options - self._socket = None - self._started = False - - def listen(self, port, address=""): - """Binds to the given port and starts the server in a single process. - - This method is a shortcut for: - - server.bind(port, address) - server.start(1) - - """ - self.bind(port, address) - self.start(1) - - def bind(self, port, address=""): - """Binds this server to the given port on the given IP address. - - To start the server, call start(). If you want to run this server - in a single process, you can call listen() as a shortcut to the - sequence of bind() and start() calls. - """ - assert not self._socket - self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) - flags = fcntl.fcntl(self._socket.fileno(), fcntl.F_GETFD) - flags |= fcntl.FD_CLOEXEC - fcntl.fcntl(self._socket.fileno(), fcntl.F_SETFD, flags) - self._socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self._socket.setblocking(0) - self._socket.bind((address, port)) - self._socket.listen(128) - - def start(self, num_processes=1): - """Starts this server in the IOLoop. - - By default, we run the server in this process and do not fork any - additional child process. - - If num_processes is None or <= 0, we detect the number of cores - available on this machine and fork that number of child - processes. If num_processes is given and > 1, we fork that - specific number of sub-processes. - - Since we use processes and not threads, there is no shared memory - between any server code. - """ - assert not self._started - self._started = True - if num_processes is None or num_processes <= 0: - # Use sysconf to detect the number of CPUs (cores) - try: - num_processes = os.sysconf("SC_NPROCESSORS_CONF") - except ValueError: - logging.error("Could not get num processors from sysconf; " - "running with one process") - num_processes = 1 - if num_processes > 1 and ioloop.IOLoop.initialized(): - logging.error("Cannot run in multiple processes: IOLoop instance " - "has already been initialized. You cannot call " - "IOLoop.instance() before calling start()") - num_processes = 1 - if num_processes > 1: - logging.info("Pre-forking %d server processes", num_processes) - for i in range(num_processes): - if os.fork() == 0: - self.io_loop = ioloop.IOLoop.instance() - self.io_loop.add_handler( - self._socket.fileno(), self._handle_events, - ioloop.IOLoop.READ) - return - os.waitpid(-1, 0) - else: - if not self.io_loop: - self.io_loop = ioloop.IOLoop.instance() - self.io_loop.add_handler(self._socket.fileno(), - self._handle_events, - ioloop.IOLoop.READ) - - def stop(self): - self.io_loop.remove_handler(self._socket.fileno()) - self._socket.close() - - def _handle_events(self, fd, events): - while True: - try: - connection, address = self._socket.accept() - except socket.error, e: - if e[0] in (errno.EWOULDBLOCK, errno.EAGAIN): - return - raise - if self.ssl_options is not None: - assert ssl, "Python 2.6+ and OpenSSL required for SSL" - connection = ssl.wrap_socket( - connection, server_side=True, **self.ssl_options) - try: - stream = iostream.IOStream(connection, io_loop=self.io_loop) - HTTPConnection(stream, address, self.request_callback, - self.no_keep_alive, self.xheaders) - except: - logging.error("Error in connection callback", exc_info=True) - - -class HTTPConnection(object): - """Handles a connection to an HTTP client, executing HTTP requests. - - We parse HTTP headers and bodies, and execute the request callback - until the HTTP conection is closed. - """ - def __init__(self, stream, address, request_callback, no_keep_alive=False, - xheaders=False): - self.stream = stream - self.address = address - self.request_callback = request_callback - self.no_keep_alive = no_keep_alive - self.xheaders = xheaders - self._request = None - self._request_finished = False - self.stream.read_until("\r\n\r\n", self._on_headers) - - def write(self, chunk): - assert self._request, "Request closed" - if not self.stream.closed(): - self.stream.write(chunk, self._on_write_complete) - - def finish(self): - assert self._request, "Request closed" - self._request_finished = True - if not self.stream.writing(): - self._finish_request() - - def _on_write_complete(self): - if self._request_finished: - self._finish_request() - - def _finish_request(self): - if self.no_keep_alive: - disconnect = True - else: - connection_header = self._request.headers.get("Connection") - if self._request.supports_http_1_1(): - disconnect = connection_header == "close" - elif ("Content-Length" in self._request.headers - or self._request.method in ("HEAD", "GET")): - disconnect = connection_header != "Keep-Alive" - else: - disconnect = True - self._request = None - self._request_finished = False - if disconnect: - self.stream.close() - return - self.stream.read_until("\r\n\r\n", self._on_headers) - - def _on_headers(self, data): - eol = data.find("\r\n") - start_line = data[:eol] - method, uri, version = start_line.split(" ") - if not version.startswith("HTTP/"): - raise Exception("Malformed HTTP version in HTTP Request-Line") - headers = httputil.HTTPHeaders.parse(data[eol:]) - self._request = HTTPRequest( - connection=self, method=method, uri=uri, version=version, - headers=headers, remote_ip=self.address[0]) - - content_length = headers.get("Content-Length") - if content_length: - content_length = int(content_length) - if content_length > self.stream.max_buffer_size: - raise Exception("Content-Length too long") - if headers.get("Expect") == "100-continue": - self.stream.write("HTTP/1.1 100 (Continue)\r\n\r\n") - self.stream.read_bytes(content_length, self._on_request_body) - return - - self.request_callback(self._request) - - def _on_request_body(self, data): - self._request.body = data - content_type = self._request.headers.get("Content-Type", "") - if self._request.method == "POST": - if content_type.startswith("application/x-www-form-urlencoded"): - arguments = cgi.parse_qs(self._request.body) - for name, values in arguments.iteritems(): - values = [v for v in values if v] - if values: - self._request.arguments.setdefault(name, []).extend( - values) - elif content_type.startswith("multipart/form-data"): - if 'boundary=' in content_type: - boundary = content_type.split('boundary=',1)[1] - if boundary: self._parse_mime_body(boundary, data) - else: - logging.warning("Invalid multipart/form-data") - self.request_callback(self._request) - - def _parse_mime_body(self, boundary, data): - # The standard allows for the boundary to be quoted in the header, - # although it's rare (it happens at least for google app engine - # xmpp). I think we're also supposed to handle backslash-escapes - # here but I'll save that until we see a client that uses them - # in the wild. - if boundary.startswith('"') and boundary.endswith('"'): - boundary = boundary[1:-1] - if data.endswith("\r\n"): - footer_length = len(boundary) + 6 - else: - footer_length = len(boundary) + 4 - parts = data[:-footer_length].split("--" + boundary + "\r\n") - for part in parts: - if not part: continue - eoh = part.find("\r\n\r\n") - if eoh == -1: - logging.warning("multipart/form-data missing headers") - continue - headers = httputil.HTTPHeaders.parse(part[:eoh]) - name_header = headers.get("Content-Disposition", "") - if not name_header.startswith("form-data;") or \ - not part.endswith("\r\n"): - logging.warning("Invalid multipart/form-data") - continue - value = part[eoh + 4:-2] - name_values = {} - for name_part in name_header[10:].split(";"): - name, name_value = name_part.strip().split("=", 1) - name_values[name] = name_value.strip('"').decode("utf-8") - if not name_values.get("name"): - logging.warning("multipart/form-data value missing name") - continue - name = name_values["name"] - if name_values.get("filename"): - ctype = headers.get("Content-Type", "application/unknown") - self._request.files.setdefault(name, []).append(dict( - filename=name_values["filename"], body=value, - content_type=ctype)) - else: - self._request.arguments.setdefault(name, []).append(value) - - -class HTTPRequest(object): - """A single HTTP request. - - GET/POST arguments are available in the arguments property, which - maps arguments names to lists of values (to support multiple values - for individual names). Names and values are both unicode always. - - File uploads are available in the files property, which maps file - names to list of files. Each file is a dictionary of the form - {"filename":..., "content_type":..., "body":...}. The content_type - comes from the provided HTTP header and should not be trusted - outright given that it can be easily forged. - - An HTTP request is attached to a single HTTP connection, which can - be accessed through the "connection" attribute. Since connections - are typically kept open in HTTP/1.1, multiple requests can be handled - sequentially on a single connection. - """ - def __init__(self, method, uri, version="HTTP/1.0", headers=None, - body=None, remote_ip=None, protocol=None, host=None, - files=None, connection=None): - self.method = method - self.uri = uri - self.version = version - self.headers = headers or httputil.HTTPHeaders() - self.body = body or "" - if connection and connection.xheaders: - # Squid uses X-Forwarded-For, others use X-Real-Ip - self.remote_ip = self.headers.get( - "X-Real-Ip", self.headers.get("X-Forwarded-For", remote_ip)) - self.protocol = self.headers.get("X-Scheme", protocol) or "http" - else: - self.remote_ip = remote_ip - self.protocol = protocol or "http" - self.host = host or self.headers.get("Host") or "127.0.0.1" - self.files = files or {} - self.connection = connection - self._start_time = time.time() - self._finish_time = None - - scheme, netloc, path, query, fragment = urlparse.urlsplit(uri) - self.path = path - self.query = query - arguments = cgi.parse_qs(query) - self.arguments = {} - for name, values in arguments.iteritems(): - values = [v for v in values if v] - if values: self.arguments[name] = values - - def supports_http_1_1(self): - """Returns True if this request supports HTTP/1.1 semantics""" - return self.version == "HTTP/1.1" - - def write(self, chunk): - """Writes the given chunk to the response stream.""" - assert isinstance(chunk, str) - self.connection.write(chunk) - - def finish(self): - """Finishes this HTTP request on the open connection.""" - self.connection.finish() - self._finish_time = time.time() - - def full_url(self): - """Reconstructs the full URL for this request.""" - return self.protocol + "://" + self.host + self.uri - - def request_time(self): - """Returns the amount of time it took for this request to execute.""" - if self._finish_time is None: - return time.time() - self._start_time - else: - return self._finish_time - self._start_time - - def __repr__(self): - attrs = ("protocol", "host", "method", "uri", "version", "remote_ip", - "remote_ip", "body") - args = ", ".join(["%s=%r" % (n, getattr(self, n)) for n in attrs]) - return "%s(%s, headers=%s)" % ( - self.__class__.__name__, args, dict(self.headers)) - diff --git a/lib/tornado/httputil.py b/lib/tornado/httputil.py deleted file mode 100755 index 5e563e8..0000000 --- a/lib/tornado/httputil.py +++ /dev/null @@ -1,140 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""HTTP utility code shared by clients and servers.""" - -class HTTPHeaders(dict): - """A dictionary that maintains Http-Header-Case for all keys. - - Supports multiple values per key via a pair of new methods, - add() and get_list(). The regular dictionary interface returns a single - value per key, with multiple values joined by a comma. - - >>> h = HTTPHeaders({"content-type": "text/html"}) - >>> h.keys() - ['Content-Type'] - >>> h["Content-Type"] - 'text/html' - - >>> h.add("Set-Cookie", "A=B") - >>> h.add("Set-Cookie", "C=D") - >>> h["set-cookie"] - 'A=B,C=D' - >>> h.get_list("set-cookie") - ['A=B', 'C=D'] - - >>> for (k,v) in sorted(h.get_all()): - ... print '%s: %s' % (k,v) - ... - Content-Type: text/html - Set-Cookie: A=B - Set-Cookie: C=D - """ - def __init__(self, *args, **kwargs): - # Don't pass args or kwargs to dict.__init__, as it will bypass - # our __setitem__ - dict.__init__(self) - self._as_list = {} - self.update(*args, **kwargs) - - # new public methods - - def add(self, name, value): - """Adds a new value for the given key.""" - norm_name = HTTPHeaders._normalize_name(name) - if norm_name in self: - # bypass our override of __setitem__ since it modifies _as_list - dict.__setitem__(self, norm_name, self[norm_name] + ',' + value) - self._as_list[norm_name].append(value) - else: - self[norm_name] = value - - def get_list(self, name): - """Returns all values for the given header as a list.""" - norm_name = HTTPHeaders._normalize_name(name) - return self._as_list.get(norm_name, []) - - def get_all(self): - """Returns an iterable of all (name, value) pairs. - - If a header has multiple values, multiple pairs will be - returned with the same name. - """ - for name, list in self._as_list.iteritems(): - for value in list: - yield (name, value) - - def parse_line(self, line): - """Updates the dictionary with a single header line. - - >>> h = HTTPHeaders() - >>> h.parse_line("Content-Type: text/html") - >>> h.get('content-type') - 'text/html' - """ - name, value = line.split(":", 1) - self.add(name, value.strip()) - - @classmethod - def parse(cls, headers): - """Returns a dictionary from HTTP header text. - - >>> h = HTTPHeaders.parse("Content-Type: text/html\\r\\nContent-Length: 42\\r\\n") - >>> sorted(h.iteritems()) - [('Content-Length', '42'), ('Content-Type', 'text/html')] - """ - h = cls() - for line in headers.splitlines(): - if line: - h.parse_line(line) - return h - - # dict implementation overrides - - def __setitem__(self, name, value): - norm_name = HTTPHeaders._normalize_name(name) - dict.__setitem__(self, norm_name, value) - self._as_list[norm_name] = [value] - - def __getitem__(self, name): - return dict.__getitem__(self, HTTPHeaders._normalize_name(name)) - - def __delitem__(self, name): - norm_name = HTTPHeaders._normalize_name(name) - dict.__delitem__(self, norm_name) - del self._as_list[norm_name] - - def get(self, name, default=None): - return dict.get(self, HTTPHeaders._normalize_name(name), default) - - def update(self, *args, **kwargs): - # dict.update bypasses our __setitem__ - for k, v in dict(*args, **kwargs).iteritems(): - self[k] = v - - @staticmethod - def _normalize_name(name): - """Converts a name to Http-Header-Case. - - >>> HTTPHeaders._normalize_name("coNtent-TYPE") - 'Content-Type' - """ - return "-".join([w.capitalize() for w in name.split("-")]) - - -if __name__ == "__main__": - import doctest - doctest.testmod() diff --git a/lib/tornado/ioloop.py b/lib/tornado/ioloop.py deleted file mode 100644 index c1345cb..0000000 --- a/lib/tornado/ioloop.py +++ /dev/null @@ -1,523 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""A level-triggered I/O loop for non-blocking sockets.""" - -import bisect -import errno -import os -import logging -import select -import time -import traceback - -try: - import signal -except ImportError: - signal = None - -try: - import fcntl -except ImportError: - if os.name == 'nt': - import win32_support - import win32_support as fcntl - else: - raise - -class IOLoop(object): - """A level-triggered I/O loop. - - We use epoll if it is available, or else we fall back on select(). If - you are implementing a system that needs to handle 1000s of simultaneous - connections, you should use Linux and either compile our epoll module or - use Python 2.6+ to get epoll support. - - Example usage for a simple TCP server: - - import errno - import functools - import ioloop - import socket - - def connection_ready(sock, fd, events): - while True: - try: - connection, address = sock.accept() - except socket.error, e: - if e[0] not in (errno.EWOULDBLOCK, errno.EAGAIN): - raise - return - connection.setblocking(0) - handle_connection(connection, address) - - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - sock.setblocking(0) - sock.bind(("", port)) - sock.listen(128) - - io_loop = ioloop.IOLoop.instance() - callback = functools.partial(connection_ready, sock) - io_loop.add_handler(sock.fileno(), callback, io_loop.READ) - io_loop.start() - - """ - # Constants from the epoll module - _EPOLLIN = 0x001 - _EPOLLPRI = 0x002 - _EPOLLOUT = 0x004 - _EPOLLERR = 0x008 - _EPOLLHUP = 0x010 - _EPOLLRDHUP = 0x2000 - _EPOLLONESHOT = (1 << 30) - _EPOLLET = (1 << 31) - - # Our events map exactly to the epoll events - NONE = 0 - READ = _EPOLLIN - WRITE = _EPOLLOUT - ERROR = _EPOLLERR | _EPOLLHUP | _EPOLLRDHUP - - def __init__(self, impl=None): - self._impl = impl or _poll() - if hasattr(self._impl, 'fileno'): - self._set_close_exec(self._impl.fileno()) - self._handlers = {} - self._events = {} - self._callbacks = set() - self._timeouts = [] - self._running = False - self._stopped = False - self._blocking_log_threshold = None - - # Create a pipe that we send bogus data to when we want to wake - # the I/O loop when it is idle - if os.name != 'nt': - r, w = os.pipe() - self._set_nonblocking(r) - self._set_nonblocking(w) - self._set_close_exec(r) - self._set_close_exec(w) - self._waker_reader = os.fdopen(r, "r", 0) - self._waker_writer = os.fdopen(w, "w", 0) - else: - self._waker_reader = self._waker_writer = win32_support.Pipe() - r = self._waker_writer.reader_fd - self.add_handler(r, self._read_waker, self.READ) - - @classmethod - def instance(cls): - """Returns a global IOLoop instance. - - Most single-threaded applications have a single, global IOLoop. - Use this method instead of passing around IOLoop instances - throughout your code. - - A common pattern for classes that depend on IOLoops is to use - a default argument to enable programs with multiple IOLoops - but not require the argument for simpler applications: - - class MyClass(object): - def __init__(self, io_loop=None): - self.io_loop = io_loop or IOLoop.instance() - """ - if not hasattr(cls, "_instance"): - cls._instance = cls() - return cls._instance - - @classmethod - def initialized(cls): - return hasattr(cls, "_instance") - - def add_handler(self, fd, handler, events): - """Registers the given handler to receive the given events for fd.""" - self._handlers[fd] = handler - self._impl.register(fd, events | self.ERROR) - - def update_handler(self, fd, events): - """Changes the events we listen for fd.""" - self._impl.modify(fd, events | self.ERROR) - - def remove_handler(self, fd): - """Stop listening for events on fd.""" - self._handlers.pop(fd, None) - self._events.pop(fd, None) - try: - self._impl.unregister(fd) - except (OSError, IOError): - logging.debug("Error deleting fd from IOLoop", exc_info=True) - - def set_blocking_log_threshold(self, s): - """Logs a stack trace if the ioloop is blocked for more than s seconds. - Pass None to disable. Requires python 2.6 on a unixy platform. - """ - if not hasattr(signal, "setitimer"): - logging.error("set_blocking_log_threshold requires a signal module " - "with the setitimer method") - return - self._blocking_log_threshold = s - if s is not None: - signal.signal(signal.SIGALRM, self._handle_alarm) - - def _handle_alarm(self, signal, frame): - logging.warning('IOLoop blocked for %f seconds in\n%s', - self._blocking_log_threshold, - ''.join(traceback.format_stack(frame))) - - def start(self): - """Starts the I/O loop. - - The loop will run until one of the I/O handlers calls stop(), which - will make the loop stop after the current event iteration completes. - """ - if self._stopped: - self._stopped = False - return - self._running = True - while True: - # Never use an infinite timeout here - it can stall epoll - poll_timeout = 0.2 - - # Prevent IO event starvation by delaying new callbacks - # to the next iteration of the event loop. - callbacks = list(self._callbacks) - for callback in callbacks: - # A callback can add or remove other callbacks - if callback in self._callbacks: - self._callbacks.remove(callback) - self._run_callback(callback) - - if self._callbacks: - poll_timeout = 0.0 - - if self._timeouts: - now = time.time() - while self._timeouts and self._timeouts[0].deadline <= now: - timeout = self._timeouts.pop(0) - self._run_callback(timeout.callback) - if self._timeouts: - milliseconds = self._timeouts[0].deadline - now - poll_timeout = min(milliseconds, poll_timeout) - - if not self._running: - break - - if self._blocking_log_threshold is not None: - # clear alarm so it doesn't fire while poll is waiting for - # events. - signal.setitimer(signal.ITIMER_REAL, 0, 0) - - try: - event_pairs = self._impl.poll(poll_timeout) - except Exception, e: - # Depending on python version and IOLoop implementation, - # different exception types may be thrown and there are - # two ways EINTR might be signaled: - # * e.errno == errno.EINTR - # * e.args is like (errno.EINTR, 'Interrupted system call') - if (getattr(e, 'errno') == errno.EINTR or - (isinstance(getattr(e, 'args'), tuple) and - len(e.args) == 2 and e.args[0] == errno.EINTR)): - logging.warning("Interrupted system call", exc_info=1) - continue - else: - raise - - if self._blocking_log_threshold is not None: - signal.setitimer(signal.ITIMER_REAL, - self._blocking_log_threshold, 0) - - # Pop one fd at a time from the set of pending fds and run - # its handler. Since that handler may perform actions on - # other file descriptors, there may be reentrant calls to - # this IOLoop that update self._events - self._events.update(event_pairs) - while self._events: - fd, events = self._events.popitem() - try: - self._handlers[fd](fd, events) - except (KeyboardInterrupt, SystemExit): - raise - except (OSError, IOError), e: - if e[0] == errno.EPIPE: - # Happens when the client closes the connection - pass - else: - logging.error("Exception in I/O handler for fd %d", - fd, exc_info=True) - except: - logging.error("Exception in I/O handler for fd %d", - fd, exc_info=True) - # reset the stopped flag so another start/stop pair can be issued - self._stopped = False - if self._blocking_log_threshold is not None: - signal.setitimer(signal.ITIMER_REAL, 0, 0) - - def stop(self): - """Stop the loop after the current event loop iteration is complete. - If the event loop is not currently running, the next call to start() - will return immediately. - - To use asynchronous methods from otherwise-synchronous code (such as - unit tests), you can start and stop the event loop like this: - ioloop = IOLoop() - async_method(ioloop=ioloop, callback=ioloop.stop) - ioloop.start() - ioloop.start() will return after async_method has run its callback, - whether that callback was invoked before or after ioloop.start. - """ - self._running = False - self._stopped = True - self._wake() - - def running(self): - """Returns true if this IOLoop is currently running.""" - return self._running - - def add_timeout(self, deadline, callback): - """Calls the given callback at the time deadline from the I/O loop.""" - timeout = _Timeout(deadline, callback) - bisect.insort(self._timeouts, timeout) - return timeout - - def remove_timeout(self, timeout): - self._timeouts.remove(timeout) - - def add_callback(self, callback): - """Calls the given callback on the next I/O loop iteration.""" - self._callbacks.add(callback) - self._wake() - - def remove_callback(self, callback): - """Removes the given callback from the next I/O loop iteration.""" - self._callbacks.remove(callback) - - def _wake(self): - try: - self._waker_writer.write("x") - except IOError: - pass - - def _run_callback(self, callback): - try: - callback() - except (KeyboardInterrupt, SystemExit): - raise - except: - self.handle_callback_exception(callback) - - def handle_callback_exception(self, callback): - """This method is called whenever a callback run by the IOLoop - throws an exception. - - By default simply logs the exception as an error. Subclasses - may override this method to customize reporting of exceptions. - - The exception itself is not passed explicitly, but is available - in sys.exc_info. - """ - logging.error("Exception in callback %r", callback, exc_info=True) - - def _read_waker(self, fd, events): - try: - while True: - self._waker_reader.read() - except IOError: - pass - - def _set_nonblocking(self, fd): - flags = fcntl.fcntl(fd, fcntl.F_GETFL) - fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK) - - def _set_close_exec(self, fd): - flags = fcntl.fcntl(fd, fcntl.F_GETFD) - fcntl.fcntl(fd, fcntl.F_SETFD, flags | fcntl.FD_CLOEXEC) - - -class _Timeout(object): - """An IOLoop timeout, a UNIX timestamp and a callback""" - - # Reduce memory overhead when there are lots of pending callbacks - __slots__ = ['deadline', 'callback'] - - def __init__(self, deadline, callback): - self.deadline = deadline - self.callback = callback - - def __cmp__(self, other): - return cmp((self.deadline, id(self.callback)), - (other.deadline, id(other.callback))) - - -class PeriodicCallback(object): - """Schedules the given callback to be called periodically. - - The callback is called every callback_time milliseconds. - """ - def __init__(self, callback, callback_time, io_loop=None): - self.callback = callback - self.callback_time = callback_time - self.io_loop = io_loop or IOLoop.instance() - self._running = True - - def start(self): - timeout = time.time() + self.callback_time / 1000.0 - self.io_loop.add_timeout(timeout, self._run) - - def stop(self): - self._running = False - - def _run(self): - if not self._running: return - try: - self.callback() - except (KeyboardInterrupt, SystemExit): - raise - except: - logging.error("Error in periodic callback", exc_info=True) - self.start() - - -class _EPoll(object): - """An epoll-based event loop using our C module for Python 2.5 systems""" - _EPOLL_CTL_ADD = 1 - _EPOLL_CTL_DEL = 2 - _EPOLL_CTL_MOD = 3 - - def __init__(self): - self._epoll_fd = epoll.epoll_create() - - def fileno(self): - return self._epoll_fd - - def register(self, fd, events): - epoll.epoll_ctl(self._epoll_fd, self._EPOLL_CTL_ADD, fd, events) - - def modify(self, fd, events): - epoll.epoll_ctl(self._epoll_fd, self._EPOLL_CTL_MOD, fd, events) - - def unregister(self, fd): - epoll.epoll_ctl(self._epoll_fd, self._EPOLL_CTL_DEL, fd, 0) - - def poll(self, timeout): - return epoll.epoll_wait(self._epoll_fd, int(timeout * 1000)) - - -class _KQueue(object): - """A kqueue-based event loop for BSD/Mac systems.""" - def __init__(self): - self._kqueue = select.kqueue() - self._active = {} - - def fileno(self): - return self._kqueue.fileno() - - def register(self, fd, events): - self._control(fd, events, select.KQ_EV_ADD) - self._active[fd] = events - - def modify(self, fd, events): - self.unregister(fd) - self.register(fd, events) - - def unregister(self, fd): - events = self._active.pop(fd) - self._control(fd, events, select.KQ_EV_DELETE) - - def _control(self, fd, events, flags): - kevents = [] - if events & IOLoop.WRITE: - kevents.append(select.kevent( - fd, filter=select.KQ_FILTER_WRITE, flags=flags)) - if events & IOLoop.READ or not kevents: - # Always read when there is not a write - kevents.append(select.kevent( - fd, filter=select.KQ_FILTER_READ, flags=flags)) - # Even though control() takes a list, it seems to return EINVAL - # on Mac OS X (10.6) when there is more than one event in the list. - for kevent in kevents: - self._kqueue.control([kevent], 0) - - def poll(self, timeout): - kevents = self._kqueue.control(None, 1000, timeout) - events = {} - for kevent in kevents: - fd = kevent.ident - flags = 0 - if kevent.filter == select.KQ_FILTER_READ: - events[fd] = events.get(fd, 0) | IOLoop.READ - if kevent.filter == select.KQ_FILTER_WRITE: - events[fd] = events.get(fd, 0) | IOLoop.WRITE - if kevent.flags & select.KQ_EV_ERROR: - events[fd] = events.get(fd, 0) | IOLoop.ERROR - return events.items() - - -class _Select(object): - """A simple, select()-based IOLoop implementation for non-Linux systems""" - def __init__(self): - self.read_fds = set() - self.write_fds = set() - self.error_fds = set() - self.fd_sets = (self.read_fds, self.write_fds, self.error_fds) - - def register(self, fd, events): - if events & IOLoop.READ: self.read_fds.add(fd) - if events & IOLoop.WRITE: self.write_fds.add(fd) - if events & IOLoop.ERROR: self.error_fds.add(fd) - - def modify(self, fd, events): - self.unregister(fd) - self.register(fd, events) - - def unregister(self, fd): - self.read_fds.discard(fd) - self.write_fds.discard(fd) - self.error_fds.discard(fd) - - def poll(self, timeout): - readable, writeable, errors = select.select( - self.read_fds, self.write_fds, self.error_fds, timeout) - events = {} - for fd in readable: - events[fd] = events.get(fd, 0) | IOLoop.READ - for fd in writeable: - events[fd] = events.get(fd, 0) | IOLoop.WRITE - for fd in errors: - events[fd] = events.get(fd, 0) | IOLoop.ERROR - return events.items() - - -# Choose a poll implementation. Use epoll if it is available, fall back to -# select() for non-Linux platforms -if hasattr(select, "epoll"): - # Python 2.6+ on Linux - _poll = select.epoll -elif hasattr(select, "kqueue"): - # Python 2.6+ on BSD or Mac - _poll = _KQueue -else: - try: - # Linux systems with our C module installed - import epoll - _poll = _EPoll - except: - # All other systems - import sys - if "linux" in sys.platform: - logging.warning("epoll module not found; using select()") - _poll = _Select diff --git a/lib/tornado/iostream.py b/lib/tornado/iostream.py deleted file mode 100644 index c22ef2c..0000000 --- a/lib/tornado/iostream.py +++ /dev/null @@ -1,242 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""A utility class to write to and read from a non-blocking socket.""" - -import errno -import ioloop -import logging -import socket - -class IOStream(object): - """A utility class to write to and read from a non-blocking socket. - - We support three methods: write(), read_until(), and read_bytes(). - All of the methods take callbacks (since writing and reading are - non-blocking and asynchronous). read_until() reads the socket until - a given delimiter, and read_bytes() reads until a specified number - of bytes have been read from the socket. - - A very simple (and broken) HTTP client using this class: - - import ioloop - import iostream - import socket - - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) - s.connect(("friendfeed.com", 80)) - stream = IOStream(s) - - def on_headers(data): - headers = {} - for line in data.split("\r\n"): - parts = line.split(":") - if len(parts) == 2: - headers[parts[0].strip()] = parts[1].strip() - stream.read_bytes(int(headers["Content-Length"]), on_body) - - def on_body(data): - print data - stream.close() - ioloop.IOLoop.instance().stop() - - stream.write("GET / HTTP/1.0\r\n\r\n") - stream.read_until("\r\n\r\n", on_headers) - ioloop.IOLoop.instance().start() - - """ - def __init__(self, socket, io_loop=None, max_buffer_size=104857600, - read_chunk_size=4096): - self.socket = socket - self.socket.setblocking(False) - self.io_loop = io_loop or ioloop.IOLoop.instance() - self.max_buffer_size = max_buffer_size - self.read_chunk_size = read_chunk_size - self._read_buffer = "" - self._write_buffer = "" - self._read_delimiter = None - self._read_bytes = None - self._read_callback = None - self._write_callback = None - self._close_callback = None - self._state = self.io_loop.ERROR - self.io_loop.add_handler( - self.socket.fileno(), self._handle_events, self._state) - - def read_until(self, delimiter, callback): - """Call callback when we read the given delimiter.""" - assert not self._read_callback, "Already reading" - loc = self._read_buffer.find(delimiter) - if loc != -1: - self._run_callback(callback, self._consume(loc + len(delimiter))) - return - self._check_closed() - self._read_delimiter = delimiter - self._read_callback = callback - self._add_io_state(self.io_loop.READ) - - def read_bytes(self, num_bytes, callback): - """Call callback when we read the given number of bytes.""" - assert not self._read_callback, "Already reading" - if len(self._read_buffer) >= num_bytes: - callback(self._consume(num_bytes)) - return - self._check_closed() - self._read_bytes = num_bytes - self._read_callback = callback - self._add_io_state(self.io_loop.READ) - - def write(self, data, callback=None): - """Write the given data to this stream. - - If callback is given, we call it when all of the buffered write - data has been successfully written to the stream. If there was - previously buffered write data and an old write callback, that - callback is simply overwritten with this new callback. - """ - self._check_closed() - self._write_buffer += data - self._add_io_state(self.io_loop.WRITE) - self._write_callback = callback - - def set_close_callback(self, callback): - """Call the given callback when the stream is closed.""" - self._close_callback = callback - - def close(self): - """Close this stream.""" - if self.socket is not None: - self.io_loop.remove_handler(self.socket.fileno()) - self.socket.close() - self.socket = None - if self._close_callback: - self._run_callback(self._close_callback) - - def reading(self): - """Returns true if we are currently reading from the stream.""" - return self._read_callback is not None - - def writing(self): - """Returns true if we are currently writing to the stream.""" - return len(self._write_buffer) > 0 - - def closed(self): - return self.socket is None - - def _handle_events(self, fd, events): - if not self.socket: - logging.warning("Got events for closed stream %d", fd) - return - if events & self.io_loop.READ: - self._handle_read() - if not self.socket: - return - if events & self.io_loop.WRITE: - self._handle_write() - if not self.socket: - return - if events & self.io_loop.ERROR: - self.close() - return - state = self.io_loop.ERROR - if self._read_delimiter or self._read_bytes: - state |= self.io_loop.READ - if self._write_buffer: - state |= self.io_loop.WRITE - if state != self._state: - self._state = state - self.io_loop.update_handler(self.socket.fileno(), self._state) - - def _run_callback(self, callback, *args, **kwargs): - try: - callback(*args, **kwargs) - except: - # Close the socket on an uncaught exception from a user callback - # (It would eventually get closed when the socket object is - # gc'd, but we don't want to rely on gc happening before we - # run out of file descriptors) - self.close() - # Re-raise the exception so that IOLoop.handle_callback_exception - # can see it and log the error - raise - - def _handle_read(self): - try: - chunk = self.socket.recv(self.read_chunk_size) - except socket.error, e: - if e[0] in (errno.EWOULDBLOCK, errno.EAGAIN): - return - else: - logging.warning("Read error on %d: %s", - self.socket.fileno(), e) - self.close() - return - if not chunk: - self.close() - return - self._read_buffer += chunk - if len(self._read_buffer) >= self.max_buffer_size: - logging.error("Reached maximum read buffer size") - self.close() - return - if self._read_bytes: - if len(self._read_buffer) >= self._read_bytes: - num_bytes = self._read_bytes - callback = self._read_callback - self._read_callback = None - self._read_bytes = None - self._run_callback(callback, self._consume(num_bytes)) - elif self._read_delimiter: - loc = self._read_buffer.find(self._read_delimiter) - if loc != -1: - callback = self._read_callback - delimiter_len = len(self._read_delimiter) - self._read_callback = None - self._read_delimiter = None - self._run_callback(callback, - self._consume(loc + delimiter_len)) - - def _handle_write(self): - while self._write_buffer: - try: - num_bytes = self.socket.send(self._write_buffer) - self._write_buffer = self._write_buffer[num_bytes:] - except socket.error, e: - if e[0] in (errno.EWOULDBLOCK, errno.EAGAIN): - break - else: - logging.warning("Write error on %d: %s", - self.socket.fileno(), e) - self.close() - return - if not self._write_buffer and self._write_callback: - callback = self._write_callback - self._write_callback = None - self._run_callback(callback) - - def _consume(self, loc): - result = self._read_buffer[:loc] - self._read_buffer = self._read_buffer[loc:] - return result - - def _check_closed(self): - if not self.socket: - raise IOError("Stream is closed") - - def _add_io_state(self, state): - if not self._state & state: - self._state = self._state | state - self.io_loop.update_handler(self.socket.fileno(), self._state) diff --git a/lib/tornado/locale.py b/lib/tornado/locale.py deleted file mode 100644 index a2d9b2b..0000000 --- a/lib/tornado/locale.py +++ /dev/null @@ -1,453 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Translation methods for generating localized strings. - -To load a locale and generate a translated string: - - user_locale = locale.get("es_LA") - print user_locale.translate("Sign out") - -locale.get() returns the closest matching locale, not necessarily the -specific locale you requested. You can support pluralization with -additional arguments to translate(), e.g.: - - people = [...] - message = user_locale.translate( - "%(list)s is online", "%(list)s are online", len(people)) - print message % {"list": user_locale.list(people)} - -The first string is chosen if len(people) == 1, otherwise the second -string is chosen. - -Applications should call one of load_translations (which uses a simple -CSV format) or load_gettext_translations (which uses the .mo format -supported by gettext and related tools). If neither method is called, -the locale.translate method will simply return the original string. -""" - -import csv -import datetime -import logging -import os - -_default_locale = "en_US" -_translations = {} -_supported_locales = frozenset([_default_locale]) -_use_gettext = False - -def get(*locale_codes): - """Returns the closest match for the given locale codes. - - We iterate over all given locale codes in order. If we have a tight - or a loose match for the code (e.g., "en" for "en_US"), we return - the locale. Otherwise we move to the next code in the list. - - By default we return en_US if no translations are found for any of - the specified locales. You can change the default locale with - set_default_locale() below. - """ - return Locale.get_closest(*locale_codes) - - -def set_default_locale(code): - """Sets the default locale, used in get_closest_locale(). - - The default locale is assumed to be the language used for all strings - in the system. The translations loaded from disk are mappings from - the default locale to the destination locale. Consequently, you don't - need to create a translation file for the default locale. - """ - global _default_locale - global _supported_locales - _default_locale = code - _supported_locales = frozenset(_translations.keys() + [_default_locale]) - - -def load_translations(directory): - """Loads translations from CSV files in a directory. - - Translations are strings with optional Python-style named placeholders - (e.g., "My name is %(name)s") and their associated translations. - - The directory should have translation files of the form LOCALE.csv, - e.g. es_GT.csv. The CSV files should have two or three columns: string, - translation, and an optional plural indicator. Plural indicators should - be one of "plural" or "singular". A given string can have both singular - and plural forms. For example "%(name)s liked this" may have a - different verb conjugation depending on whether %(name)s is one - name or a list of names. There should be two rows in the CSV file for - that string, one with plural indicator "singular", and one "plural". - For strings with no verbs that would change on translation, simply - use "unknown" or the empty string (or don't include the column at all). - - Example translation es_LA.csv: - - "I love you","Te amo" - "%(name)s liked this","A %(name)s les gust\xf3 esto","plural" - "%(name)s liked this","A %(name)s le gust\xf3 esto","singular" - - """ - global _translations - global _supported_locales - _translations = {} - for path in os.listdir(directory): - if not path.endswith(".csv"): continue - locale, extension = path.split(".") - if locale not in LOCALE_NAMES: - logging.error("Unrecognized locale %r (path: %s)", locale, - os.path.join(directory, path)) - continue - f = open(os.path.join(directory, path), "r") - _translations[locale] = {} - for i, row in enumerate(csv.reader(f)): - if not row or len(row) < 2: continue - row = [c.decode("utf-8").strip() for c in row] - english, translation = row[:2] - if len(row) > 2: - plural = row[2] or "unknown" - else: - plural = "unknown" - if plural not in ("plural", "singular", "unknown"): - logging.error("Unrecognized plural indicator %r in %s line %d", - plural, path, i + 1) - continue - _translations[locale].setdefault(plural, {})[english] = translation - f.close() - _supported_locales = frozenset(_translations.keys() + [_default_locale]) - logging.info("Supported locales: %s", sorted(_supported_locales)) - -def load_gettext_translations(directory, domain): - """Loads translations from gettext's locale tree - - Locale tree is similar to system's /usr/share/locale, like: - - {directory}/{lang}/LC_MESSAGES/{domain}.mo - - Three steps are required to have you app translated: - - 1. Generate POT translation file - xgettext --language=Python --keyword=_:1,2 -d cyclone file1.py file2.html etc - - 2. Merge against existing POT file: - msgmerge old.po cyclone.po > new.po - - 3. Compile: - msgfmt cyclone.po -o {directory}/pt_BR/LC_MESSAGES/cyclone.mo - """ - import gettext - global _translations - global _supported_locales - global _use_gettext - _translations = {} - for lang in os.listdir(directory): - if os.path.isfile(os.path.join(directory, lang)): continue - try: - os.stat(os.path.join(directory, lang, "LC_MESSAGES", domain+".mo")) - _translations[lang] = gettext.translation(domain, directory, - languages=[lang]) - except Exception, e: - logging.error("Cannot load translation for '%s': %s", lang, str(e)) - continue - _supported_locales = frozenset(_translations.keys() + [_default_locale]) - _use_gettext = True - logging.info("Supported locales: %s", sorted(_supported_locales)) - - -def get_supported_locales(cls): - """Returns a list of all the supported locale codes.""" - return _supported_locales - - -class Locale(object): - @classmethod - def get_closest(cls, *locale_codes): - """Returns the closest match for the given locale code.""" - for code in locale_codes: - if not code: continue - code = code.replace("-", "_") - parts = code.split("_") - if len(parts) > 2: - continue - elif len(parts) == 2: - code = parts[0].lower() + "_" + parts[1].upper() - if code in _supported_locales: - return cls.get(code) - if parts[0].lower() in _supported_locales: - return cls.get(parts[0].lower()) - return cls.get(_default_locale) - - @classmethod - def get(cls, code): - """Returns the Locale for the given locale code. - - If it is not supported, we raise an exception. - """ - if not hasattr(cls, "_cache"): - cls._cache = {} - if code not in cls._cache: - assert code in _supported_locales - translations = _translations.get(code, None) - if translations is None: - locale = CSVLocale(code, {}) - elif _use_gettext: - locale = GettextLocale(code, translations) - else: - locale = CSVLocale(code, translations) - cls._cache[code] = locale - return cls._cache[code] - - def __init__(self, code, translations): - self.code = code - self.name = LOCALE_NAMES.get(code, {}).get("name", u"Unknown") - self.rtl = False - for prefix in ["fa", "ar", "he"]: - if self.code.startswith(prefix): - self.rtl = True - break - self.translations = translations - - # Initialize strings for date formatting - _ = self.translate - self._months = [ - _("January"), _("February"), _("March"), _("April"), - _("May"), _("June"), _("July"), _("August"), - _("September"), _("October"), _("November"), _("December")] - self._weekdays = [ - _("Monday"), _("Tuesday"), _("Wednesday"), _("Thursday"), - _("Friday"), _("Saturday"), _("Sunday")] - - def translate(self, message, plural_message=None, count=None): - raise NotImplementedError() - - def format_date(self, date, gmt_offset=0, relative=True, shorter=False, - full_format=False): - """Formats the given date (which should be GMT). - - By default, we return a relative time (e.g., "2 minutes ago"). You - can return an absolute date string with relative=False. - - You can force a full format date ("July 10, 1980") with - full_format=True. - """ - if self.code.startswith("ru"): - relative = False - if type(date) in (int, long, float): - date = datetime.datetime.utcfromtimestamp(date) - now = datetime.datetime.utcnow() - # Round down to now. Due to click skew, things are somethings - # slightly in the future. - if date > now: date = now - local_date = date - datetime.timedelta(minutes=gmt_offset) - local_now = now - datetime.timedelta(minutes=gmt_offset) - local_yesterday = local_now - datetime.timedelta(hours=24) - difference = now - date - seconds = difference.seconds - days = difference.days - - _ = self.translate - format = None - if not full_format: - if relative and days == 0: - if seconds < 50: - return _("1 second ago", "%(seconds)d seconds ago", - seconds) % { "seconds": seconds } - - if seconds < 50 * 60: - minutes = round(seconds / 60.0) - return _("1 minute ago", "%(minutes)d minutes ago", - minutes) % { "minutes": minutes } - - hours = round(seconds / (60.0 * 60)) - return _("1 hour ago", "%(hours)d hours ago", - hours) % { "hours": hours } - - if days == 0: - format = _("%(time)s") - elif days == 1 and local_date.day == local_yesterday.day and \ - relative: - format = _("yesterday") if shorter else \ - _("yesterday at %(time)s") - elif days < 5: - format = _("%(weekday)s") if shorter else \ - _("%(weekday)s at %(time)s") - elif days < 334: # 11mo, since confusing for same month last year - format = _("%(month_name)s %(day)s") if shorter else \ - _("%(month_name)s %(day)s at %(time)s") - - if format is None: - format = _("%(month_name)s %(day)s, %(year)s") if shorter else \ - _("%(month_name)s %(day)s, %(year)s at %(time)s") - - tfhour_clock = self.code not in ("en", "en_US", "zh_CN") - if tfhour_clock: - str_time = "%d:%02d" % (local_date.hour, local_date.minute) - elif self.code == "zh_CN": - str_time = "%s%d:%02d" % ( - (u'\u4e0a\u5348', u'\u4e0b\u5348')[local_date.hour >= 12], - local_date.hour % 12 or 12, local_date.minute) - else: - str_time = "%d:%02d %s" % ( - local_date.hour % 12 or 12, local_date.minute, - ("am", "pm")[local_date.hour >= 12]) - - return format % { - "month_name": self._months[local_date.month - 1], - "weekday": self._weekdays[local_date.weekday()], - "day": str(local_date.day), - "year": str(local_date.year), - "time": str_time - } - - def format_day(self, date, gmt_offset=0, dow=True): - """Formats the given date as a day of week. - - Example: "Monday, January 22". You can remove the day of week with - dow=False. - """ - local_date = date - datetime.timedelta(minutes=gmt_offset) - _ = self.translate - if dow: - return _("%(weekday)s, %(month_name)s %(day)s") % { - "month_name": self._months[local_date.month - 1], - "weekday": self._weekdays[local_date.weekday()], - "day": str(local_date.day), - } - else: - return _("%(month_name)s %(day)s") % { - "month_name": self._months[local_date.month - 1], - "day": str(local_date.day), - } - - def list(self, parts): - """Returns a comma-separated list for the given list of parts. - - The format is, e.g., "A, B and C", "A and B" or just "A" for lists - of size 1. - """ - _ = self.translate - if len(parts) == 0: return "" - if len(parts) == 1: return parts[0] - comma = u' \u0648 ' if self.code.startswith("fa") else u", " - return _("%(commas)s and %(last)s") % { - "commas": comma.join(parts[:-1]), - "last": parts[len(parts) - 1], - } - - def friendly_number(self, value): - """Returns a comma-separated number for the given integer.""" - if self.code not in ("en", "en_US"): - return str(value) - value = str(value) - parts = [] - while value: - parts.append(value[-3:]) - value = value[:-3] - return ",".join(reversed(parts)) - -class CSVLocale(Locale): - """Locale implementation using tornado's CSV translation format.""" - def translate(self, message, plural_message=None, count=None): - """Returns the translation for the given message for this locale. - - If plural_message is given, you must also provide count. We return - plural_message when count != 1, and we return the singular form - for the given message when count == 1. - """ - if plural_message is not None: - assert count is not None - if count != 1: - message = plural_message - message_dict = self.translations.get("plural", {}) - else: - message_dict = self.translations.get("singular", {}) - else: - message_dict = self.translations.get("unknown", {}) - return message_dict.get(message, message) - -class GettextLocale(Locale): - """Locale implementation using the gettext module.""" - def translate(self, message, plural_message=None, count=None): - if plural_message is not None: - assert count is not None - return self.translations.ungettext(message, plural_message, count) - else: - return self.translations.ugettext(message) - -LOCALE_NAMES = { - "af_ZA": {"name_en": u"Afrikaans", "name": u"Afrikaans"}, - "ar_AR": {"name_en": u"Arabic", "name": u"\u0627\u0644\u0639\u0631\u0628\u064a\u0629"}, - "bg_BG": {"name_en": u"Bulgarian", "name": u"\u0411\u044a\u043b\u0433\u0430\u0440\u0441\u043a\u0438"}, - "bn_IN": {"name_en": u"Bengali", "name": u"\u09ac\u09be\u0982\u09b2\u09be"}, - "bs_BA": {"name_en": u"Bosnian", "name": u"Bosanski"}, - "ca_ES": {"name_en": u"Catalan", "name": u"Catal\xe0"}, - "cs_CZ": {"name_en": u"Czech", "name": u"\u010ce\u0161tina"}, - "cy_GB": {"name_en": u"Welsh", "name": u"Cymraeg"}, - "da_DK": {"name_en": u"Danish", "name": u"Dansk"}, - "de_DE": {"name_en": u"German", "name": u"Deutsch"}, - "el_GR": {"name_en": u"Greek", "name": u"\u0395\u03bb\u03bb\u03b7\u03bd\u03b9\u03ba\u03ac"}, - "en_GB": {"name_en": u"English (UK)", "name": u"English (UK)"}, - "en_US": {"name_en": u"English (US)", "name": u"English (US)"}, - "es_ES": {"name_en": u"Spanish (Spain)", "name": u"Espa\xf1ol (Espa\xf1a)"}, - "es_LA": {"name_en": u"Spanish", "name": u"Espa\xf1ol"}, - "et_EE": {"name_en": u"Estonian", "name": u"Eesti"}, - "eu_ES": {"name_en": u"Basque", "name": u"Euskara"}, - "fa_IR": {"name_en": u"Persian", "name": u"\u0641\u0627\u0631\u0633\u06cc"}, - "fi_FI": {"name_en": u"Finnish", "name": u"Suomi"}, - "fr_CA": {"name_en": u"French (Canada)", "name": u"Fran\xe7ais (Canada)"}, - "fr_FR": {"name_en": u"French", "name": u"Fran\xe7ais"}, - "ga_IE": {"name_en": u"Irish", "name": u"Gaeilge"}, - "gl_ES": {"name_en": u"Galician", "name": u"Galego"}, - "he_IL": {"name_en": u"Hebrew", "name": u"\u05e2\u05d1\u05e8\u05d9\u05ea"}, - "hi_IN": {"name_en": u"Hindi", "name": u"\u0939\u093f\u0928\u094d\u0926\u0940"}, - "hr_HR": {"name_en": u"Croatian", "name": u"Hrvatski"}, - "hu_HU": {"name_en": u"Hungarian", "name": u"Magyar"}, - "id_ID": {"name_en": u"Indonesian", "name": u"Bahasa Indonesia"}, - "is_IS": {"name_en": u"Icelandic", "name": u"\xcdslenska"}, - "it_IT": {"name_en": u"Italian", "name": u"Italiano"}, - "ja_JP": {"name_en": u"Japanese", "name": u"\xe6\xe6\xe8"}, - "ko_KR": {"name_en": u"Korean", "name": u"\xed\xea\xec"}, - "lt_LT": {"name_en": u"Lithuanian", "name": u"Lietuvi\u0173"}, - "lv_LV": {"name_en": u"Latvian", "name": u"Latvie\u0161u"}, - "mk_MK": {"name_en": u"Macedonian", "name": u"\u041c\u0430\u043a\u0435\u0434\u043e\u043d\u0441\u043a\u0438"}, - "ml_IN": {"name_en": u"Malayalam", "name": u"\u0d2e\u0d32\u0d2f\u0d3e\u0d33\u0d02"}, - "ms_MY": {"name_en": u"Malay", "name": u"Bahasa Melayu"}, - "nb_NO": {"name_en": u"Norwegian (bokmal)", "name": u"Norsk (bokm\xe5l)"}, - "nl_NL": {"name_en": u"Dutch", "name": u"Nederlands"}, - "nn_NO": {"name_en": u"Norwegian (nynorsk)", "name": u"Norsk (nynorsk)"}, - "pa_IN": {"name_en": u"Punjabi", "name": u"\u0a2a\u0a70\u0a1c\u0a3e\u0a2c\u0a40"}, - "pl_PL": {"name_en": u"Polish", "name": u"Polski"}, - "pt_BR": {"name_en": u"Portuguese (Brazil)", "name": u"Portugu\xeas (Brasil)"}, - "pt_PT": {"name_en": u"Portuguese (Portugal)", "name": u"Portugu\xeas (Portugal)"}, - "ro_RO": {"name_en": u"Romanian", "name": u"Rom\xe2n\u0103"}, - "ru_RU": {"name_en": u"Russian", "name": u"\u0420\u0443\u0441\u0441\u043a\u0438\u0439"}, - "sk_SK": {"name_en": u"Slovak", "name": u"Sloven\u010dina"}, - "sl_SI": {"name_en": u"Slovenian", "name": u"Sloven\u0161\u010dina"}, - "sq_AL": {"name_en": u"Albanian", "name": u"Shqip"}, - "sr_RS": {"name_en": u"Serbian", "name": u"\u0421\u0440\u043f\u0441\u043a\u0438"}, - "sv_SE": {"name_en": u"Swedish", "name": u"Svenska"}, - "sw_KE": {"name_en": u"Swahili", "name": u"Kiswahili"}, - "ta_IN": {"name_en": u"Tamil", "name": u"\u0ba4\u0bae\u0bbf\u0bb4\u0bcd"}, - "te_IN": {"name_en": u"Telugu", "name": u"\u0c24\u0c46\u0c32\u0c41\u0c17\u0c41"}, - "th_TH": {"name_en": u"Thai", "name": u"\u0e20\u0e32\u0e29\u0e32\u0e44\u0e17\u0e22"}, - "tl_PH": {"name_en": u"Filipino", "name": u"Filipino"}, - "tr_TR": {"name_en": u"Turkish", "name": u"T\xfcrk\xe7e"}, - "uk_UA": {"name_en": u"Ukraini ", "name": u"\u0423\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0430"}, - "vi_VN": {"name_en": u"Vietnamese", "name": u"Ti\u1ebfng Vi\u1ec7t"}, - "zh_CN": {"name_en": u"Chinese (Simplified)", "name": u"\xe4\xe6(\xe7\xe4)"}, - "zh_HK": {"name_en": u"Chinese (Hong Kong)", "name": u"\xe4\xe6(\xe9\xe6)"}, - "zh_TW": {"name_en": u"Chinese (Taiwan)", "name": u"\xe4\xe6(\xe5\xe7)"}, -} diff --git a/lib/tornado/options.py b/lib/tornado/options.py deleted file mode 100644 index a0bb1a7..0000000 --- a/lib/tornado/options.py +++ /dev/null @@ -1,386 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""A command line parsing module that lets modules define their own options. - -Each module defines its own options, e.g., - - from tornado.options import define, options - - define("mysql_host", default="127.0.0.1:3306", help="Main user DB") - define("memcache_hosts", default="127.0.0.1:11011", multiple=True, - help="Main user memcache servers") - - def connect(): - db = database.Connection(options.mysql_host) - ... - -The main() method of your application does not need to be aware of all of -the options used throughout your program; they are all automatically loaded -when the modules are loaded. Your main() method can parse the command line -or parse a config file with: - - import tornado.options - tornado.options.parse_config_file("/etc/server.conf") - tornado.options.parse_command_line() - -Command line formats are what you would expect ("--myoption=myvalue"). -Config files are just Python files. Global names become options, e.g., - - myoption = "myvalue" - myotheroption = "myothervalue" - -We support datetimes, timedeltas, ints, and floats (just pass a 'type' -kwarg to define). We also accept multi-value options. See the documentation -for define() below. -""" - -import datetime -import logging -import logging.handlers -import re -import sys -import time - -# For pretty log messages, if available -try: - import curses -except: - curses = None - - -def define(name, default=None, type=str, help=None, metavar=None, - multiple=False): - """Defines a new command line option. - - If type is given (one of str, float, int, datetime, or timedelta), - we parse the command line arguments based on the given type. If - multiple is True, we accept comma-separated values, and the option - value is always a list. - - For multi-value integers, we also accept the syntax x:y, which - turns into range(x, y) - very useful for long integer ranges. - - help and metavar are used to construct the automatically generated - command line help string. The help message is formatted like: - - --name=METAVAR help string - - Command line option names must be unique globally. They can be parsed - from the command line with parse_command_line() or parsed from a - config file with parse_config_file. - """ - if name in options: - raise Error("Option %r already defined in %s", name, - options[name].file_name) - frame = sys._getframe(0) - options_file = frame.f_code.co_filename - file_name = frame.f_back.f_code.co_filename - if file_name == options_file: file_name = "" - options[name] = _Option(name, file_name=file_name, default=default, - type=type, help=help, metavar=metavar, - multiple=multiple) - - -def parse_command_line(args=None): - """Parses all options given on the command line. - - We return all command line arguments that are not options as a list. - """ - if args is None: args = sys.argv - remaining = [] - for i in xrange(1, len(args)): - # All things after the last option are command line arguments - if not args[i].startswith("-"): - remaining = args[i:] - break - if args[i] == "--": - remaining = args[i+1:] - break - arg = args[i].lstrip("-") - name, equals, value = arg.partition("=") - name = name.replace('-', '_') - if not name in options: - print_help() - raise Error('Unrecognized command line option: %r' % name) - option = options[name] - if not equals: - if option.type == bool: - value = "true" - else: - raise Error('Option %r requires a value' % name) - option.parse(value) - if options.help: - print_help() - sys.exit(0) - - # Set up log level and pretty console logging by default - if options.logging != 'none': - logging.getLogger().setLevel(getattr(logging, options.logging.upper())) - enable_pretty_logging() - - return remaining - - -def parse_config_file(path): - """Parses and loads the Python config file at the given path.""" - config = {} - execfile(path, config, config) - for name in config: - if name in options: - options[name].set(config[name]) - - -def print_help(file=sys.stdout): - """Prints all the command line options to stdout.""" - print >> file, "Usage: %s [OPTIONS]" % sys.argv[0] - print >> file, "" - print >> file, "Options:" - by_file = {} - for option in options.itervalues(): - by_file.setdefault(option.file_name, []).append(option) - - for filename, o in sorted(by_file.items()): - if filename: print >> file, filename - o.sort(key=lambda option: option.name) - for option in o: - prefix = option.name - if option.metavar: - prefix += "=" + option.metavar - print >> file, " --%-30s %s" % (prefix, option.help or "") - print >> file - - -class _Options(dict): - """Our global program options, an dictionary with object-like access.""" - @classmethod - def instance(cls): - if not hasattr(cls, "_instance"): - cls._instance = cls() - return cls._instance - - def __getattr__(self, name): - if isinstance(self.get(name), _Option): - return self[name].value() - raise AttributeError("Unrecognized option %r" % name) - - -class _Option(object): - def __init__(self, name, default=None, type=str, help=None, metavar=None, - multiple=False, file_name=None): - if default is None and multiple: - default = [] - self.name = name - self.type = type - self.help = help - self.metavar = metavar - self.multiple = multiple - self.file_name = file_name - self.default = default - self._value = None - - def value(self): - return self.default if self._value is None else self._value - - def parse(self, value): - _parse = { - datetime.datetime: self._parse_datetime, - datetime.timedelta: self._parse_timedelta, - bool: self._parse_bool, - str: self._parse_string, - }.get(self.type, self.type) - if self.multiple: - if self._value is None: - self._value = [] - for part in value.split(","): - if self.type in (int, long): - # allow ranges of the form X:Y (inclusive at both ends) - lo, _, hi = part.partition(":") - lo = _parse(lo) - hi = _parse(hi) if hi else lo - self._value.extend(range(lo, hi+1)) - else: - self._value.append(_parse(part)) - else: - self._value = _parse(value) - return self.value() - - def set(self, value): - if self.multiple: - if not isinstance(value, list): - raise Error("Option %r is required to be a list of %s" % - (self.name, self.type.__name__)) - for item in value: - if item != None and not isinstance(item, self.type): - raise Error("Option %r is required to be a list of %s" % - (self.name, self.type.__name__)) - else: - if value != None and not isinstance(value, self.type): - raise Error("Option %r is required to be a %s" % - (self.name, self.type.__name__)) - self._value = value - - # Supported date/time formats in our options - _DATETIME_FORMATS = [ - "%a %b %d %H:%M:%S %Y", - "%Y-%m-%d %H:%M:%S", - "%Y-%m-%d %H:%M", - "%Y-%m-%dT%H:%M", - "%Y%m%d %H:%M:%S", - "%Y%m%d %H:%M", - "%Y-%m-%d", - "%Y%m%d", - "%H:%M:%S", - "%H:%M", - ] - - def _parse_datetime(self, value): - for format in self._DATETIME_FORMATS: - try: - return datetime.datetime.strptime(value, format) - except ValueError: - pass - raise Error('Unrecognized date/time format: %r' % value) - - _TIMEDELTA_ABBREVS = [ - ('hours', ['h']), - ('minutes', ['m', 'min']), - ('seconds', ['s', 'sec']), - ('milliseconds', ['ms']), - ('microseconds', ['us']), - ('days', ['d']), - ('weeks', ['w']), - ] - - _TIMEDELTA_ABBREV_DICT = dict( - (abbrev, full) for full, abbrevs in _TIMEDELTA_ABBREVS - for abbrev in abbrevs) - - _FLOAT_PATTERN = r'[-+]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][-+]?\d+)?' - - _TIMEDELTA_PATTERN = re.compile( - r'\s*(%s)\s*(\w*)\s*' % _FLOAT_PATTERN, re.IGNORECASE) - - def _parse_timedelta(self, value): - try: - sum = datetime.timedelta() - start = 0 - while start < len(value): - m = self._TIMEDELTA_PATTERN.match(value, start) - if not m: - raise Exception() - num = float(m.group(1)) - units = m.group(2) or 'seconds' - units = self._TIMEDELTA_ABBREV_DICT.get(units, units) - sum += datetime.timedelta(**{units: num}) - start = m.end() - return sum - except: - raise - - def _parse_bool(self, value): - return value.lower() not in ("false", "0", "f") - - def _parse_string(self, value): - return value.decode("utf-8") - - -class Error(Exception): - pass - - -def enable_pretty_logging(): - """Turns on formatted logging output as configured.""" - if (options.log_to_stderr or - (options.log_to_stderr is None and not options.log_file_prefix)): - # Set up color if we are in a tty and curses is installed - color = False - if curses and sys.stderr.isatty(): - try: - curses.setupterm() - if curses.tigetnum("colors") > 0: - color = True - except: - pass - channel = logging.StreamHandler() - channel.setFormatter(_LogFormatter(color=color)) - logging.getLogger().addHandler(channel) - - if options.log_file_prefix: - channel = logging.handlers.RotatingFileHandler( - filename=options.log_file_prefix, - maxBytes=options.log_file_max_size, - backupCount=options.log_file_num_backups) - channel.setFormatter(_LogFormatter(color=False)) - logging.getLogger().addHandler(channel) - - -class _LogFormatter(logging.Formatter): - def __init__(self, color, *args, **kwargs): - logging.Formatter.__init__(self, *args, **kwargs) - self._color = color - if color: - fg_color = curses.tigetstr("setaf") or curses.tigetstr("setf") or "" - self._colors = { - logging.DEBUG: curses.tparm(fg_color, 4), # Blue - logging.INFO: curses.tparm(fg_color, 2), # Green - logging.WARNING: curses.tparm(fg_color, 3), # Yellow - logging.ERROR: curses.tparm(fg_color, 1), # Red - } - self._normal = curses.tigetstr("sgr0") - - def format(self, record): - try: - record.message = record.getMessage() - except Exception, e: - record.message = "Bad message (%r): %r" % (e, record.__dict__) - record.asctime = time.strftime( - "%y%m%d %H:%M:%S", self.converter(record.created)) - prefix = '[%(levelname)1.1s %(asctime)s %(module)s:%(lineno)d]' % \ - record.__dict__ - if self._color: - prefix = (self._colors.get(record.levelno, self._normal) + - prefix + self._normal) - formatted = prefix + " " + record.message - if record.exc_info: - if not record.exc_text: - record.exc_text = self.formatException(record.exc_info) - if record.exc_text: - formatted = formatted.rstrip() + "\n" + record.exc_text - return formatted.replace("\n", "\n ") - - -options = _Options.instance() - - -# Default options -define("help", type=bool, help="show this help information") -define("logging", default="info", - help=("Set the Python log level. If 'none', tornado won't touch the " - "logging configuration."), - metavar="info|warning|error|none") -define("log_to_stderr", type=bool, default=None, - help=("Send log output to stderr (colorized if possible). " - "By default use stderr if --log_file_prefix is not set.")) -define("log_file_prefix", type=str, default=None, metavar="PATH", - help=("Path prefix for log files. " - "Note that if you are running multiple tornado processes, " - "log_file_prefix must be different for each of them (e.g. " - "include the port number)")) -define("log_file_max_size", type=int, default=100 * 1000 * 1000, - help="max size of log files before rollover") -define("log_file_num_backups", type=int, default=10, - help="number of log files to keep") diff --git a/lib/tornado/s3server.py b/lib/tornado/s3server.py deleted file mode 100644 index 2e8a97d..0000000 --- a/lib/tornado/s3server.py +++ /dev/null @@ -1,255 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""Implementation of an S3-like storage server based on local files. - -Useful to test features that will eventually run on S3, or if you want to -run something locally that was once running on S3. - -We don't support all the features of S3, but it does work with the -standard S3 client for the most basic semantics. To use the standard -S3 client with this module: - - c = S3.AWSAuthConnection("", "", server="localhost", port=8888, - is_secure=False) - c.create_bucket("mybucket") - c.put("mybucket", "mykey", "a value") - print c.get("mybucket", "mykey").body - -""" - -import bisect -import datetime -import escape -import hashlib -import httpserver -import ioloop -import os -import os.path -import urllib -import web - - -def start(port, root_directory="/tmp/s3", bucket_depth=0): - """Starts the mock S3 server on the given port at the given path.""" - application = S3Application(root_directory, bucket_depth) - http_server = httpserver.HTTPServer(application) - http_server.listen(port) - ioloop.IOLoop.instance().start() - - -class S3Application(web.Application): - """Implementation of an S3-like storage server based on local files. - - If bucket depth is given, we break files up into multiple directories - to prevent hitting file system limits for number of files in each - directories. 1 means one level of directories, 2 means 2, etc. - """ - def __init__(self, root_directory, bucket_depth=0): - web.Application.__init__(self, [ - (r"/", RootHandler), - (r"/([^/]+)/(.+)", ObjectHandler), - (r"/([^/]+)/", BucketHandler), - ]) - self.directory = os.path.abspath(root_directory) - if not os.path.exists(self.directory): - os.makedirs(self.directory) - self.bucket_depth = bucket_depth - - -class BaseRequestHandler(web.RequestHandler): - SUPPORTED_METHODS = ("PUT", "GET", "DELETE") - - def render_xml(self, value): - assert isinstance(value, dict) and len(value) == 1 - self.set_header("Content-Type", "application/xml; charset=UTF-8") - name = value.keys()[0] - parts = [] - parts.append('<' + escape.utf8(name) + - ' xmlns="http://doc.s3.amazonaws.com/2006-03-01">') - self._render_parts(value.values()[0], parts) - parts.append('') - self.finish('\n' + - ''.join(parts)) - - def _render_parts(self, value, parts=[]): - if isinstance(value, basestring): - parts.append(escape.xhtml_escape(value)) - elif isinstance(value, int) or isinstance(value, long): - parts.append(str(value)) - elif isinstance(value, datetime.datetime): - parts.append(value.strftime("%Y-%m-%dT%H:%M:%S.000Z")) - elif isinstance(value, dict): - for name, subvalue in value.iteritems(): - if not isinstance(subvalue, list): - subvalue = [subvalue] - for subsubvalue in subvalue: - parts.append('<' + escape.utf8(name) + '>') - self._render_parts(subsubvalue, parts) - parts.append('') - else: - raise Exception("Unknown S3 value type %r", value) - - def _object_path(self, bucket, object_name): - if self.application.bucket_depth < 1: - return os.path.abspath(os.path.join( - self.application.directory, bucket, object_name)) - hash = hashlib.md5(object_name).hexdigest() - path = os.path.abspath(os.path.join( - self.application.directory, bucket)) - for i in range(self.application.bucket_depth): - path = os.path.join(path, hash[:2 * (i + 1)]) - return os.path.join(path, object_name) - - -class RootHandler(BaseRequestHandler): - def get(self): - names = os.listdir(self.application.directory) - buckets = [] - for name in names: - path = os.path.join(self.application.directory, name) - info = os.stat(path) - buckets.append({ - "Name": name, - "CreationDate": datetime.datetime.utcfromtimestamp( - info.st_ctime), - }) - self.render_xml({"ListAllMyBucketsResult": { - "Buckets": {"Bucket": buckets}, - }}) - - -class BucketHandler(BaseRequestHandler): - def get(self, bucket_name): - prefix = self.get_argument("prefix", u"") - marker = self.get_argument("marker", u"") - max_keys = int(self.get_argument("max-keys", 50000)) - path = os.path.abspath(os.path.join(self.application.directory, - bucket_name)) - terse = int(self.get_argument("terse", 0)) - if not path.startswith(self.application.directory) or \ - not os.path.isdir(path): - raise web.HTTPError(404) - object_names = [] - for root, dirs, files in os.walk(path): - for file_name in files: - object_names.append(os.path.join(root, file_name)) - skip = len(path) + 1 - for i in range(self.application.bucket_depth): - skip += 2 * (i + 1) + 1 - object_names = [n[skip:] for n in object_names] - object_names.sort() - contents = [] - - start_pos = 0 - if marker: - start_pos = bisect.bisect_right(object_names, marker, start_pos) - if prefix: - start_pos = bisect.bisect_left(object_names, prefix, start_pos) - - truncated = False - for object_name in object_names[start_pos:]: - if not object_name.startswith(prefix): - break - if len(contents) >= max_keys: - truncated = True - break - object_path = self._object_path(bucket_name, object_name) - c = {"Key": object_name} - if not terse: - info = os.stat(object_path) - c.update({ - "LastModified": datetime.datetime.utcfromtimestamp( - info.st_mtime), - "Size": info.st_size, - }) - contents.append(c) - marker = object_name - self.render_xml({"ListBucketResult": { - "Name": bucket_name, - "Prefix": prefix, - "Marker": marker, - "MaxKeys": max_keys, - "IsTruncated": truncated, - "Contents": contents, - }}) - - def put(self, bucket_name): - path = os.path.abspath(os.path.join( - self.application.directory, bucket_name)) - if not path.startswith(self.application.directory) or \ - os.path.exists(path): - raise web.HTTPError(403) - os.makedirs(path) - self.finish() - - def delete(self, bucket_name): - path = os.path.abspath(os.path.join( - self.application.directory, bucket_name)) - if not path.startswith(self.application.directory) or \ - not os.path.isdir(path): - raise web.HTTPError(404) - if len(os.listdir(path)) > 0: - raise web.HTTPError(403) - os.rmdir(path) - self.set_status(204) - self.finish() - - -class ObjectHandler(BaseRequestHandler): - def get(self, bucket, object_name): - object_name = urllib.unquote(object_name) - path = self._object_path(bucket, object_name) - if not path.startswith(self.application.directory) or \ - not os.path.isfile(path): - raise web.HTTPError(404) - info = os.stat(path) - self.set_header("Content-Type", "application/unknown") - self.set_header("Last-Modified", datetime.datetime.utcfromtimestamp( - info.st_mtime)) - object_file = open(path, "r") - try: - self.finish(object_file.read()) - finally: - object_file.close() - - def put(self, bucket, object_name): - object_name = urllib.unquote(object_name) - bucket_dir = os.path.abspath(os.path.join( - self.application.directory, bucket)) - if not bucket_dir.startswith(self.application.directory) or \ - not os.path.isdir(bucket_dir): - raise web.HTTPError(404) - path = self._object_path(bucket, object_name) - if not path.startswith(bucket_dir) or os.path.isdir(path): - raise web.HTTPError(403) - directory = os.path.dirname(path) - if not os.path.exists(directory): - os.makedirs(directory) - object_file = open(path, "w") - object_file.write(self.request.body) - object_file.close() - self.finish() - - def delete(self, bucket, object_name): - object_name = urllib.unquote(object_name) - path = self._object_path(bucket, object_name) - if not path.startswith(self.application.directory) or \ - not os.path.isfile(path): - raise web.HTTPError(404) - os.unlink(path) - self.set_status(204) - self.finish() diff --git a/lib/tornado/template.py b/lib/tornado/template.py deleted file mode 100644 index b807cc6..0000000 --- a/lib/tornado/template.py +++ /dev/null @@ -1,574 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2009 Facebook -# -# Licensed under the Apache License, Version 2.0 (the "License"); you may -# not use this file except in compliance with the License. You may obtain -# a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -# License for the specific language governing permissions and limitations -# under the License. - -"""A simple template system that compiles templates to Python code. - -Basic usage looks like: - - t = template.Template("{{ myvalue }}") - print t.generate(myvalue="XXX") - -Loader is a class that loads templates from a root directory and caches -the compiled templates: - - loader = template.Loader("/home/btaylor") - print loader.load("test.html").generate(myvalue="XXX") - -We compile all templates to raw Python. Error-reporting is currently... uh, -interesting. Syntax for the templates - - ### base.html - - - {% block title %}Default title{% end %} - - -
    - {% for student in students %} - {% block student %} -
  • {{ escape(student.name) }}
  • - {% end %} - {% end %} -
- - - - ### bold.html - {% extends "base.html" %} - - {% block title %}A bolder title{% end %} - - {% block student %} -
  • {{ escape(student.name) }}
  • - {% block %} - -Unlike most other template systems, we do not put any restrictions on the -expressions you can include in your statements. if and for blocks get -translated exactly into Python, do you can do complex expressions like: - - {% for student in [p for p in people if p.student and p.age > 23] %} -
  • {{ escape(student.name) }}
  • - {% end %} - -Translating directly to Python means you can apply functions to expressions -easily, like the escape() function in the examples above. You can pass -functions in to your template just like any other variable: - - ### Python code - def add(x, y): - return x + y - template.execute(add=add) - - ### The template - {{ add(1, 2) }} - -We provide the functions escape(), url_escape(), json_encode(), and squeeze() -to all templates by default. -""" - -from __future__ import with_statement - -import cStringIO -import datetime -import escape -import logging -import os.path -import re - -class Template(object): - """A compiled template. - - We compile into Python from the given template_string. You can generate - the template from variables with generate(). - """ - def __init__(self, template_string, name="", loader=None, - compress_whitespace=None): - self.name = name - if compress_whitespace is None: - compress_whitespace = name.endswith(".html") or \ - name.endswith(".js") - reader = _TemplateReader(name, template_string) - self.file = _File(_parse(reader)) - self.code = self._generate_python(loader, compress_whitespace) - try: - self.compiled = compile(self.code, self.name, "exec") - except: - formatted_code = _format_code(self.code).rstrip() - logging.error("%s code:\n%s", self.name, formatted_code) - raise - - def generate(self, **kwargs): - """Generate this template with the given arguments.""" - namespace = { - "escape": escape.xhtml_escape, - "url_escape": escape.url_escape, - "json_encode": escape.json_encode, - "squeeze": escape.squeeze, - "datetime": datetime, - } - namespace.update(kwargs) - exec self.compiled in namespace - execute = namespace["_execute"] - try: - return execute() - except: - formatted_code = _format_code(self.code).rstrip() - logging.error("%s code:\n%s", self.name, formatted_code) - raise - - def _generate_python(self, loader, compress_whitespace): - buffer = cStringIO.StringIO() - try: - named_blocks = {} - ancestors = self._get_ancestors(loader) - ancestors.reverse() - for ancestor in ancestors: - ancestor.find_named_blocks(loader, named_blocks) - self.file.find_named_blocks(loader, named_blocks) - writer = _CodeWriter(buffer, named_blocks, loader, self, - compress_whitespace) - ancestors[0].generate(writer) - return buffer.getvalue() - finally: - buffer.close() - - def _get_ancestors(self, loader): - ancestors = [self.file] - for chunk in self.file.body.chunks: - if isinstance(chunk, _ExtendsBlock): - if not loader: - raise ParseError("{% extends %} block found, but no " - "template loader") - template = loader.load(chunk.name, self.name) - ancestors.extend(template._get_ancestors(loader)) - return ancestors - - -class Loader(object): - """A template loader that loads from a single root directory. - - You must use a template loader to use template constructs like - {% extends %} and {% include %}. Loader caches all templates after - they are loaded the first time. - """ - def __init__(self, root_directory): - self.root = os.path.abspath(root_directory) - self.templates = {} - - def reset(self): - self.templates = {} - - def resolve_path(self, name, parent_path=None): - if parent_path and not parent_path.startswith("<") and \ - not parent_path.startswith("/") and \ - not name.startswith("/"): - current_path = os.path.join(self.root, parent_path) - file_dir = os.path.dirname(os.path.abspath(current_path)) - relative_path = os.path.abspath(os.path.join(file_dir, name)) - if relative_path.startswith(self.root): - name = relative_path[len(self.root) + 1:] - return name - - def load(self, name, parent_path=None): - name = self.resolve_path(name, parent_path=parent_path) - if name not in self.templates: - path = os.path.join(self.root, name) - f = open(path, "r") - self.templates[name] = Template(f.read(), name=name, loader=self) - f.close() - return self.templates[name] - - -class _Node(object): - def each_child(self): - return () - - def generate(self, writer): - raise NotImplementedError() - - def find_named_blocks(self, loader, named_blocks): - for child in self.each_child(): - child.find_named_blocks(loader, named_blocks) - - -class _File(_Node): - def __init__(self, body): - self.body = body - - def generate(self, writer): - writer.write_line("def _execute():") - with writer.indent(): - writer.write_line("_buffer = []") - self.body.generate(writer) - writer.write_line("return ''.join(_buffer)") - - def each_child(self): - return (self.body,) - - - -class _ChunkList(_Node): - def __init__(self, chunks): - self.chunks = chunks - - def generate(self, writer): - for chunk in self.chunks: - chunk.generate(writer) - - def each_child(self): - return self.chunks - - -class _NamedBlock(_Node): - def __init__(self, name, body=None): - self.name = name - self.body = body - - def each_child(self): - return (self.body,) - - def generate(self, writer): - writer.named_blocks[self.name].generate(writer) - - def find_named_blocks(self, loader, named_blocks): - named_blocks[self.name] = self.body - _Node.find_named_blocks(self, loader, named_blocks) - - -class _ExtendsBlock(_Node): - def __init__(self, name): - self.name = name - - -class _IncludeBlock(_Node): - def __init__(self, name, reader): - self.name = name - self.template_name = reader.name - - def find_named_blocks(self, loader, named_blocks): - included = loader.load(self.name, self.template_name) - included.file.find_named_blocks(loader, named_blocks) - - def generate(self, writer): - included = writer.loader.load(self.name, self.template_name) - old = writer.current_template - writer.current_template = included - included.file.body.generate(writer) - writer.current_template = old - - -class _ApplyBlock(_Node): - def __init__(self, method, body=None): - self.method = method - self.body = body - - def each_child(self): - return (self.body,) - - def generate(self, writer): - method_name = "apply%d" % writer.apply_counter - writer.apply_counter += 1 - writer.write_line("def %s():" % method_name) - with writer.indent(): - writer.write_line("_buffer = []") - self.body.generate(writer) - writer.write_line("return ''.join(_buffer)") - writer.write_line("_buffer.append(%s(%s()))" % ( - self.method, method_name)) - - -class _ControlBlock(_Node): - def __init__(self, statement, body=None): - self.statement = statement - self.body = body - - def each_child(self): - return (self.body,) - - def generate(self, writer): - writer.write_line("%s:" % self.statement) - with writer.indent(): - self.body.generate(writer) - - -class _IntermediateControlBlock(_Node): - def __init__(self, statement): - self.statement = statement - - def generate(self, writer): - writer.write_line("%s:" % self.statement, writer.indent_size() - 1) - - -class _Statement(_Node): - def __init__(self, statement): - self.statement = statement - - def generate(self, writer): - writer.write_line(self.statement) - - -class _Expression(_Node): - def __init__(self, expression): - self.expression = expression - - def generate(self, writer): - writer.write_line("_tmp = %s" % self.expression) - writer.write_line("if isinstance(_tmp, str): _buffer.append(_tmp)") - writer.write_line("elif isinstance(_tmp, unicode): " - "_buffer.append(_tmp.encode('utf-8'))") - writer.write_line("else: _buffer.append(str(_tmp))") - - -class _Text(_Node): - def __init__(self, value): - self.value = value - - def generate(self, writer): - value = self.value - - # Compress lots of white space to a single character. If the whitespace - # breaks a line, have it continue to break a line, but just with a - # single \n character - if writer.compress_whitespace and "
    " not in value:
    -            value = re.sub(r"([\t ]+)", " ", value)
    -            value = re.sub(r"(\s*\n\s*)", "\n", value)
    -
    -        if value:
    -            writer.write_line('_buffer.append(%r)' % value)
    -
    -
    -class ParseError(Exception):
    -    """Raised for template syntax errors."""
    -    pass
    -
    -
    -class _CodeWriter(object):
    -    def __init__(self, file, named_blocks, loader, current_template,
    -                 compress_whitespace):
    -        self.file = file
    -        self.named_blocks = named_blocks
    -        self.loader = loader
    -        self.current_template = current_template
    -        self.compress_whitespace = compress_whitespace
    -        self.apply_counter = 0
    -        self._indent = 0
    -
    -    def indent(self):
    -        return self
    -
    -    def indent_size(self):
    -        return self._indent
    -
    -    def __enter__(self):
    -        self._indent += 1
    -        return self
    -
    -    def __exit__(self, *args):
    -        assert self._indent > 0
    -        self._indent -= 1
    -
    -    def write_line(self, line, indent=None):
    -        if indent == None:
    -            indent = self._indent
    -        for i in xrange(indent):
    -            self.file.write("    ")
    -        print >> self.file, line
    -
    -
    -class _TemplateReader(object):
    -    def __init__(self, name, text):
    -        self.name = name
    -        self.text = text
    -        self.line = 0
    -        self.pos = 0
    -
    -    def find(self, needle, start=0, end=None):
    -        assert start >= 0, start
    -        pos = self.pos
    -        start += pos
    -        if end is None:
    -            index = self.text.find(needle, start)
    -        else:
    -            end += pos
    -            assert end >= start
    -            index = self.text.find(needle, start, end)
    -        if index != -1:
    -            index -= pos
    -        return index
    -
    -    def consume(self, count=None):
    -        if count is None:
    -            count = len(self.text) - self.pos
    -        newpos = self.pos + count
    -        self.line += self.text.count("\n", self.pos, newpos)
    -        s = self.text[self.pos:newpos]
    -        self.pos = newpos
    -        return s
    -
    -    def remaining(self):
    -        return len(self.text) - self.pos
    -
    -    def __len__(self):
    -        return self.remaining()
    -
    -    def __getitem__(self, key):
    -        if type(key) is slice:
    -            size = len(self)
    -            start, stop, step = slice.indices(size)
    -            if start is None: start = self.pos
    -            else: start += self.pos
    -            if stop is not None: stop += self.pos
    -            return self.text[slice(start, stop, step)]
    -        elif key < 0:
    -            return self.text[key]
    -        else:
    -            return self.text[self.pos + key]
    -
    -    def __str__(self):
    -        return self.text[self.pos:]
    -
    -
    -def _format_code(code):
    -    lines = code.splitlines()
    -    format = "%%%dd  %%s\n" % len(repr(len(lines) + 1))
    -    return "".join([format % (i + 1, line) for (i, line) in enumerate(lines)])
    -
    -
    -def _parse(reader, in_block=None):
    -    body = _ChunkList([])
    -    while True:
    -        # Find next template directive
    -        curly = 0
    -        while True:
    -            curly = reader.find("{", curly)
    -            if curly == -1 or curly + 1 == reader.remaining():
    -                # EOF
    -                if in_block:
    -                    raise ParseError("Missing {%% end %%} block for %s" %
    -                                     in_block)
    -                body.chunks.append(_Text(reader.consume()))
    -                return body
    -            # If the first curly brace is not the start of a special token,
    -            # start searching from the character after it
    -            if reader[curly + 1] not in ("{", "%"):
    -                curly += 1
    -                continue
    -            # When there are more than 2 curlies in a row, use the
    -            # innermost ones.  This is useful when generating languages
    -            # like latex where curlies are also meaningful
    -            if (curly + 2 < reader.remaining() and
    -                reader[curly + 1] == '{' and reader[curly + 2] == '{'):
    -                curly += 1
    -                continue
    -            break
    -
    -        # Append any text before the special token
    -        if curly > 0:
    -            body.chunks.append(_Text(reader.consume(curly)))
    -
    -        start_brace = reader.consume(2)
    -        line = reader.line
    -
    -        # Expression
    -        if start_brace == "{{":
    -            end = reader.find("}}")
    -            if end == -1 or reader.find("\n", 0, end) != -1:
    -                raise ParseError("Missing end expression }} on line %d" % line)
    -            contents = reader.consume(end).strip()
    -            reader.consume(2)
    -            if not contents:
    -                raise ParseError("Empty expression on line %d" % line)
    -            body.chunks.append(_Expression(contents))
    -            continue
    -
    -        # Block
    -        assert start_brace == "{%", start_brace
    -        end = reader.find("%}")
    -        if end == -1 or reader.find("\n", 0, end) != -1:
    -            raise ParseError("Missing end block %%} on line %d" % line)
    -        contents = reader.consume(end).strip()
    -        reader.consume(2)
    -        if not contents:
    -            raise ParseError("Empty block tag ({%% %%}) on line %d" % line)
    -
    -        operator, space, suffix = contents.partition(" ")
    -        suffix = suffix.strip()
    -
    -        # Intermediate ("else", "elif", etc) blocks
    -        intermediate_blocks = {
    -            "else": set(["if", "for", "while"]),
    -            "elif": set(["if"]),
    -            "except": set(["try"]),
    -            "finally": set(["try"]),
    -        }
    -        allowed_parents = intermediate_blocks.get(operator)
    -        if allowed_parents is not None:
    -            if not in_block:
    -                raise ParseError("%s outside %s block" %
    -                            (operator, allowed_parents))
    -            if in_block not in allowed_parents:
    -                raise ParseError("%s block cannot be attached to %s block" % (operator, in_block))
    -            body.chunks.append(_IntermediateControlBlock(contents))
    -            continue
    -
    -        # End tag
    -        elif operator == "end":
    -            if not in_block:
    -                raise ParseError("Extra {%% end %%} block on line %d" % line)
    -            return body
    -
    -        elif operator in ("extends", "include", "set", "import", "comment"):
    -            if operator == "comment":
    -                continue
    -            if operator == "extends":
    -                suffix = suffix.strip('"').strip("'")
    -                if not suffix:
    -                    raise ParseError("extends missing file path on line %d" % line)
    -                block = _ExtendsBlock(suffix)
    -            elif operator == "import":
    -                if not suffix:
    -                    raise ParseError("import missing statement on line %d" % line)
    -                block = _Statement(contents)
    -            elif operator == "include":
    -                suffix = suffix.strip('"').strip("'")
    -                if not suffix:
    -                    raise ParseError("include missing file path on line %d" % line)
    -                block = _IncludeBlock(suffix, reader)
    -            elif operator == "set":
    -                if not suffix:
    -                    raise ParseError("set missing statement on line %d" % line)
    -                block = _Statement(suffix)
    -            body.chunks.append(block)
    -            continue
    -
    -        elif operator in ("apply", "block", "try", "if", "for", "while"):
    -            # parse inner body recursively
    -            block_body = _parse(reader, operator)
    -            if operator == "apply":
    -                if not suffix:
    -                    raise ParseError("apply missing method name on line %d" % line)
    -                block = _ApplyBlock(suffix, block_body)
    -            elif operator == "block":
    -                if not suffix:
    -                    raise ParseError("block missing name on line %d" % line)
    -                block = _NamedBlock(suffix, block_body)
    -            else:
    -                block = _ControlBlock(contents, block_body)
    -            body.chunks.append(block)
    -            continue
    -
    -        else:
    -            raise ParseError("unknown operator: %r" % operator)
    diff --git a/lib/tornado/test/README b/lib/tornado/test/README
    deleted file mode 100644
    index 2d6195d..0000000
    --- a/lib/tornado/test/README
    +++ /dev/null
    @@ -1,4 +0,0 @@
    -Test coverage is almost non-existent, but it's a start.  Be sure to
    -set PYTHONPATH apprioriately (generally to the root directory of your
    -tornado checkout) when running tests to make sure you're getting the
    -version of the tornado package that you expect.
    \ No newline at end of file
    diff --git a/lib/tornado/test/test_ioloop.py b/lib/tornado/test/test_ioloop.py
    deleted file mode 100755
    index 2541fa8..0000000
    --- a/lib/tornado/test/test_ioloop.py
    +++ /dev/null
    @@ -1,38 +0,0 @@
    -#!/usr/bin/env python
    -
    -import unittest
    -import time
    -
    -from tornado import ioloop
    -
    -
    -class TestIOLoop(unittest.TestCase):
    -    def setUp(self):
    -        self.loop = ioloop.IOLoop()
    -
    -    def tearDown(self):
    -        pass
    -
    -    def _callback(self):
    -        self.called = True
    -        self.loop.stop()
    -
    -    def _schedule_callback(self):
    -        self.loop.add_callback(self._callback)
    -        # Scroll away the time so we can check if we woke up immediately
    -        self._start_time = time.time()
    -        self.called = False
    -
    -    def test_add_callback(self):
    -        self.loop.add_timeout(time.time(), self._schedule_callback)
    -        self.loop.start() # Set some long poll timeout so we can check wakeup
    -        self.assertAlmostEqual(time.time(), self._start_time, places=2)
    -        self.assertTrue(self.called)
    -
    -
    -if __name__ == "__main__":
    -    import logging
    -
    -    logging.basicConfig(level=logging.DEBUG, format='%(asctime)s:%(msecs)03d %(levelname)-8s %(name)-8s %(message)s', datefmt='%H:%M:%S')
    -
    -    unittest.main()
    diff --git a/lib/tornado/web.py b/lib/tornado/web.py
    deleted file mode 100644
    index 3beac23..0000000
    --- a/lib/tornado/web.py
    +++ /dev/null
    @@ -1,1488 +0,0 @@
    -#!/usr/bin/env python
    -#
    -# Copyright 2009 Facebook
    -#
    -# Licensed under the Apache License, Version 2.0 (the "License"); you may
    -# not use this file except in compliance with the License. You may obtain
    -# a copy of the License at
    -#
    -#     http://www.apache.org/licenses/LICENSE-2.0
    -#
    -# Unless required by applicable law or agreed to in writing, software
    -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    -# License for the specific language governing permissions and limitations
    -# under the License.
    -
    -"""The Tornado web framework.
    -
    -The Tornado web framework looks a bit like web.py (http://webpy.org/) or
    -Google's webapp (http://code.google.com/appengine/docs/python/tools/webapp/),
    -but with additional tools and optimizations to take advantage of the
    -Tornado non-blocking web server and tools.
    -
    -Here is the canonical "Hello, world" example app:
    -
    -    import tornado.httpserver
    -    import tornado.ioloop
    -    import tornado.web
    -
    -    class MainHandler(tornado.web.RequestHandler):
    -        def get(self):
    -            self.write("Hello, world")
    -
    -    if __name__ == "__main__":
    -        application = tornado.web.Application([
    -            (r"/", MainHandler),
    -        ])
    -        http_server = tornado.httpserver.HTTPServer(application)
    -        http_server.listen(8888)
    -        tornado.ioloop.IOLoop.instance().start()
    -
    -See the Tornado walkthrough on GitHub for more details and a good
    -getting started guide.
    -"""
    -
    -import base64
    -import binascii
    -import calendar
    -import Cookie
    -import cStringIO
    -import datetime
    -import email.utils
    -import escape
    -import functools
    -import gzip
    -import hashlib
    -import hmac
    -import httplib
    -import locale
    -import logging
    -import mimetypes
    -import os.path
    -import re
    -import stat
    -import sys
    -import template
    -import time
    -import types
    -import urllib
    -import urlparse
    -import uuid
    -
    -class RequestHandler(object):
    -    """Subclass this class and define get() or post() to make a handler.
    -
    -    If you want to support more methods than the standard GET/HEAD/POST, you
    -    should override the class variable SUPPORTED_METHODS in your
    -    RequestHandler class.
    -    """
    -    SUPPORTED_METHODS = ("GET", "HEAD", "POST", "DELETE", "PUT")
    -
    -    def __init__(self, application, request, transforms=None):
    -        self.application = application
    -        self.request = request
    -        self._headers_written = False
    -        self._finished = False
    -        self._auto_finish = True
    -        self._transforms = transforms or []
    -        self.ui = _O((n, self._ui_method(m)) for n, m in
    -                     application.ui_methods.iteritems())
    -        self.ui["modules"] = _O((n, self._ui_module(n, m)) for n, m in
    -                                application.ui_modules.iteritems())
    -        self.clear()
    -        # Check since connection is not available in WSGI
    -        if hasattr(self.request, "connection"):
    -            self.request.connection.stream.set_close_callback(
    -                self.on_connection_close)
    -
    -    @property
    -    def settings(self):
    -        return self.application.settings
    -
    -    def head(self, *args, **kwargs):
    -        raise HTTPError(405)
    -
    -    def get(self, *args, **kwargs):
    -        raise HTTPError(405)
    -
    -    def post(self, *args, **kwargs):
    -        raise HTTPError(405)
    -
    -    def delete(self, *args, **kwargs):
    -        raise HTTPError(405)
    -
    -    def put(self, *args, **kwargs):
    -        raise HTTPError(405)
    -
    -    def prepare(self):
    -        """Called before the actual handler method.
    -
    -        Useful to override in a handler if you want a common bottleneck for
    -        all of your requests.
    -        """
    -        pass
    -
    -    def on_connection_close(self):
    -        """Called in async handlers if the client closed the connection.
    -
    -        You may override this to clean up resources associated with
    -        long-lived connections.
    -
    -        Note that the select()-based implementation of IOLoop does not detect
    -        closed connections and so this method will not be called until
    -        you try (and fail) to produce some output.  The epoll- and kqueue-
    -        based implementations should detect closed connections even while
    -        the request is idle.
    -        """
    -        pass
    -
    -    def clear(self):
    -        """Resets all headers and content for this response."""
    -        self._headers = {
    -            "Server": "TornadoServer/0.1",
    -            "Content-Type": "text/html; charset=UTF-8",
    -        }
    -        if not self.request.supports_http_1_1():
    -            if self.request.headers.get("Connection") == "Keep-Alive":
    -                self.set_header("Connection", "Keep-Alive")
    -        self._write_buffer = []
    -        self._status_code = 200
    -
    -    def set_status(self, status_code):
    -        """Sets the status code for our response."""
    -        assert status_code in httplib.responses
    -        self._status_code = status_code
    -
    -    def set_header(self, name, value):
    -        """Sets the given response header name and value.
    -
    -        If a datetime is given, we automatically format it according to the
    -        HTTP specification. If the value is not a string, we convert it to
    -        a string. All header values are then encoded as UTF-8.
    -        """
    -        if isinstance(value, datetime.datetime):
    -            t = calendar.timegm(value.utctimetuple())
    -            value = email.utils.formatdate(t, localtime=False, usegmt=True)
    -        elif isinstance(value, int) or isinstance(value, long):
    -            value = str(value)
    -        else:
    -            value = _utf8(value)
    -            # If \n is allowed into the header, it is possible to inject
    -            # additional headers or split the request. Also cap length to
    -            # prevent obviously erroneous values.
    -            safe_value = re.sub(r"[\x00-\x1f]", " ", value)[:4000]
    -            if safe_value != value:
    -                raise ValueError("Unsafe header value %r", value)
    -        self._headers[name] = value
    -
    -    _ARG_DEFAULT = []
    -    def get_argument(self, name, default=_ARG_DEFAULT, strip=True):
    -        """Returns the value of the argument with the given name.
    -
    -        If default is not provided, the argument is considered to be
    -        required, and we throw an HTTP 404 exception if it is missing.
    -
    -        If the argument appears in the url more than once, we return the
    -        last value.
    -
    -        The returned value is always unicode.
    -        """
    -        args = self.get_arguments(name, strip=strip)
    -        if not args:
    -            if default is self._ARG_DEFAULT:
    -                raise HTTPError(404, "Missing argument %s" % name)
    -            return default
    -        return args[-1]
    -
    -    def get_arguments(self, name, strip=True):
    -        """Returns a list of the arguments with the given name.
    -
    -        If the argument is not present, returns an empty list.
    -
    -        The returned values are always unicode.
    -        """
    -        values = self.request.arguments.get(name, [])
    -        # Get rid of any weird control chars
    -        values = [re.sub(r"[\x00-\x08\x0e-\x1f]", " ", x) for x in values]
    -        values = [_unicode(x) for x in values]
    -        if strip:
    -            values = [x.strip() for x in values]
    -        return values
    -
    -
    -    @property
    -    def cookies(self):
    -        """A dictionary of Cookie.Morsel objects."""
    -        if not hasattr(self, "_cookies"):
    -            self._cookies = Cookie.BaseCookie()
    -            if "Cookie" in self.request.headers:
    -                try:
    -                    self._cookies.load(self.request.headers["Cookie"])
    -                except:
    -                    self.clear_all_cookies()
    -        return self._cookies
    -
    -    def get_cookie(self, name, default=None):
    -        """Gets the value of the cookie with the given name, else default."""
    -        if name in self.cookies:
    -            return self.cookies[name].value
    -        return default
    -
    -    def set_cookie(self, name, value, domain=None, expires=None, path="/",
    -                   expires_days=None, **kwargs):
    -        """Sets the given cookie name/value with the given options.
    -
    -        Additional keyword arguments are set on the Cookie.Morsel
    -        directly.
    -        See http://docs.python.org/library/cookie.html#morsel-objects
    -        for available attributes.
    -        """
    -        name = _utf8(name)
    -        value = _utf8(value)
    -        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.BaseCookie()
    -        self._new_cookies.append(new_cookie)
    -        new_cookie[name] = value
    -        if domain:
    -            new_cookie[name]["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(
    -                timestamp, localtime=False, usegmt=True)
    -        if path:
    -            new_cookie[name]["path"] = path
    -        for k, v in kwargs.iteritems():
    -            new_cookie[name][k] = v
    -
    -    def clear_cookie(self, name, path="/", domain=None):
    -        """Deletes the cookie with the given name."""
    -        expires = datetime.datetime.utcnow() - datetime.timedelta(days=365)
    -        self.set_cookie(name, value="", path=path, expires=expires,
    -                        domain=domain)
    -
    -    def clear_all_cookies(self):
    -        """Deletes all the cookies the user sent with this request."""
    -        for name in self.cookies.iterkeys():
    -            self.clear_cookie(name)
    -
    -    def set_secure_cookie(self, name, value, expires_days=30, **kwargs):
    -        """Signs and timestamps a cookie so it cannot be forged.
    -
    -        You must specify the 'cookie_secret' setting in your Application
    -        to use this method. It should be a long, random sequence of bytes
    -        to be used as the HMAC secret for the signature.
    -
    -        To read a cookie set with this method, use get_secure_cookie().
    -        """
    -        timestamp = str(int(time.time()))
    -        value = base64.b64encode(value)
    -        signature = self._cookie_signature(name, value, timestamp)
    -        value = "|".join([value, timestamp, signature])
    -        self.set_cookie(name, value, expires_days=expires_days, **kwargs)
    -
    -    def get_secure_cookie(self, name, include_name=True, value=None):
    -        """Returns the given signed cookie if it validates, or None.
    -
    -        In older versions of Tornado (0.1 and 0.2), we did not include the
    -        name of the cookie in the cookie signature. To read these old-style
    -        cookies, pass include_name=False to this method. Otherwise, all
    -        attempts to read old-style cookies will fail (and you may log all
    -        your users out whose cookies were written with a previous Tornado
    -        version).
    -        """
    -        if value is None: value = self.get_cookie(name)
    -        if not value: return None
    -        parts = value.split("|")
    -        if len(parts) != 3: return None
    -        if include_name:
    -            signature = self._cookie_signature(name, parts[0], parts[1])
    -        else:
    -            signature = self._cookie_signature(parts[0], parts[1])
    -        if not _time_independent_equals(parts[2], signature):
    -            logging.warning("Invalid cookie signature %r", value)
    -            return None
    -        timestamp = int(parts[1])
    -        if timestamp < time.time() - 31 * 86400:
    -            logging.warning("Expired cookie %r", value)
    -            return None
    -        try:
    -            return base64.b64decode(parts[0])
    -        except:
    -            return None
    -
    -    def _cookie_signature(self, *parts):
    -        self.require_setting("cookie_secret", "secure cookies")
    -        hash = hmac.new(self.application.settings["cookie_secret"],
    -                        digestmod=hashlib.sha1)
    -        for part in parts: hash.update(part)
    -        return hash.hexdigest()
    -
    -    def redirect(self, url, permanent=False):
    -        """Sends a redirect to the given (optionally relative) URL."""
    -        if self._headers_written:
    -            raise Exception("Cannot redirect after headers have been written")
    -        self.set_status(301 if permanent else 302)
    -        # Remove whitespace
    -        url = re.sub(r"[\x00-\x20]+", "", _utf8(url))
    -        self.set_header("Location", urlparse.urljoin(self.request.uri, url))
    -        self.finish()
    -
    -    def write(self, chunk):
    -        """Writes the given chunk to the output buffer.
    -
    -        To write the output to the network, use the flush() method below.
    -
    -        If the given chunk is a dictionary, we write it as JSON and set
    -        the Content-Type of the response to be text/javascript.
    -        """
    -        assert not self._finished
    -        if isinstance(chunk, dict):
    -            chunk = escape.json_encode(chunk)
    -            self.set_header("Content-Type", "text/javascript; charset=UTF-8")
    -        chunk = _utf8(chunk)
    -        self._write_buffer.append(chunk)
    -
    -    def render(self, template_name, **kwargs):
    -        """Renders the template with the given arguments as the response."""
    -        html = self.render_string(template_name, **kwargs)
    -
    -        # Insert the additional JS and CSS added by the modules on the page
    -        js_embed = []
    -        js_files = []
    -        css_embed = []
    -        css_files = []
    -        html_heads = []
    -        html_bodies = []
    -        for module in getattr(self, "_active_modules", {}).itervalues():
    -            embed_part = module.embedded_javascript()
    -            if embed_part: js_embed.append(_utf8(embed_part))
    -            file_part = module.javascript_files()
    -            if file_part:
    -                if isinstance(file_part, basestring):
    -                    js_files.append(file_part)
    -                else:
    -                    js_files.extend(file_part)
    -            embed_part = module.embedded_css()
    -            if embed_part: css_embed.append(_utf8(embed_part))
    -            file_part = module.css_files()
    -            if file_part:
    -                if isinstance(file_part, basestring):
    -                    css_files.append(file_part)
    -                else:
    -                    css_files.extend(file_part)
    -            head_part = module.html_head()
    -            if head_part: html_heads.append(_utf8(head_part))
    -            body_part = module.html_body()
    -            if body_part: html_bodies.append(_utf8(body_part))
    -        if js_files:
    -            # Maintain order of JavaScript files given by modules
    -            paths = []
    -            unique_paths = set()
    -            for path in js_files:
    -                if not path.startswith("/") and not path.startswith("http:"):
    -                    path = self.static_url(path)
    -                if path not in unique_paths:
    -                    paths.append(path)
    -                    unique_paths.add(path)
    -            js = ''.join(''
    -                         for p in paths)
    -            sloc = html.rindex('')
    -            html = html[:sloc] + js + '\n' + html[sloc:]
    -        if js_embed:
    -            js = ''
    -            sloc = html.rindex('')
    -            html = html[:sloc] + js + '\n' + html[sloc:]
    -        if css_files:
    -            paths = set()
    -            for path in css_files:
    -                if not path.startswith("/") and not path.startswith("http:"):
    -                    paths.add(self.static_url(path))
    -                else:
    -                    paths.add(path)
    -            css = ''.join(''
    -                          for p in paths)
    -            hloc = html.index('')
    -            html = html[:hloc] + css + '\n' + html[hloc:]
    -        if css_embed:
    -            css = ''
    -            hloc = html.index('')
    -            html = html[:hloc] + css + '\n' + html[hloc:]
    -        if html_heads:
    -            hloc = html.index('')
    -            html = html[:hloc] + ''.join(html_heads) + '\n' + html[hloc:]
    -        if html_bodies:
    -            hloc = html.index('')
    -            html = html[:hloc] + ''.join(html_bodies) + '\n' + html[hloc:]
    -        self.finish(html)
    -
    -    def render_string(self, template_name, **kwargs):
    -        """Generate the given template with the given arguments.
    -
    -        We return the generated string. To generate and write a template
    -        as a response, use render() above.
    -        """
    -        # If no template_path is specified, use the path of the calling file
    -        template_path = self.get_template_path()
    -        if not template_path:
    -            frame = sys._getframe(0)
    -            web_file = frame.f_code.co_filename
    -            while frame.f_code.co_filename == web_file:
    -                frame = frame.f_back
    -            template_path = os.path.dirname(frame.f_code.co_filename)
    -        if not getattr(RequestHandler, "_templates", None):
    -            RequestHandler._templates = {}
    -        if template_path not in RequestHandler._templates:
    -            loader = self.application.settings.get("template_loader") or\
    -              template.Loader(template_path)
    -            RequestHandler._templates[template_path] = loader
    -        t = RequestHandler._templates[template_path].load(template_name)
    -        args = dict(
    -            handler=self,
    -            request=self.request,
    -            current_user=self.current_user,
    -            locale=self.locale,
    -            _=self.locale.translate,
    -            static_url=self.static_url,
    -            xsrf_form_html=self.xsrf_form_html,
    -            reverse_url=self.application.reverse_url
    -        )
    -        args.update(self.ui)
    -        args.update(kwargs)
    -        return t.generate(**args)
    -
    -    def flush(self, include_footers=False):
    -        """Flushes the current output buffer to the nextwork."""
    -        if self.application._wsgi:
    -            raise Exception("WSGI applications do not support flush()")
    -
    -        chunk = "".join(self._write_buffer)
    -        self._write_buffer = []
    -        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)
    -            headers = self._generate_headers()
    -        else:
    -            for transform in self._transforms:
    -                chunk = transform.transform_chunk(chunk, include_footers)
    -            headers = ""
    -
    -        # Ignore the chunk and only write the headers for HEAD requests
    -        if self.request.method == "HEAD":
    -            if headers: self.request.write(headers)
    -            return
    -
    -        if headers or chunk:
    -            self.request.write(headers + chunk)
    -
    -    def finish(self, chunk=None):
    -        """Finishes this response, ending the HTTP request."""
    -        assert not self._finished
    -        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.
    -        if not self._headers_written:
    -            if (self._status_code == 200 and self.request.method == "GET" and
    -                "Etag" not in self._headers):
    -                hasher = hashlib.sha1()
    -                for part in self._write_buffer:
    -                    hasher.update(part)
    -                etag = '"%s"' % hasher.hexdigest()
    -                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:
    -                content_length = sum(len(part) for part in self._write_buffer)
    -                self.set_header("Content-Length", content_length)
    -
    -        if hasattr(self.request, "connection"):
    -            # Now that the request is finished, clear the callback we
    -            # set on the IOStream (which would otherwise prevent the
    -            # garbage collection of the RequestHandler when there
    -            # are keepalive connections)
    -            self.request.connection.stream.set_close_callback(None)
    -
    -        if not self.application._wsgi:
    -            self.flush(include_footers=True)
    -            self.request.finish()
    -            self._log()
    -        self._finished = True
    -
    -    def send_error(self, status_code=500, **kwargs):
    -        """Sends the given HTTP error code to the browser.
    -
    -        We also send the error HTML for the given error code as returned by
    -        get_error_html. Override that method if you want custom error pages
    -        for your application.
    -        """
    -        if self._headers_written:
    -            logging.error("Cannot send error response after headers written")
    -            if not self._finished:
    -                self.finish()
    -            return
    -        self.clear()
    -        self.set_status(status_code)
    -        message = self.get_error_html(status_code, **kwargs)
    -        self.finish(message)
    -
    -    def get_error_html(self, status_code, **kwargs):
    -        """Override to implement custom error pages.
    -
    -        If this error was caused by an uncaught exception, the
    -        exception object can be found in kwargs e.g. kwargs['exception']
    -        """
    -        return "%(code)d: %(message)s" \
    -               "%(code)d: %(message)s" % {
    -            "code": status_code,
    -            "message": httplib.responses[status_code],
    -        }
    -
    -    @property
    -    def locale(self):
    -        """The local for the current session.
    -
    -        Determined by either get_user_locale, which you can override to
    -        set the locale based on, e.g., a user preference stored in a
    -        database, or get_browser_locale, which uses the Accept-Language
    -        header.
    -        """
    -        if not hasattr(self, "_locale"):
    -            self._locale = self.get_user_locale()
    -            if not self._locale:
    -                self._locale = self.get_browser_locale()
    -                assert self._locale
    -        return self._locale
    -
    -    def get_user_locale(self):
    -        """Override to determine the locale from the authenticated user.
    -
    -        If None is returned, we use the Accept-Language header.
    -        """
    -        return None
    -
    -    def get_browser_locale(self, default="en_US"):
    -        """Determines the user's locale from Accept-Language header.
    -
    -        See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
    -        """
    -        if "Accept-Language" in self.request.headers:
    -            languages = self.request.headers["Accept-Language"].split(",")
    -            locales = []
    -            for language in languages:
    -                parts = language.strip().split(";")
    -                if len(parts) > 1 and parts[1].startswith("q="):
    -                    try:
    -                        score = float(parts[1][2:])
    -                    except (ValueError, TypeError):
    -                        score = 0.0
    -                else:
    -                    score = 1.0
    -                locales.append((parts[0], score))
    -            if locales:
    -                locales.sort(key=lambda (l, s): s, reverse=True)
    -                codes = [l[0] for l in locales]
    -                return locale.get(*codes)
    -        return locale.get(default)
    -
    -    @property
    -    def current_user(self):
    -        """The authenticated user for this request.
    -
    -        Determined by either get_current_user, which you can override to
    -        set the user based on, e.g., a cookie. If that method is not
    -        overridden, this method always returns None.
    -
    -        We lazy-load the current user the first time this method is called
    -        and cache the result after that.
    -        """
    -        if not hasattr(self, "_current_user"):
    -            self._current_user = self.get_current_user()
    -        return self._current_user
    -
    -    def get_current_user(self):
    -        """Override to determine the current user from, e.g., a cookie."""
    -        return None
    -
    -    def get_login_url(self):
    -        """Override to customize the login URL based on the request.
    -
    -        By default, we use the 'login_url' application setting.
    -        """
    -        self.require_setting("login_url", "@tornado.web.authenticated")
    -        return self.application.settings["login_url"]
    -
    -    def get_template_path(self):
    -        """Override to customize template path for each handler.
    -
    -        By default, we use the 'template_path' application setting.
    -        Return None to load templates relative to the calling file.
    -        """
    -        return self.application.settings.get("template_path")
    -
    -    @property
    -    def xsrf_token(self):
    -        """The XSRF-prevention token for the current user/session.
    -
    -        To prevent cross-site request forgery, we set an '_xsrf' cookie
    -        and include the same '_xsrf' value as an argument with all POST
    -        requests. If the two do not match, we reject the form submission
    -        as a potential forgery.
    -
    -        See http://en.wikipedia.org/wiki/Cross-site_request_forgery
    -        """
    -        if not hasattr(self, "_xsrf_token"):
    -            token = self.get_cookie("_xsrf")
    -            if not token:
    -                token = binascii.b2a_hex(uuid.uuid4().bytes)
    -                expires_days = 30 if self.current_user else None
    -                self.set_cookie("_xsrf", token, expires_days=expires_days)
    -            self._xsrf_token = token
    -        return self._xsrf_token
    -
    -    def check_xsrf_cookie(self):
    -        """Verifies that the '_xsrf' cookie matches the '_xsrf' argument.
    -
    -        To prevent cross-site request forgery, we set an '_xsrf' cookie
    -        and include the same '_xsrf' value as an argument with all POST
    -        requests. If the two do not match, we reject the form submission
    -        as a potential forgery.
    -
    -        See http://en.wikipedia.org/wiki/Cross-site_request_forgery
    -        """
    -        if self.request.headers.get("X-Requested-With") == "XMLHttpRequest":
    -            return
    -        token = self.get_argument("_xsrf", None)
    -        if not token:
    -            raise HTTPError(403, "'_xsrf' argument missing from POST")
    -        if self.xsrf_token != token:
    -            raise HTTPError(403, "XSRF cookie does not match POST argument")
    -
    -    def xsrf_form_html(self):
    -        """An HTML  element to be included with all POST forms.
    -
    -        It defines the _xsrf input value, which we check on all POST
    -        requests to prevent cross-site request forgery. If you have set
    -        the 'xsrf_cookies' application setting, you must include this
    -        HTML within all of your HTML forms.
    -
    -        See check_xsrf_cookie() above for more information.
    -        """
    -        return ''
    -
    -    def static_url(self, path):
    -        """Returns a static URL for the given relative static file path.
    -
    -        This method requires you set the 'static_path' setting in your
    -        application (which specifies the root directory of your static
    -        files).
    -
    -        We append ?v= to the returned URL, which makes our
    -        static file handler set an infinite expiration header on the
    -        returned content. The signature is based on the content of the
    -        file.
    -
    -        If this handler has a "include_host" attribute, we include the
    -        full host for every static URL, including the "http://". Set
    -        this attribute for handlers whose output needs non-relative static
    -        path names.
    -        """
    -        self.require_setting("static_path", "static_url")
    -        if not hasattr(RequestHandler, "_static_hashes"):
    -            RequestHandler._static_hashes = {}
    -        hashes = RequestHandler._static_hashes
    -        if path not in hashes:
    -            try:
    -                f = open(os.path.join(
    -                    self.application.settings["static_path"], path))
    -                hashes[path] = hashlib.md5(f.read()).hexdigest()
    -                f.close()
    -            except:
    -                logging.error("Could not open static file %r", path)
    -                hashes[path] = None
    -        base = self.request.protocol + "://" + self.request.host \
    -            if getattr(self, "include_host", False) else ""
    -        static_url_prefix = self.settings.get('static_url_prefix', '/static/')
    -        if hashes.get(path):
    -            return base + static_url_prefix + path + "?v=" + hashes[path][:5]
    -        else:
    -            return base + static_url_prefix + path
    -
    -    def async_callback(self, callback, *args, **kwargs):
    -        """Wrap callbacks with this if they are used on asynchronous requests.
    -
    -        Catches exceptions and properly finishes the request.
    -        """
    -        if callback is None:
    -            return None
    -        if args or kwargs:
    -            callback = functools.partial(callback, *args, **kwargs)
    -        def wrapper(*args, **kwargs):
    -            try:
    -                return callback(*args, **kwargs)
    -            except Exception, e:
    -                if self._headers_written:
    -                    logging.error("Exception after headers written",
    -                                  exc_info=True)
    -                else:
    -                    self._handle_request_exception(e)
    -        return wrapper
    -
    -    def require_setting(self, name, feature="this feature"):
    -        """Raises an exception if the given app setting is not defined."""
    -        if not self.application.settings.get(name):
    -            raise Exception("You must define the '%s' setting in your "
    -                            "application to use %s" % (name, feature))
    -
    -    def reverse_url(self, name, *args):
    -        return self.application.reverse_url(name, *args)
    -
    -    def _execute(self, transforms, *args, **kwargs):
    -        """Executes this request with the given output transforms."""
    -        self._transforms = transforms
    -        try:
    -            if self.request.method not in self.SUPPORTED_METHODS:
    -                raise HTTPError(405)
    -            # If XSRF cookies are turned on, reject form submissions without
    -            # the proper cookie
    -            if self.request.method == "POST" and \
    -               self.application.settings.get("xsrf_cookies"):
    -                self.check_xsrf_cookie()
    -            self.prepare()
    -            if not self._finished:
    -                getattr(self, self.request.method.lower())(*args, **kwargs)
    -                if self._auto_finish and not self._finished:
    -                    self.finish()
    -        except Exception, e:
    -            self._handle_request_exception(e)
    -
    -    def _generate_headers(self):
    -        lines = [self.request.version + " " + str(self._status_code) + " " +
    -                 httplib.responses[self._status_code]]
    -        lines.extend(["%s: %s" % (n, v) for n, v in self._headers.iteritems()])
    -        for cookie_dict in getattr(self, "_new_cookies", []):
    -            for cookie in cookie_dict.values():
    -                lines.append("Set-Cookie: " + cookie.OutputString(None))
    -        return "\r\n".join(lines) + "\r\n\r\n"
    -
    -    def _log(self):
    -        if self._status_code < 400:
    -            log_method = logging.info
    -        elif self._status_code < 500:
    -            log_method = logging.warning
    -        else:
    -            log_method = logging.error
    -        request_time = 1000.0 * self.request.request_time()
    -        log_method("%d %s %.2fms", self._status_code,
    -                   self._request_summary(), request_time)
    -
    -    def _request_summary(self):
    -        return self.request.method + " " + self.request.uri + " (" + \
    -            self.request.remote_ip + ")"
    -
    -    def _handle_request_exception(self, e):
    -        if isinstance(e, HTTPError):
    -            if e.log_message:
    -                format = "%d %s: " + e.log_message
    -                args = [e.status_code, self._request_summary()] + list(e.args)
    -                logging.warning(format, *args)
    -            if e.status_code not in httplib.responses:
    -                logging.error("Bad HTTP status code: %d", e.status_code)
    -                self.send_error(500, exception=e)
    -            else:
    -                self.send_error(e.status_code, exception=e)
    -        else:
    -            logging.error("Uncaught exception %s\n%r", self._request_summary(),
    -                          self.request, exc_info=e)
    -            self.send_error(500, exception=e)
    -
    -    def _ui_module(self, name, module):
    -        def render(*args, **kwargs):
    -            if not hasattr(self, "_active_modules"):
    -                self._active_modules = {}
    -            if name not in self._active_modules:
    -                self._active_modules[name] = module(self)
    -            rendered = self._active_modules[name].render(*args, **kwargs)
    -            return rendered
    -        return render
    -
    -    def _ui_method(self, method):
    -        return lambda *args, **kwargs: method(self, *args, **kwargs)
    -
    -
    -def asynchronous(method):
    -    """Wrap request handler methods with this if they are asynchronous.
    -
    -    If this decorator is given, the response is not finished when the
    -    method returns. It is up to the request handler to call self.finish()
    -    to finish the HTTP request. Without this decorator, the request is
    -    automatically finished when the get() or post() method returns.
    -
    -       class MyRequestHandler(web.RequestHandler):
    -           @web.asynchronous
    -           def get(self):
    -              http = httpclient.AsyncHTTPClient()
    -              http.fetch("http://friendfeed.com/", self._on_download)
    -
    -           def _on_download(self, response):
    -              self.write("Downloaded!")
    -              self.finish()
    -
    -    """
    -    @functools.wraps(method)
    -    def wrapper(self, *args, **kwargs):
    -        if self.application._wsgi:
    -            raise Exception("@asynchronous is not supported for WSGI apps")
    -        self._auto_finish = False
    -        return method(self, *args, **kwargs)
    -    return wrapper
    -
    -
    -def removeslash(method):
    -    """Use this decorator to remove trailing slashes from the request path.
    -
    -    For example, a request to '/foo/' would redirect to '/foo' with this
    -    decorator. Your request handler mapping should use a regular expression
    -    like r'/foo/*' in conjunction with using the decorator.
    -    """
    -    @functools.wraps(method)
    -    def wrapper(self, *args, **kwargs):
    -        if self.request.path.endswith("/"):
    -            if self.request.method == "GET":
    -                uri = self.request.path.rstrip("/")
    -                if self.request.query: uri += "?" + self.request.query
    -                self.redirect(uri)
    -                return
    -            raise HTTPError(404)
    -        return method(self, *args, **kwargs)
    -    return wrapper
    -
    -
    -def addslash(method):
    -    """Use this decorator to add a missing trailing slash to the request path.
    -
    -    For example, a request to '/foo' would redirect to '/foo/' with this
    -    decorator. Your request handler mapping should use a regular expression
    -    like r'/foo/?' in conjunction with using the decorator.
    -    """
    -    @functools.wraps(method)
    -    def wrapper(self, *args, **kwargs):
    -        if not self.request.path.endswith("/"):
    -            if self.request.method == "GET":
    -                uri = self.request.path + "/"
    -                if self.request.query: uri += "?" + self.request.query
    -                self.redirect(uri)
    -                return
    -            raise HTTPError(404)
    -        return method(self, *args, **kwargs)
    -    return wrapper
    -
    -
    -class Application(object):
    -    """A collection of request handlers that make up a web application.
    -
    -    Instances of this class are callable and can be passed directly to
    -    HTTPServer to serve the application:
    -
    -        application = web.Application([
    -            (r"/", MainPageHandler),
    -        ])
    -        http_server = httpserver.HTTPServer(application)
    -        http_server.listen(8080)
    -        ioloop.IOLoop.instance().start()
    -
    -    The constructor for this class takes in a list of URLSpec objects
    -    or (regexp, request_class) tuples. When we receive requests, we
    -    iterate over the list in order and instantiate an instance of the
    -    first request class whose regexp matches the request path.
    -
    -    Each tuple can contain an optional third element, which should be a
    -    dictionary if it is present. That dictionary is passed as keyword
    -    arguments to the contructor of the handler. This pattern is used
    -    for the StaticFileHandler below:
    -
    -        application = web.Application([
    -            (r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
    -        ])
    -
    -    We support virtual hosts with the add_handlers method, which takes in
    -    a host regular expression as the first argument:
    -
    -        application.add_handlers(r"www\.myhost\.com", [
    -            (r"/article/([0-9]+)", ArticleHandler),
    -        ])
    -
    -    You can serve static files by sending the static_path setting as a
    -    keyword argument. We will serve those files from the /static/ URI
    -    (this is configurable with the static_url_prefix setting),
    -    and we will serve /favicon.ico and /robots.txt from the same directory.
    -    """
    -    def __init__(self, handlers=None, default_host="", transforms=None,
    -                 wsgi=False, **settings):
    -        if transforms is None:
    -            self.transforms = []
    -            if settings.get("gzip"):
    -                self.transforms.append(GZipContentEncoding)
    -            self.transforms.append(ChunkedTransferEncoding)
    -        else:
    -            self.transforms = transforms
    -        self.handlers = []
    -        self.named_handlers = {}
    -        self.default_host = default_host
    -        self.settings = settings
    -        self.ui_modules = {}
    -        self.ui_methods = {}
    -        self._wsgi = wsgi
    -        self._load_ui_modules(settings.get("ui_modules", {}))
    -        self._load_ui_methods(settings.get("ui_methods", {}))
    -        if self.settings.get("static_path"):
    -            path = self.settings["static_path"]
    -            handlers = list(handlers or [])
    -            static_url_prefix = settings.get("static_url_prefix",
    -                                             "/static/")
    -            handlers = [
    -                (re.escape(static_url_prefix) + r"(.*)", StaticFileHandler,
    -                 dict(path=path)),
    -                (r"/(favicon\.ico)", StaticFileHandler, dict(path=path)),
    -                (r"/(robots\.txt)", StaticFileHandler, dict(path=path)),
    -            ] + handlers
    -        if handlers: self.add_handlers(".*$", handlers)
    -
    -        # Automatically reload modified modules
    -        if self.settings.get("debug") and not wsgi:
    -            import autoreload
    -            autoreload.start()
    -
    -    def add_handlers(self, host_pattern, host_handlers):
    -        """Appends the given handlers to our handler list."""
    -        if not host_pattern.endswith("$"):
    -            host_pattern += "$"
    -        handlers = []
    -        # The handlers with the wildcard host_pattern are a special
    -        # case - they're added in the constructor but should have lower
    -        # precedence than the more-precise handlers added later.
    -        # If a wildcard handler group exists, it should always be last
    -        # in the list, so insert new groups just before it.
    -        if self.handlers and self.handlers[-1][0].pattern == '.*$':
    -            self.handlers.insert(-1, (re.compile(host_pattern), handlers))
    -        else:
    -            self.handlers.append((re.compile(host_pattern), handlers))
    -
    -        for spec in host_handlers:
    -            if type(spec) is type(()):
    -                assert len(spec) in (2, 3)
    -                pattern = spec[0]
    -                handler = spec[1]
    -                if len(spec) == 3:
    -                    kwargs = spec[2]
    -                else:
    -                    kwargs = {}
    -                spec = URLSpec(pattern, handler, kwargs)
    -            handlers.append(spec)
    -            if spec.name:
    -                if spec.name in self.named_handlers:
    -                    logging.warning(
    -                        "Multiple handlers named %s; replacing previous value",
    -                        spec.name)
    -                self.named_handlers[spec.name] = spec
    -
    -    def add_transform(self, transform_class):
    -        """Adds the given OutputTransform to our transform list."""
    -        self.transforms.append(transform_class)
    -
    -    def _get_host_handlers(self, request):
    -        host = request.host.lower().split(':')[0]
    -        for pattern, handlers in self.handlers:
    -            if pattern.match(host):
    -                return handlers
    -        # Look for default host if not behind load balancer (for debugging)
    -        if "X-Real-Ip" not in request.headers:
    -            for pattern, handlers in self.handlers:
    -                if pattern.match(self.default_host):
    -                    return handlers
    -        return None
    -
    -    def _load_ui_methods(self, methods):
    -        if type(methods) is types.ModuleType:
    -            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)
    -        else:
    -            for name, fn in methods.iteritems():
    -                if not name.startswith("_") and hasattr(fn, "__call__") \
    -                   and name[0].lower() == name[0]:
    -                    self.ui_methods[name] = fn
    -
    -    def _load_ui_modules(self, modules):
    -        if type(modules) is types.ModuleType:
    -            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)
    -        else:
    -            assert isinstance(modules, dict)
    -            for name, cls in modules.iteritems():
    -                try:
    -                    if issubclass(cls, UIModule):
    -                        self.ui_modules[name] = cls
    -                except TypeError:
    -                    pass
    -
    -    def __call__(self, request):
    -        """Called by HTTPServer to execute the request."""
    -        transforms = [t(request) for t in self.transforms]
    -        handler = None
    -        args = []
    -        kwargs = {}
    -        handlers = self._get_host_handlers(request)
    -        if not handlers:
    -            handler = RedirectHandler(
    -                request, "http://" + self.default_host + "/")
    -        else:
    -            for spec in handlers:
    -                match = spec.regex.match(request.path)
    -                if match:
    -                    handler = spec.handler_class(self, request, **spec.kwargs)
    -                    # Pass matched groups to the handler.  Since
    -                    # match.groups() includes both named and unnamed groups,
    -                    # we want to use either groups or groupdict but not both.
    -                    kwargs = dict((k, urllib.unquote(v))
    -                                  for (k, v) in match.groupdict().iteritems())
    -                    if kwargs:
    -                        args = []
    -                    else:
    -                        args = [urllib.unquote(s) for s in match.groups()]
    -                    break
    -            if not handler:
    -                handler = ErrorHandler(self, request, 404)
    -
    -        # In debug mode, re-compile templates and reload static files on every
    -        # request so you don't need to restart to see changes
    -        if self.settings.get("debug"):
    -            if getattr(RequestHandler, "_templates", None):
    -              map(lambda loader: loader.reset(),
    -                  RequestHandler._templates.values())
    -            RequestHandler._static_hashes = {}
    -
    -        handler._execute(transforms, *args, **kwargs)
    -        return handler
    -
    -    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
    -        """
    -        if name in self.named_handlers:
    -            return self.named_handlers[name].reverse(*args)
    -        raise KeyError("%s not found in named urls" % name)
    -
    -
    -class HTTPError(Exception):
    -    """An exception that will turn into an HTTP error response."""
    -    def __init__(self, status_code, log_message=None, *args):
    -        self.status_code = status_code
    -        self.log_message = log_message
    -        self.args = args
    -
    -    def __str__(self):
    -        message = "HTTP %d: %s" % (
    -            self.status_code, httplib.responses[self.status_code])
    -        if self.log_message:
    -            return message + " (" + (self.log_message % self.args) + ")"
    -        else:
    -            return message
    -
    -
    -class ErrorHandler(RequestHandler):
    -    """Generates an error response with status_code for all requests."""
    -    def __init__(self, application, request, status_code):
    -        RequestHandler.__init__(self, application, request)
    -        self.set_status(status_code)
    -
    -    def prepare(self):
    -        raise HTTPError(self._status_code)
    -
    -
    -class RedirectHandler(RequestHandler):
    -    """Redirects the client to the given URL for all GET requests.
    -
    -    You should provide the keyword argument "url" to the handler, e.g.:
    -
    -        application = web.Application([
    -            (r"/oldpath", web.RedirectHandler, {"url": "/newpath"}),
    -        ])
    -    """
    -    def __init__(self, application, request, url, permanent=True):
    -        RequestHandler.__init__(self, application, request)
    -        self._url = url
    -        self._permanent = permanent
    -
    -    def get(self):
    -        self.redirect(self._url, permanent=self._permanent)
    -
    -
    -class StaticFileHandler(RequestHandler):
    -    """A simple handler that can serve static content from a directory.
    -
    -    To map a path to this handler for a static data directory /var/www,
    -    you would add a line to your application like:
    -
    -        application = web.Application([
    -            (r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
    -        ])
    -
    -    The local root directory of the content should be passed as the "path"
    -    argument to the handler.
    -
    -    To support aggressive browser caching, if the argument "v" is given
    -    with the path, we set an infinite HTTP expiration header. So, if you
    -    want browsers to cache a file indefinitely, send them to, e.g.,
    -    /static/images/myimage.png?v=xxx.
    -    """
    -    def __init__(self, application, request, path):
    -        RequestHandler.__init__(self, application, request)
    -        self.root = os.path.abspath(path) + os.path.sep
    -
    -    def head(self, path):
    -        self.get(path, include_body=False)
    -
    -    def get(self, path, include_body=True):
    -        abspath = os.path.abspath(os.path.join(self.root, path))
    -        if not abspath.startswith(self.root):
    -            raise HTTPError(403, "%s is not in root static directory", path)
    -        if not os.path.exists(abspath):
    -            raise HTTPError(404)
    -        if not os.path.isfile(abspath):
    -            raise HTTPError(403, "%s is not a file", path)
    -
    -        stat_result = os.stat(abspath)
    -        modified = datetime.datetime.fromtimestamp(stat_result[stat.ST_MTIME])
    -
    -        self.set_header("Last-Modified", modified)
    -        if "v" in self.request.arguments:
    -            self.set_header("Expires", datetime.datetime.utcnow() + \
    -                                       datetime.timedelta(days=365*10))
    -            self.set_header("Cache-Control", "max-age=" + str(86400*365*10))
    -        else:
    -            self.set_header("Cache-Control", "public")
    -        mime_type, encoding = mimetypes.guess_type(abspath)
    -        if mime_type:
    -            self.set_header("Content-Type", mime_type)
    -
    -        self.set_extra_headers(path)
    -
    -        # Check the If-Modified-Since, and don't send the result if the
    -        # content has not been modified
    -        ims_value = self.request.headers.get("If-Modified-Since")
    -        if ims_value is not None:
    -            date_tuple = email.utils.parsedate(ims_value)
    -            if_since = datetime.datetime.fromtimestamp(time.mktime(date_tuple))
    -            if if_since >= modified:
    -                self.set_status(304)
    -                return
    -
    -        if not include_body:
    -            return
    -        self.set_header("Content-Length", stat_result[stat.ST_SIZE])
    -        file = open(abspath, "rb")
    -        try:
    -            self.write(file.read())
    -        finally:
    -            file.close()
    -
    -    def set_extra_headers(self, path):
    -      """For subclass to add extra headers to the response"""
    -      pass
    -
    -
    -class FallbackHandler(RequestHandler):
    -    """A RequestHandler that wraps another HTTP server callback.
    -
    -    The fallback is a callable object that accepts an HTTPRequest,
    -    such as an Application or tornado.wsgi.WSGIContainer.  This is most
    -    useful to use both tornado RequestHandlers and WSGI in the same server.
    -    Typical usage:
    -        wsgi_app = tornado.wsgi.WSGIContainer(
    -            django.core.handlers.wsgi.WSGIHandler())
    -        application = tornado.web.Application([
    -            (r"/foo", FooHandler),
    -            (r".*", FallbackHandler, dict(fallback=wsgi_app),
    -        ])
    -    """
    -    def __init__(self, app, request, fallback):
    -        RequestHandler.__init__(self, app, request)
    -        self.fallback = fallback
    -
    -    def prepare(self):
    -        self.fallback(self.request)
    -        self._finished = True
    -
    -
    -class OutputTransform(object):
    -    """A transform modifies the result of an HTTP request (e.g., GZip encoding)
    -
    -    A new transform instance is created for every request. See the
    -    ChunkedTransferEncoding example below if you want to implement a
    -    new Transform.
    -    """
    -    def __init__(self, request):
    -        pass
    -
    -    def transform_first_chunk(self, headers, chunk, finishing):
    -        return headers, chunk
    -
    -    def transform_chunk(self, chunk, finishing):
    -        return chunk
    -
    -
    -class GZipContentEncoding(OutputTransform):
    -    """Applies the gzip content encoding to the response.
    -
    -    See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
    -    """
    -    CONTENT_TYPES = set([
    -        "text/plain", "text/html", "text/css", "text/xml",
    -        "application/x-javascript", "application/xml", "application/atom+xml",
    -        "text/javascript", "application/json", "application/xhtml+xml"])
    -    MIN_LENGTH = 5
    -
    -    def __init__(self, request):
    -        self._gzipping = request.supports_http_1_1() and \
    -            "gzip" in request.headers.get("Accept-Encoding", "")
    -
    -    def transform_first_chunk(self, headers, chunk, finishing):
    -        if self._gzipping:
    -            ctype = headers.get("Content-Type", "").split(";")[0]
    -            self._gzipping = (ctype in self.CONTENT_TYPES) and \
    -                (not finishing or len(chunk) >= self.MIN_LENGTH) and \
    -                (finishing or "Content-Length" not in headers) and \
    -                ("Content-Encoding" not in headers)
    -        if self._gzipping:
    -            headers["Content-Encoding"] = "gzip"
    -            self._gzip_value = cStringIO.StringIO()
    -            self._gzip_file = gzip.GzipFile(mode="w", fileobj=self._gzip_value)
    -            self._gzip_pos = 0
    -            chunk = self.transform_chunk(chunk, finishing)
    -            if "Content-Length" in headers:
    -                headers["Content-Length"] = str(len(chunk))
    -        return headers, chunk
    -
    -    def transform_chunk(self, chunk, finishing):
    -        if self._gzipping:
    -            self._gzip_file.write(chunk)
    -            if finishing:
    -                self._gzip_file.close()
    -            else:
    -                self._gzip_file.flush()
    -            chunk = self._gzip_value.getvalue()
    -            if self._gzip_pos > 0:
    -                chunk = chunk[self._gzip_pos:]
    -            self._gzip_pos += len(chunk)
    -        return chunk
    -
    -
    -class ChunkedTransferEncoding(OutputTransform):
    -    """Applies the chunked transfer encoding to the response.
    -
    -    See http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1
    -    """
    -    def __init__(self, request):
    -        self._chunking = request.supports_http_1_1()
    -
    -    def transform_first_chunk(self, headers, chunk, finishing):
    -        if self._chunking:
    -            # 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
    -
    -    def transform_chunk(self, block, finishing):
    -        if self._chunking:
    -            # Don't write out empty chunks because that means END-OF-STREAM
    -            # with chunked encoding
    -            if block:
    -                block = ("%x" % len(block)) + "\r\n" + block + "\r\n"
    -            if finishing:
    -                block += "0\r\n\r\n"
    -        return block
    -
    -
    -def authenticated(method):
    -    """Decorate methods with this to require that the user be logged in."""
    -    @functools.wraps(method)
    -    def wrapper(self, *args, **kwargs):
    -        if not self.current_user:
    -            if self.request.method == "GET":
    -                url = self.get_login_url()
    -                if "?" not in url:
    -                    url += "?" + urllib.urlencode(dict(next=self.request.uri))
    -                self.redirect(url)
    -                return
    -            raise HTTPError(403)
    -        return method(self, *args, **kwargs)
    -    return wrapper
    -
    -
    -class UIModule(object):
    -    """A UI re-usable, modular unit on a page.
    -
    -    UI modules often execute additional queries, and they can include
    -    additional CSS and JavaScript that will be included in the output
    -    page, which is automatically inserted on page render.
    -    """
    -    def __init__(self, handler):
    -        self.handler = handler
    -        self.request = handler.request
    -        self.ui = handler.ui
    -        self.current_user = handler.current_user
    -        self.locale = handler.locale
    -
    -    def render(self, *args, **kwargs):
    -        raise NotImplementedError()
    -
    -    def embedded_javascript(self):
    -        """Returns a JavaScript string that will be embedded in the page."""
    -        return None
    -
    -    def javascript_files(self):
    -        """Returns a list of JavaScript files required by this module."""
    -        return None
    -
    -    def embedded_css(self):
    -        """Returns a CSS string that will be embedded in the page."""
    -        return None
    -
    -    def css_files(self):
    -        """Returns a list of CSS files required by this module."""
    -        return None
    -
    -    def html_head(self):
    -        """Returns a CSS string that will be put in the  element"""
    -        return None
    -
    -    def html_body(self):
    -        """Returns an HTML string that will be put in the  element"""
    -        return None
    -
    -    def render_string(self, path, **kwargs):
    -        return self.handler.render_string(path, **kwargs)
    -
    -class URLSpec(object):
    -    """Specifies mappings between URLs and handlers."""
    -    def __init__(self, pattern, handler_class, kwargs={}, name=None):
    -        """Creates a URLSpec.
    -
    -        Parameters:
    -        pattern: Regular expression to be matched.  Any groups in the regex
    -            will be passed in to the handler's get/post/etc methods as
    -            arguments.
    -        handler_class: RequestHandler subclass to be invoked.
    -        kwargs (optional): A dictionary of additional arguments to be passed
    -            to the handler's constructor.
    -        name (optional): A name for this handler.  Used by
    -            Application.reverse_url.
    -        """
    -        if not pattern.endswith('$'):
    -            pattern += '$'
    -        self.regex = re.compile(pattern)
    -        self.handler_class = handler_class
    -        self.kwargs = kwargs
    -        self.name = name
    -        self._path, self._group_count = self._find_groups()
    -
    -    def _find_groups(self):
    -        """Returns a tuple (reverse string, group count) for a url.
    -
    -        For example: Given the url pattern /([0-9]{4})/([a-z-]+)/, this method
    -        would return ('/%s/%s/', 2).
    -        """
    -        pattern = self.regex.pattern
    -        if pattern.startswith('^'):
    -            pattern = pattern[1:]
    -        if pattern.endswith('$'):
    -            pattern = pattern[:-1]
    -
    -        if self.regex.groups != pattern.count('('):
    -            # The pattern is too complicated for our simplistic matching,
    -            # so we can't support reversing it.
    -            return (None, None)
    -
    -        pieces = []
    -        for fragment in pattern.split('('):
    -            if ')' in fragment:
    -                paren_loc = fragment.index(')')
    -                if paren_loc >= 0:
    -                    pieces.append('%s' + fragment[paren_loc + 1:])
    -            else:
    -                pieces.append(fragment)
    -
    -        return (''.join(pieces), self.regex.groups)
    -
    -    def reverse(self, *args):
    -        assert self._path is not None, \
    -            "Cannot reverse url regex " + self.regex.pattern
    -        assert len(args) == self._group_count, "required number of arguments "\
    -            "not found"
    -        if not len(args):
    -            return self._path
    -        return self._path % tuple([str(a) for a in args])
    -
    -url = URLSpec
    -
    -def _utf8(s):
    -    if isinstance(s, unicode):
    -        return s.encode("utf-8")
    -    assert isinstance(s, str)
    -    return s
    -
    -
    -def _unicode(s):
    -    if isinstance(s, str):
    -        try:
    -            return s.decode("utf-8")
    -        except UnicodeDecodeError:
    -            raise HTTPError(400, "Non-utf8 argument")
    -    assert isinstance(s, unicode)
    -    return s
    -
    -
    -def _time_independent_equals(a, b):
    -    if len(a) != len(b):
    -        return False
    -    result = 0
    -    for x, y in zip(a, b):
    -        result |= ord(x) ^ ord(y)
    -    return result == 0
    -
    -
    -class _O(dict):
    -    """Makes a dictionary behave like an object."""
    -    def __getattr__(self, name):
    -        try:
    -            return self[name]
    -        except KeyError:
    -            raise AttributeError(name)
    -
    -    def __setattr__(self, name, value):
    -        self[name] = value
    diff --git a/lib/tornado/websocket.py b/lib/tornado/websocket.py
    deleted file mode 100644
    index 3c5223a..0000000
    --- a/lib/tornado/websocket.py
    +++ /dev/null
    @@ -1,139 +0,0 @@
    -#!/usr/bin/env python
    -#
    -# Copyright 2009 Facebook
    -#
    -# Licensed under the Apache License, Version 2.0 (the "License"); you may
    -# not use this file except in compliance with the License. You may obtain
    -# a copy of the License at
    -#
    -#     http://www.apache.org/licenses/LICENSE-2.0
    -#
    -# Unless required by applicable law or agreed to in writing, software
    -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    -# License for the specific language governing permissions and limitations
    -# under the License.
    -
    -import functools
    -import logging
    -import tornado.escape
    -import tornado.web
    -
    -class WebSocketHandler(tornado.web.RequestHandler):
    -    """A request handler for HTML 5 Web Sockets.
    -
    -    See http://www.w3.org/TR/2009/WD-websockets-20091222/ for details on the
    -    JavaScript interface. We implement the protocol as specified at
    -    http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-55.
    -
    -    Here is an example Web Socket handler that echos back all received messages
    -    back to the client:
    -
    -      class EchoWebSocket(websocket.WebSocketHandler):
    -          def open(self):
    -              self.receive_message(self.on_message)
    -
    -          def on_message(self, message):
    -              self.write_message(u"You said: " + message)
    -              # receive_message only reads a single message, so call it
    -              # again to listen for the next one
    -              self.receive_message(self.on_message)
    -
    -    Web Sockets are not standard HTTP connections. The "handshake" is HTTP,
    -    but after the handshake, the protocol is message-based. Consequently,
    -    most of the Tornado HTTP facilities are not available in handlers of this
    -    type. The only communication methods available to you are send_message()
    -    and receive_message(). Likewise, your request handler class should
    -    implement open() method rather than get() or post().
    -
    -    If you map the handler above to "/websocket" in your application, you can
    -    invoke it in JavaScript with:
    -
    -      var ws = new WebSocket("ws://localhost:8888/websocket");
    -      ws.onopen = function() {
    -         ws.send("Hello, world");
    -      };
    -      ws.onmessage = function (evt) {
    -         alert(evt.data);
    -      };
    -
    -    This script pops up an alert box that says "You said: Hello, world".
    -    """
    -    def __init__(self, application, request):
    -        tornado.web.RequestHandler.__init__(self, application, request)
    -        self.stream = request.connection.stream
    -
    -    def _execute(self, transforms, *args, **kwargs):
    -        if self.request.headers.get("Upgrade") != "WebSocket" or \
    -           self.request.headers.get("Connection") != "Upgrade" or \
    -           not self.request.headers.get("Origin"):
    -            message = "Expected WebSocket headers"
    -            self.stream.write(
    -                "HTTP/1.1 403 Forbidden\r\nContent-Length: " +
    -                str(len(message)) + "\r\n\r\n" + message)
    -            return
    -        self.stream.write(
    -            "HTTP/1.1 101 Web Socket Protocol Handshake\r\n"
    -            "Upgrade: WebSocket\r\n"
    -            "Connection: Upgrade\r\n"
    -            "Server: TornadoServer/0.1\r\n"
    -            "WebSocket-Origin: " + self.request.headers["Origin"] + "\r\n"
    -            "WebSocket-Location: ws://" + self.request.host +
    -            self.request.path + "\r\n\r\n")
    -        self.async_callback(self.open)(*args, **kwargs)
    -
    -    def write_message(self, message):
    -        """Sends the given message to the client of this Web Socket."""
    -        if isinstance(message, dict):
    -            message = tornado.escape.json_encode(message)
    -        if isinstance(message, unicode):
    -            message = message.encode("utf-8")
    -        assert isinstance(message, str)
    -        self.stream.write("\x00" + message + "\xff")
    -
    -    def receive_message(self, callback):
    -        """Calls callback when the browser calls send() on this Web Socket."""
    -        callback = self.async_callback(callback)
    -        self.stream.read_bytes(
    -            1, functools.partial(self._on_frame_type, callback))
    -
    -    def close(self):
    -        """Closes this Web Socket.
    -
    -        The browser will receive the onclose event for the open web socket
    -        when this method is called.
    -        """
    -        self.stream.close()
    -
    -    def async_callback(self, callback, *args, **kwargs):
    -        """Wrap callbacks with this if they are used on asynchronous requests.
    -
    -        Catches exceptions properly and closes this Web Socket if an exception
    -        is uncaught.
    -        """
    -        if args or kwargs:
    -            callback = functools.partial(callback, *args, **kwargs)
    -        def wrapper(*args, **kwargs):
    -            try:
    -                return callback(*args, **kwargs)
    -            except Exception, e:
    -                logging.error("Uncaught exception in %s",
    -                              self.request.path, exc_info=True)
    -                self.stream.close()
    -        return wrapper
    -
    -    def _on_frame_type(self, callback, byte):
    -        if ord(byte) & 0x80 == 0x80:
    -            raise Exception("Length-encoded format not yet supported")
    -        self.stream.read_until(
    -            "\xff", functools.partial(self._on_end_delimiter, callback))
    -
    -    def _on_end_delimiter(self, callback, frame):
    -        callback(frame[:-1].decode("utf-8", "replace"))
    -
    -    def _not_supported(self, *args, **kwargs):
    -        raise Exception("Method not supported for Web Sockets")
    -
    -for method in ["write", "redirect", "set_header", "send_error", "set_cookie",
    -               "set_status", "flush", "finish"]:
    -    setattr(WebSocketHandler, method, WebSocketHandler._not_supported)
    diff --git a/lib/tornado/win32_support.py b/lib/tornado/win32_support.py
    deleted file mode 100644
    index f3efa8e..0000000
    --- a/lib/tornado/win32_support.py
    +++ /dev/null
    @@ -1,123 +0,0 @@
    -# NOTE: win32 support is currently experimental, and not recommended
    -# for production use.
    -
    -import ctypes
    -import ctypes.wintypes
    -import os
    -import socket
    -import errno
    -
    -
    -# See: http://msdn.microsoft.com/en-us/library/ms738573(VS.85).aspx
    -ioctlsocket = ctypes.windll.ws2_32.ioctlsocket
    -ioctlsocket.argtypes = (ctypes.wintypes.HANDLE, ctypes.wintypes.LONG, ctypes.wintypes.ULONG)
    -ioctlsocket.restype = ctypes.c_int
    -
    -# See: http://msdn.microsoft.com/en-us/library/ms724935(VS.85).aspx
    -SetHandleInformation = ctypes.windll.kernel32.SetHandleInformation
    -SetHandleInformation.argtypes = (ctypes.wintypes.HANDLE, ctypes.wintypes.DWORD, ctypes.wintypes.DWORD)
    -SetHandleInformation.restype = ctypes.wintypes.BOOL
    -
    -HANDLE_FLAG_INHERIT = 0x00000001
    -
    -
    -F_GETFD = 1
    -F_SETFD = 2
    -F_GETFL = 3
    -F_SETFL = 4
    -
    -FD_CLOEXEC = 1
    -
    -os.O_NONBLOCK = 2048
    -
    -FIONBIO = 126
    -
    -
    -def fcntl(fd, op, arg=0):
    -    if op == F_GETFD or op == F_GETFL:
    -        return 0
    -    elif op == F_SETFD:
    -        # Check that the flag is CLOEXEC and translate
    -        if arg == FD_CLOEXEC:
    -            success = SetHandleInformation(fd, HANDLE_FLAG_INHERIT, arg)
    -            if not success:
    -                raise ctypes.GetLastError()
    -        else:
    -            raise ValueError("Unsupported arg")
    -    #elif op == F_SETFL:
    -        ## Check that the flag is NONBLOCK and translate
    -        #if arg == os.O_NONBLOCK:
    -            ##pass
    -            #result = ioctlsocket(fd, FIONBIO, 1)
    -            #if result != 0:
    -                #raise ctypes.GetLastError()
    -        #else:
    -            #raise ValueError("Unsupported arg")
    -    else:
    -        raise ValueError("Unsupported op")
    -
    -
    -class Pipe(object):
    -    """Create an OS independent asynchronous pipe"""
    -    def __init__(self):
    -        # Based on Zope async.py: http://svn.zope.org/zc.ngi/trunk/src/zc/ngi/async.py
    -
    -        self.writer = socket.socket()
    -        # Disable buffering -- pulling the trigger sends 1 byte,
    -        # and we want that sent immediately, to wake up ASAP.
    -        self.writer.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
    -
    -        count = 0
    -        while 1:
    -            count += 1
    -            # Bind to a local port; for efficiency, let the OS pick
    -            # a free port for us.
    -            # Unfortunately, stress tests showed that we may not
    -            # be able to connect to that port ("Address already in
    -            # use") despite that the OS picked it.  This appears
    -            # to be a race bug in the Windows socket implementation.
    -            # So we loop until a connect() succeeds (almost always
    -            # on the first try).  See the long thread at
    -            # http://mail.zope.org/pipermail/zope/2005-July/160433.html
    -            # for hideous details.
    -            a = socket.socket()
    -            a.bind(("127.0.0.1", 0))
    -            connect_address = a.getsockname()  # assigned (host, port) pair
    -            a.listen(1)
    -            try:
    -                self.writer.connect(connect_address)
    -                break    # success
    -            except socket.error, detail:
    -                if detail[0] != errno.WSAEADDRINUSE:
    -                    # "Address already in use" is the only error
    -                    # I've seen on two WinXP Pro SP2 boxes, under
    -                    # Pythons 2.3.5 and 2.4.1.
    -                    raise
    -                # (10048, 'Address already in use')
    -                # assert count <= 2 # never triggered in Tim's tests
    -                if count >= 10:  # I've never seen it go above 2
    -                    a.close()
    -                    self.writer.close()
    -                    raise socket.error("Cannot bind trigger!")
    -                # Close `a` and try again.  Note:  I originally put a short
    -                # sleep() here, but it didn't appear to help or hurt.
    -                a.close()
    -
    -        self.reader, addr = a.accept()
    -        self.reader.setblocking(0)
    -        self.writer.setblocking(0)
    -        a.close()
    -        self.reader_fd = self.reader.fileno()
    -
    -    def read(self):
    -        """Emulate a file descriptors read method"""
    -        try:
    -            return self.reader.recv(1)
    -        except socket.error, ex:
    -            if ex.args[0] == errno.EWOULDBLOCK:
    -                raise IOError
    -            raise
    -
    -    def write(self, data):
    -        """Emulate a file descriptors write method"""
    -        return self.writer.send(data)
    diff --git a/lib/tornado/wsgi.py b/lib/tornado/wsgi.py
    deleted file mode 100644
    index de35669..0000000
    --- a/lib/tornado/wsgi.py
    +++ /dev/null
    @@ -1,296 +0,0 @@
    -#!/usr/bin/env python
    -#
    -# Copyright 2009 Facebook
    -#
    -# Licensed under the Apache License, Version 2.0 (the "License"); you may
    -# not use this file except in compliance with the License. You may obtain
    -# a copy of the License at
    -#
    -#     http://www.apache.org/licenses/LICENSE-2.0
    -#
    -# Unless required by applicable law or agreed to in writing, software
    -# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
    -# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
    -# License for the specific language governing permissions and limitations
    -# under the License.
    -
    -"""WSGI support for the Tornado web framework.
    -
    -We export WSGIApplication, which is very similar to web.Application, except
    -no asynchronous methods are supported (since WSGI does not support
    -non-blocking requests properly). If you call self.flush() or other
    -asynchronous methods in your request handlers running in a WSGIApplication,
    -we throw an exception.
    -
    -Example usage:
    -
    -    import tornado.web
    -    import tornado.wsgi
    -    import wsgiref.simple_server
    -
    -    class MainHandler(tornado.web.RequestHandler):
    -        def get(self):
    -            self.write("Hello, world")
    -
    -    if __name__ == "__main__":
    -        application = tornado.wsgi.WSGIApplication([
    -            (r"/", MainHandler),
    -        ])
    -        server = wsgiref.simple_server.make_server('', 8888, application)
    -        server.serve_forever()
    -
    -See the 'appengine' demo for an example of using this module to run
    -a Tornado app on Google AppEngine.
    -
    -Since no asynchronous methods are available for WSGI applications, the
    -httpclient and auth modules are both not available for WSGI applications.
    -
    -We also export WSGIContainer, which lets you run other WSGI-compatible
    -frameworks on the Tornado HTTP server and I/O loop. See WSGIContainer for
    -details and documentation.
    -"""
    -
    -import cgi
    -import cStringIO
    -import escape
    -import httplib
    -import httputil
    -import logging
    -import sys
    -import time
    -import urllib
    -import web
    -
    -class WSGIApplication(web.Application):
    -    """A WSGI-equivalent of web.Application.
    -
    -    We support the same interface, but handlers running in a WSGIApplication
    -    do not support flush() or asynchronous methods.
    -    """
    -    def __init__(self, handlers=None, default_host="", **settings):
    -        web.Application.__init__(self, handlers, default_host, transforms=[],
    -                                 wsgi=True, **settings)
    -
    -    def __call__(self, environ, start_response):
    -        handler = web.Application.__call__(self, HTTPRequest(environ))
    -        assert handler._finished
    -        status = str(handler._status_code) + " " + \
    -            httplib.responses[handler._status_code]
    -        headers = handler._headers.items()
    -        for cookie_dict in getattr(handler, "_new_cookies", []):
    -            for cookie in cookie_dict.values():
    -                headers.append(("Set-Cookie", cookie.OutputString(None)))
    -        start_response(status, headers)
    -        return handler._write_buffer
    -
    -
    -class HTTPRequest(object):
    -    """Mimics httpserver.HTTPRequest for WSGI applications."""
    -    def __init__(self, environ):
    -        """Parses the given WSGI environ to construct the request."""
    -        self.method = environ["REQUEST_METHOD"]
    -        self.path = urllib.quote(environ.get("SCRIPT_NAME", ""))
    -        self.path += urllib.quote(environ.get("PATH_INFO", ""))
    -        self.uri = self.path
    -        self.arguments = {}
    -        self.query = environ.get("QUERY_STRING", "")
    -        if self.query:
    -            self.uri += "?" + self.query
    -            arguments = cgi.parse_qs(self.query)
    -            for name, values in arguments.iteritems():
    -                values = [v for v in values if v]
    -                if values: self.arguments[name] = values
    -        self.version = "HTTP/1.1"
    -        self.headers = httputil.HTTPHeaders()
    -        if environ.get("CONTENT_TYPE"):
    -            self.headers["Content-Type"] = environ["CONTENT_TYPE"]
    -        if environ.get("CONTENT_LENGTH"):
    -            self.headers["Content-Length"] = int(environ["CONTENT_LENGTH"])
    -        for key in environ:
    -            if key.startswith("HTTP_"):
    -                self.headers[key[5:].replace("_", "-")] = environ[key]
    -        if self.headers.get("Content-Length"):
    -            self.body = environ["wsgi.input"].read()
    -        else:
    -            self.body = ""
    -        self.protocol = environ["wsgi.url_scheme"]
    -        self.remote_ip = environ.get("REMOTE_ADDR", "")
    -        if environ.get("HTTP_HOST"):
    -            self.host = environ["HTTP_HOST"]
    -        else:
    -            self.host = environ["SERVER_NAME"]
    -
    -        # Parse request body
    -        self.files = {}
    -        content_type = self.headers.get("Content-Type", "")
    -        if content_type.startswith("application/x-www-form-urlencoded"):
    -            for name, values in cgi.parse_qs(self.body).iteritems():
    -                self.arguments.setdefault(name, []).extend(values)
    -        elif content_type.startswith("multipart/form-data"):
    -            if 'boundary=' in content_type:
    -                boundary = content_type.split('boundary=',1)[1]
    -                if boundary: self._parse_mime_body(boundary)
    -            else:
    -                logging.warning("Invalid multipart/form-data")
    -
    -        self._start_time = time.time()
    -        self._finish_time = None
    -
    -    def supports_http_1_1(self):
    -        """Returns True if this request supports HTTP/1.1 semantics"""
    -        return self.version == "HTTP/1.1"
    -
    -    def full_url(self):
    -        """Reconstructs the full URL for this request."""
    -        return self.protocol + "://" + self.host + self.uri
    -
    -    def request_time(self):
    -        """Returns the amount of time it took for this request to execute."""
    -        if self._finish_time is None:
    -            return time.time() - self._start_time
    -        else:
    -            return self._finish_time - self._start_time
    -
    -    def _parse_mime_body(self, boundary):
    -        if boundary.startswith('"') and boundary.endswith('"'):
    -            boundary = boundary[1:-1]
    -        if self.body.endswith("\r\n"):
    -            footer_length = len(boundary) + 6
    -        else:
    -            footer_length = len(boundary) + 4
    -        parts = self.body[:-footer_length].split("--" + boundary + "\r\n")
    -        for part in parts:
    -            if not part: continue
    -            eoh = part.find("\r\n\r\n")
    -            if eoh == -1:
    -                logging.warning("multipart/form-data missing headers")
    -                continue
    -            headers = httputil.HTTPHeaders.parse(part[:eoh])
    -            name_header = headers.get("Content-Disposition", "")
    -            if not name_header.startswith("form-data;") or \
    -               not part.endswith("\r\n"):
    -                logging.warning("Invalid multipart/form-data")
    -                continue
    -            value = part[eoh + 4:-2]
    -            name_values = {}
    -            for name_part in name_header[10:].split(";"):
    -                name, name_value = name_part.strip().split("=", 1)
    -                name_values[name] = name_value.strip('"').decode("utf-8")
    -            if not name_values.get("name"):
    -                logging.warning("multipart/form-data value missing name")
    -                continue
    -            name = name_values["name"]
    -            if name_values.get("filename"):
    -                ctype = headers.get("Content-Type", "application/unknown")
    -                self.files.setdefault(name, []).append(dict(
    -                    filename=name_values["filename"], body=value,
    -                    content_type=ctype))
    -            else:
    -                self.arguments.setdefault(name, []).append(value)
    -
    -
    -class WSGIContainer(object):
    -    """Makes a WSGI-compatible function runnable on Tornado's HTTP server.
    -
    -    Wrap a WSGI function in a WSGIContainer and pass it to HTTPServer to
    -    run it. For example:
    -
    -        def simple_app(environ, start_response):
    -            status = "200 OK"
    -            response_headers = [("Content-type", "text/plain")]
    -            start_response(status, response_headers)
    -            return ["Hello world!\n"]
    -
    -        container = tornado.wsgi.WSGIContainer(simple_app)
    -        http_server = tornado.httpserver.HTTPServer(container)
    -        http_server.listen(8888)
    -        tornado.ioloop.IOLoop.instance().start()
    -
    -    This class is intended to let other frameworks (Django, web.py, etc)
    -    run on the Tornado HTTP server and I/O loop. It has not yet been
    -    thoroughly tested in production.
    -    """
    -    def __init__(self, wsgi_application):
    -        self.wsgi_application = wsgi_application
    -
    -    def __call__(self, request):
    -        data = {}
    -        response = []
    -        def start_response(status, response_headers, exc_info=None):
    -            data["status"] = status
    -            data["headers"] = response_headers
    -            return response.append
    -        app_response = self.wsgi_application(
    -            WSGIContainer.environ(request), start_response)
    -        response.extend(app_response)
    -        body = "".join(response)
    -        if hasattr(app_response, "close"):
    -            app_response.close()
    -        if not data: raise Exception("WSGI app did not call start_response")
    -
    -        status_code = int(data["status"].split()[0])
    -        headers = data["headers"]
    -        header_set = set(k.lower() for (k,v) in headers)
    -        body = escape.utf8(body)
    -        if "content-length" not in header_set:
    -            headers.append(("Content-Length", str(len(body))))
    -        if "content-type" not in header_set:
    -            headers.append(("Content-Type", "text/html; charset=UTF-8"))
    -        if "server" not in header_set:
    -            headers.append(("Server", "TornadoServer/0.1"))
    -
    -        parts = ["HTTP/1.1 " + data["status"] + "\r\n"]
    -        for key, value in headers:
    -            parts.append(escape.utf8(key) + ": " + escape.utf8(value) + "\r\n")
    -        parts.append("\r\n")
    -        parts.append(body)
    -        request.write("".join(parts))
    -        request.finish()
    -        self._log(status_code, request)
    -
    -    @staticmethod
    -    def environ(request):
    -        hostport = request.host.split(":")
    -        if len(hostport) == 2:
    -            host = hostport[0]
    -            port = int(hostport[1])
    -        else:
    -            host = request.host
    -            port = 443 if request.protocol == "https" else 80
    -        environ = {
    -            "REQUEST_METHOD": request.method,
    -            "SCRIPT_NAME": "",
    -            "PATH_INFO": request.path,
    -            "QUERY_STRING": request.query,
    -            "REMOTE_ADDR": request.remote_ip,
    -            "SERVER_NAME": host,
    -            "SERVER_PORT": port,
    -            "SERVER_PROTOCOL": request.version,
    -            "wsgi.version": (1, 0),
    -            "wsgi.url_scheme": request.protocol,
    -            "wsgi.input": cStringIO.StringIO(escape.utf8(request.body)),
    -            "wsgi.errors": sys.stderr,
    -            "wsgi.multithread": False,
    -            "wsgi.multiprocess": True,
    -            "wsgi.run_once": False,
    -        }
    -        if "Content-Type" in request.headers:
    -            environ["CONTENT_TYPE"] = request.headers["Content-Type"]
    -        if "Content-Length" in request.headers:
    -            environ["CONTENT_LENGTH"] = request.headers["Content-Length"]
    -        for key, value in request.headers.iteritems():
    -            environ["HTTP_" + key.replace("-", "_").upper()] = value
    -        return environ
    -
    -    def _log(self, status_code, request):
    -        if status_code < 400:
    -            log_method = logging.info
    -        elif status_code < 500:
    -            log_method = logging.warning
    -        else:
    -            log_method = logging.error
    -        request_time = 1000.0 * request.request_time()
    -        summary = request.method + " " + request.uri + " (" + \
    -            request.remote_ip + ")"
    -        log_method("%d %s %.2fms", status_code, summary, request_time)
    -
    -- 
    2.39.2