PageRenderTime 78ms CodeModel.GetById 31ms app.highlight 41ms RepoModel.GetById 1ms app.codeStats 0ms

/circuits/web/wsgi.py

https://bitbucket.org/prologic/circuits/
Python | 235 lines | 219 code | 9 blank | 7 comment | 1 complexity | 3a3fd840dde529c8d09b8a0ee53122db MD5 | raw file
  1# Module:   wsgi
  2# Date:     6th November 2008
  3# Author:   James Mills, prologic at shortcircuit dot net dot au
  4
  5"""WSGI Components
  6
  7This module implements WSGI Components.
  8"""
  9
 10try:
 11    from urllib.parse import unquote
 12except ImportError:
 13    from urllib import unquote  # NOQA
 14
 15from operator import itemgetter
 16from traceback import format_tb
 17from types import GeneratorType
 18from sys import exc_info as _exc_info
 19
 20from circuits.tools import tryimport
 21from circuits.core import handler, BaseComponent
 22
 23StringIO = tryimport(("cStringIO", "StringIO", "io"), "StringIO")
 24
 25from .http import HTTP
 26from .events import request
 27from .headers import Headers
 28from .errors import httperror
 29from circuits.web import wrappers
 30from .dispatchers import Dispatcher
 31
 32
 33def create_environ(errors, path, req):
 34    environ = {}
 35    env = environ.__setitem__
 36
 37    env("REQUEST_METHOD", req.method)
 38    env("SERVER_NAME", req.host.split(":", 1)[0])
 39    env("SERVER_PORT", "%i" % (req.server.port or 0))
 40    env("SERVER_PROTOCOL", "HTTP/%d.%d" % req.server.http.protocol)
 41    env("QUERY_STRING", req.qs)
 42    env("SCRIPT_NAME", req.script_name)
 43    env("CONTENT_TYPE", req.headers.get("Content-Type", ""))
 44    env("CONTENT_LENGTH", req.headers.get("Content-Length", ""))
 45    env("REMOTE_ADDR", req.remote.ip)
 46    env("REMOTE_PORT", "%i" % (req.remote.port or 0))
 47    env("wsgi.version", (1, 0))
 48    env("wsgi.input", req.body)
 49    env("wsgi.errors", errors)
 50    env("wsgi.multithread", False)
 51    env("wsgi.multiprocess", False)
 52    env("wsgi.run_once", False)
 53    env("wsgi.url_scheme", req.scheme)
 54
 55    if req.path:
 56        req.script_name = req.path[:len(path)]
 57        req.path = req.path[len(path):]
 58        env("SCRIPT_NAME", req.script_name)
 59        env("PATH_INFO", req.path)
 60
 61    for k, v in list(req.headers.items()):
 62        env("HTTP_%s" % k.upper().replace("-", "_"), v)
 63
 64    return environ
 65
 66
 67class Application(BaseComponent):
 68
 69    channel = "web"
 70
 71    headerNames = {
 72        "HTTP_CGI_AUTHORIZATION": "Authorization",
 73        "CONTENT_LENGTH": "Content-Length",
 74        "CONTENT_TYPE": "Content-Type",
 75        "REMOTE_HOST": "Remote-Host",
 76        "REMOTE_ADDR": "Remote-Addr",
 77    }
 78
 79    def init(self):
 80        self._finished = False
 81
 82        HTTP(self).register(self)
 83        Dispatcher().register(self)
 84
 85    def translateHeaders(self, environ):
 86        for cgiName in environ:
 87            # We assume all incoming header keys are uppercase already.
 88            if cgiName in self.headerNames:
 89                yield self.headerNames[cgiName], environ[cgiName]
 90            elif cgiName[:5] == "HTTP_":
 91                # Hackish attempt at recovering original header names.
 92                translatedHeader = cgiName[5:].replace("_", "-")
 93                yield translatedHeader, environ[cgiName]
 94
 95    def getRequestResponse(self, environ):
 96        env = environ.get
 97
 98        headers = Headers(list(self.translateHeaders(environ)))
 99
100        protocol = tuple(map(int, env("SERVER_PROTOCOL")[5:].split(".")))
101        req = wrappers.Request(
102            None,
103            env("REQUEST_METHOD"),
104            env("wsgi.url_scheme"),
105            env("PATH_INFO", ""),
106            protocol,
107            env("QUERY_STRING", ""),
108            headers=headers
109        )
110
111        req.remote = wrappers.Host(env("REMOTE_ADDR"), env("REMTOE_PORT"))
112        req.script_name = env("SCRIPT_NAME")
113        req.wsgi_environ = environ
114
115        try:
116            cl = int(headers.get("Content-Length", "0"))
117        except:
118            cl = 0
119
120        req.body.write(env("wsgi.input").read(cl))
121        req.body.seek(0)
122
123        res = wrappers.Response(req)
124        res.gzip = "gzip" in req.headers.get("Accept-Encoding", "")
125
126        return req, res
127
128    def __call__(self, environ, start_response, exc_info=None):
129        self.request, self.response = self.getRequestResponse(environ)
130        self.fire(request(self.request, self.response))
131
132        self._finished = False
133        while self or not self._finished:
134            self.tick()
135
136        self.response.prepare()
137        body = self.response.body
138        status = self.response.status
139        headers = list(self.response.headers.items())
140
141        start_response(str(status), headers, exc_info)
142        return body
143
144    @handler("response", channel="web")
145    def response(self, event, response):
146        self._finished = True
147        event.stop()
148
149    @property
150    def host(self):
151        return ""
152
153    @property
154    def port(self):
155        return 0
156
157    @property
158    def secure(self):
159        return False
160
161
162class _Empty(str):
163
164    def __bool__(self):
165        return True
166
167    __nonzero__ = __bool__
168
169empty = _Empty()
170del _Empty
171
172
173class Gateway(BaseComponent):
174
175    channel = "web"
176
177    def init(self, apps):
178        self.apps = apps
179
180        self.errors = dict((k, StringIO()) for k in self.apps.keys())
181
182    @handler("request", priority=0.2)
183    def _on_request(self, event, req, res):
184        if not self.apps:
185            return
186
187        parts = req.path.split("/")
188
189        candidates = []
190        for i in range(len(parts)):
191            k = "/".join(parts[:(i + 1)]) or "/"
192            if k in self.apps:
193                candidates.append((k, self.apps[k]))
194        candidates = sorted(candidates, key=itemgetter(0), reverse=True)
195
196        if not candidates:
197            return
198
199        path, app = candidates[0]
200
201        buffer = StringIO()
202
203        def start_response(status, headers, exc_info=None):
204            res.status = int(status.split(" ", 1)[0])
205            for header in headers:
206                res.headers.add_header(*header)
207            return buffer.write
208
209        errors = self.errors[path]
210
211        environ = create_environ(errors, path, req)
212
213        try:
214            body = app(environ, start_response)
215            if isinstance(body, list):
216                body = "".join(body)
217            elif isinstance(body, GeneratorType):
218                res.body = body
219                res.stream = True
220                return res
221
222            if not body:
223                if not buffer.tell():
224                    return empty
225                else:
226                    buffer.seek(0)
227                    return buffer
228            else:
229                return body
230        except Exception as error:
231            etype, evalue, etraceback = _exc_info()
232            error = (etype, evalue, format_tb(etraceback))
233            return httperror(req, res, 500, error=error)
234        finally:
235            event.stop()