wsgi.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 #
00003 # Copyright 2009 Facebook
00004 #
00005 # Licensed under the Apache License, Version 2.0 (the "License"); you may
00006 # not use this file except in compliance with the License. You may obtain
00007 # a copy of the License at
00008 #
00009 #     http://www.apache.org/licenses/LICENSE-2.0
00010 #
00011 # Unless required by applicable law or agreed to in writing, software
00012 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
00013 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
00014 # License for the specific language governing permissions and limitations
00015 # under the License.
00016 
00017 """WSGI support for the Tornado web framework.
00018 
00019 WSGI is the Python standard for web servers, and allows for interoperability
00020 between Tornado and other Python web frameworks and servers.  This module
00021 provides WSGI support in two ways:
00022 
00023 * `WSGIAdapter` converts a `tornado.web.Application` to the WSGI application
00024   interface.  This is useful for running a Tornado app on another
00025   HTTP server, such as Google App Engine.  See the `WSGIAdapter` class
00026   documentation for limitations that apply.
00027 * `WSGIContainer` lets you run other WSGI applications and frameworks on the
00028   Tornado HTTP server.  For example, with this class you can mix Django
00029   and Tornado handlers in a single server.
00030 """
00031 
00032 from __future__ import absolute_import, division, print_function, with_statement
00033 
00034 import sys
00035 import tornado
00036 
00037 from tornado.concurrent import Future
00038 from tornado import escape
00039 from tornado import httputil
00040 from tornado.log import access_log
00041 from tornado import web
00042 from tornado.escape import native_str
00043 from tornado.util import bytes_type, unicode_type
00044 
00045 try:
00046     from io import BytesIO  # python 3
00047 except ImportError:
00048     from cStringIO import StringIO as BytesIO  # python 2
00049 
00050 try:
00051     import urllib.parse as urllib_parse  # py3
00052 except ImportError:
00053     import urllib as urllib_parse
00054 
00055 # PEP 3333 specifies that WSGI on python 3 generally deals with byte strings
00056 # that are smuggled inside objects of type unicode (via the latin1 encoding).
00057 # These functions are like those in the tornado.escape module, but defined
00058 # here to minimize the temptation to use them in non-wsgi contexts.
00059 if str is unicode_type:
00060     def to_wsgi_str(s):
00061         assert isinstance(s, bytes_type)
00062         return s.decode('latin1')
00063 
00064     def from_wsgi_str(s):
00065         assert isinstance(s, str)
00066         return s.encode('latin1')
00067 else:
00068     def to_wsgi_str(s):
00069         assert isinstance(s, bytes_type)
00070         return s
00071 
00072     def from_wsgi_str(s):
00073         assert isinstance(s, str)
00074         return s
00075 
00076 
00077 class WSGIApplication(web.Application):
00078     """A WSGI equivalent of `tornado.web.Application`.
00079 
00080     .. deprecated:: 4.0
00081 
00082        Use a regular `.Application` and wrap it in `WSGIAdapter` instead.
00083     """
00084     def __call__(self, environ, start_response):
00085         return WSGIAdapter(self)(environ, start_response)
00086 
00087 
00088 # WSGI has no facilities for flow control, so just return an already-done
00089 # Future when the interface requires it.
00090 _dummy_future = Future()
00091 _dummy_future.set_result(None)
00092 
00093 
00094 class _WSGIConnection(httputil.HTTPConnection):
00095     def __init__(self, method, start_response, context):
00096         self.method = method
00097         self.start_response = start_response
00098         self.context = context
00099         self._write_buffer = []
00100         self._finished = False
00101         self._expected_content_remaining = None
00102         self._error = None
00103 
00104     def set_close_callback(self, callback):
00105         # WSGI has no facility for detecting a closed connection mid-request,
00106         # so we can simply ignore the callback.
00107         pass
00108 
00109     def write_headers(self, start_line, headers, chunk=None, callback=None):
00110         if self.method == 'HEAD':
00111             self._expected_content_remaining = 0
00112         elif 'Content-Length' in headers:
00113             self._expected_content_remaining = int(headers['Content-Length'])
00114         else:
00115             self._expected_content_remaining = None
00116         self.start_response(
00117             '%s %s' % (start_line.code, start_line.reason),
00118             [(native_str(k), native_str(v)) for (k, v) in headers.get_all()])
00119         if chunk is not None:
00120             self.write(chunk, callback)
00121         elif callback is not None:
00122             callback()
00123         return _dummy_future
00124 
00125     def write(self, chunk, callback=None):
00126         if self._expected_content_remaining is not None:
00127             self._expected_content_remaining -= len(chunk)
00128             if self._expected_content_remaining < 0:
00129                 self._error = httputil.HTTPOutputError(
00130                     "Tried to write more data than Content-Length")
00131                 raise self._error
00132         self._write_buffer.append(chunk)
00133         if callback is not None:
00134             callback()
00135         return _dummy_future
00136 
00137     def finish(self):
00138         if (self._expected_content_remaining is not None and
00139                 self._expected_content_remaining != 0):
00140             self._error = httputil.HTTPOutputError(
00141                 "Tried to write %d bytes less than Content-Length" %
00142                 self._expected_content_remaining)
00143             raise self._error
00144         self._finished = True
00145 
00146 
00147 class _WSGIRequestContext(object):
00148     def __init__(self, remote_ip, protocol):
00149         self.remote_ip = remote_ip
00150         self.protocol = protocol
00151 
00152     def __str__(self):
00153         return self.remote_ip
00154 
00155 
00156 class WSGIAdapter(object):
00157     """Converts a `tornado.web.Application` instance into a WSGI application.
00158 
00159     Example usage::
00160 
00161         import tornado.web
00162         import tornado.wsgi
00163         import wsgiref.simple_server
00164 
00165         class MainHandler(tornado.web.RequestHandler):
00166             def get(self):
00167                 self.write("Hello, world")
00168 
00169         if __name__ == "__main__":
00170             application = tornado.web.Application([
00171                 (r"/", MainHandler),
00172             ])
00173             wsgi_app = tornado.wsgi.WSGIAdapter(application)
00174             server = wsgiref.simple_server.make_server('', 8888, wsgi_app)
00175             server.serve_forever()
00176 
00177     See the `appengine demo
00178     <https://github.com/tornadoweb/tornado/tree/stable/demos/appengine>`_
00179     for an example of using this module to run a Tornado app on Google
00180     App Engine.
00181 
00182     In WSGI mode asynchronous methods are not supported.  This means
00183     that it is not possible to use `.AsyncHTTPClient`, or the
00184     `tornado.auth` or `tornado.websocket` modules.
00185 
00186     .. versionadded:: 4.0
00187     """
00188     def __init__(self, application):
00189         if isinstance(application, WSGIApplication):
00190             self.application = lambda request: web.Application.__call__(
00191                 application, request)
00192         else:
00193             self.application = application
00194 
00195     def __call__(self, environ, start_response):
00196         method = environ["REQUEST_METHOD"]
00197         uri = urllib_parse.quote(from_wsgi_str(environ.get("SCRIPT_NAME", "")))
00198         uri += urllib_parse.quote(from_wsgi_str(environ.get("PATH_INFO", "")))
00199         if environ.get("QUERY_STRING"):
00200             uri += "?" + environ["QUERY_STRING"]
00201         headers = httputil.HTTPHeaders()
00202         if environ.get("CONTENT_TYPE"):
00203             headers["Content-Type"] = environ["CONTENT_TYPE"]
00204         if environ.get("CONTENT_LENGTH"):
00205             headers["Content-Length"] = environ["CONTENT_LENGTH"]
00206         for key in environ:
00207             if key.startswith("HTTP_"):
00208                 headers[key[5:].replace("_", "-")] = environ[key]
00209         if headers.get("Content-Length"):
00210             body = environ["wsgi.input"].read(
00211                 int(headers["Content-Length"]))
00212         else:
00213             body = ""
00214         protocol = environ["wsgi.url_scheme"]
00215         remote_ip = environ.get("REMOTE_ADDR", "")
00216         if environ.get("HTTP_HOST"):
00217             host = environ["HTTP_HOST"]
00218         else:
00219             host = environ["SERVER_NAME"]
00220         connection = _WSGIConnection(method, start_response,
00221                                      _WSGIRequestContext(remote_ip, protocol))
00222         request = httputil.HTTPServerRequest(
00223             method, uri, "HTTP/1.1", headers=headers, body=body,
00224             host=host, connection=connection)
00225         request._parse_body()
00226         self.application(request)
00227         if connection._error:
00228             raise connection._error
00229         if not connection._finished:
00230             raise Exception("request did not finish synchronously")
00231         return connection._write_buffer
00232 
00233 
00234 class WSGIContainer(object):
00235     r"""Makes a WSGI-compatible function runnable on Tornado's HTTP server.
00236 
00237     .. warning::
00238 
00239        WSGI is a *synchronous* interface, while Tornado's concurrency model
00240        is based on single-threaded asynchronous execution.  This means that
00241        running a WSGI app with Tornado's `WSGIContainer` is *less scalable*
00242        than running the same app in a multi-threaded WSGI server like
00243        ``gunicorn`` or ``uwsgi``.  Use `WSGIContainer` only when there are
00244        benefits to combining Tornado and WSGI in the same process that
00245        outweigh the reduced scalability.
00246 
00247     Wrap a WSGI function in a `WSGIContainer` and pass it to `.HTTPServer` to
00248     run it. For example::
00249 
00250         def simple_app(environ, start_response):
00251             status = "200 OK"
00252             response_headers = [("Content-type", "text/plain")]
00253             start_response(status, response_headers)
00254             return ["Hello world!\n"]
00255 
00256         container = tornado.wsgi.WSGIContainer(simple_app)
00257         http_server = tornado.httpserver.HTTPServer(container)
00258         http_server.listen(8888)
00259         tornado.ioloop.IOLoop.instance().start()
00260 
00261     This class is intended to let other frameworks (Django, web.py, etc)
00262     run on the Tornado HTTP server and I/O loop.
00263 
00264     The `tornado.web.FallbackHandler` class is often useful for mixing
00265     Tornado and WSGI apps in the same server.  See
00266     https://github.com/bdarnell/django-tornado-demo for a complete example.
00267     """
00268     def __init__(self, wsgi_application):
00269         self.wsgi_application = wsgi_application
00270 
00271     def __call__(self, request):
00272         data = {}
00273         response = []
00274 
00275         def start_response(status, response_headers, exc_info=None):
00276             data["status"] = status
00277             data["headers"] = response_headers
00278             return response.append
00279         app_response = self.wsgi_application(
00280             WSGIContainer.environ(request), start_response)
00281         try:
00282             response.extend(app_response)
00283             body = b"".join(response)
00284         finally:
00285             if hasattr(app_response, "close"):
00286                 app_response.close()
00287         if not data:
00288             raise Exception("WSGI app did not call start_response")
00289 
00290         status_code = int(data["status"].split()[0])
00291         headers = data["headers"]
00292         header_set = set(k.lower() for (k, v) in headers)
00293         body = escape.utf8(body)
00294         if status_code != 304:
00295             if "content-length" not in header_set:
00296                 headers.append(("Content-Length", str(len(body))))
00297             if "content-type" not in header_set:
00298                 headers.append(("Content-Type", "text/html; charset=UTF-8"))
00299         if "server" not in header_set:
00300             headers.append(("Server", "TornadoServer/%s" % tornado.version))
00301 
00302         parts = [escape.utf8("HTTP/1.1 " + data["status"] + "\r\n")]
00303         for key, value in headers:
00304             parts.append(escape.utf8(key) + b": " + escape.utf8(value) + b"\r\n")
00305         parts.append(b"\r\n")
00306         parts.append(body)
00307         request.write(b"".join(parts))
00308         request.finish()
00309         self._log(status_code, request)
00310 
00311     @staticmethod
00312     def environ(request):
00313         """Converts a `tornado.httputil.HTTPServerRequest` to a WSGI environment.
00314         """
00315         hostport = request.host.split(":")
00316         if len(hostport) == 2:
00317             host = hostport[0]
00318             port = int(hostport[1])
00319         else:
00320             host = request.host
00321             port = 443 if request.protocol == "https" else 80
00322         environ = {
00323             "REQUEST_METHOD": request.method,
00324             "SCRIPT_NAME": "",
00325             "PATH_INFO": to_wsgi_str(escape.url_unescape(
00326                 request.path, encoding=None, plus=False)),
00327             "QUERY_STRING": request.query,
00328             "REMOTE_ADDR": request.remote_ip,
00329             "SERVER_NAME": host,
00330             "SERVER_PORT": str(port),
00331             "SERVER_PROTOCOL": request.version,
00332             "wsgi.version": (1, 0),
00333             "wsgi.url_scheme": request.protocol,
00334             "wsgi.input": BytesIO(escape.utf8(request.body)),
00335             "wsgi.errors": sys.stderr,
00336             "wsgi.multithread": False,
00337             "wsgi.multiprocess": True,
00338             "wsgi.run_once": False,
00339         }
00340         if "Content-Type" in request.headers:
00341             environ["CONTENT_TYPE"] = request.headers.pop("Content-Type")
00342         if "Content-Length" in request.headers:
00343             environ["CONTENT_LENGTH"] = request.headers.pop("Content-Length")
00344         for key, value in request.headers.items():
00345             environ["HTTP_" + key.replace("-", "_").upper()] = value
00346         return environ
00347 
00348     def _log(self, status_code, request):
00349         if status_code < 400:
00350             log_method = access_log.info
00351         elif status_code < 500:
00352             log_method = access_log.warning
00353         else:
00354             log_method = access_log.error
00355         request_time = 1000.0 * request.request_time()
00356         summary = request.method + " " + request.uri + " (" + \
00357             request.remote_ip + ")"
00358         log_method("%d %s %.2fms", status_code, summary, request_time)
00359 
00360 
00361 HTTPRequest = httputil.HTTPServerRequest


rosbridge_server
Author(s): Jonathan Mace
autogenerated on Thu Jun 6 2019 21:51:51