diff options
Diffstat (limited to 'vendor/Twisted-10.0.0/twisted/web/wsgi.py')
-rw-r--r-- | vendor/Twisted-10.0.0/twisted/web/wsgi.py | 401 |
1 files changed, 401 insertions, 0 deletions
diff --git a/vendor/Twisted-10.0.0/twisted/web/wsgi.py b/vendor/Twisted-10.0.0/twisted/web/wsgi.py new file mode 100644 index 0000000000..cb18de379b --- /dev/null +++ b/vendor/Twisted-10.0.0/twisted/web/wsgi.py @@ -0,0 +1,401 @@ +# Copyright (c) 2008-2009 Twisted Matrix Laboratories. +# See LICENSE for details. + +""" +An implementation of +U{Web Resource Gateway Interface<http://www.python.org/dev/peps/pep-0333/>}. +""" + +__metaclass__ = type + +from sys import exc_info + +from zope.interface import implements + +from twisted.python.log import msg, err +from twisted.python.failure import Failure +from twisted.web.resource import IResource +from twisted.web.server import NOT_DONE_YET +from twisted.web.http import INTERNAL_SERVER_ERROR + + +class _ErrorStream: + """ + File-like object instances of which are used as the value for the + C{'wsgi.errors'} key in the C{environ} dictionary passed to the application + object. + + This simply passes writes on to L{logging<twisted.python.log>} system as + error events from the C{'wsgi'} system. In the future, it may be desirable + to expose more information in the events it logs, such as the application + object which generated the message. + """ + def write(self, bytes): + """ + Generate an event for the logging system with the given bytes as the + message. + + This is called in a WSGI application thread, not the I/O thread. + """ + msg(bytes, system='wsgi', isError=True) + + + def writelines(self, iovec): + """ + Join the given lines and pass them to C{write} to be handled in the + usual way. + + This is called in a WSGI application thread, not the I/O thread. + + @param iovec: A C{list} of C{'\\n'}-terminated C{str} which will be + logged. + """ + self.write(''.join(iovec)) + + + def flush(self): + """ + Nothing is buffered, so flushing does nothing. This method is required + to exist by PEP 333, though. + + This is called in a WSGI application thread, not the I/O thread. + """ + + + +class _InputStream: + """ + File-like object instances of which are used as the value for the + C{'wsgi.input'} key in the C{environ} dictionary passed to the application + object. + + This only exists to make the handling of C{readline(-1)} consistent across + different possible underlying file-like object implementations. The other + supported methods pass through directly to the wrapped object. + """ + def __init__(self, input): + """ + Initialize the instance. + + This is called in the I/O thread, not a WSGI application thread. + """ + self._wrapped = input + + + def read(self, size=None): + """ + Pass through to the underlying C{read}. + + This is called in a WSGI application thread, not the I/O thread. + """ + # Avoid passing None because cStringIO and file don't like it. + if size is None: + return self._wrapped.read() + return self._wrapped.read(size) + + + def readline(self, size=None): + """ + Pass through to the underlying C{readline}, with a size of C{-1} replaced + with a size of C{None}. + + This is called in a WSGI application thread, not the I/O thread. + """ + # Check for -1 because StringIO doesn't handle it correctly. Check for + # None because files and tempfiles don't accept that. + if size == -1 or size is None: + return self._wrapped.readline() + return self._wrapped.readline(size) + + + def readlines(self, size=None): + """ + Pass through to the underlying C{readlines}. + + This is called in a WSGI application thread, not the I/O thread. + """ + # Avoid passing None because cStringIO and file don't like it. + if size is None: + return self._wrapped.readlines() + return self._wrapped.readlines(size) + + + def __iter__(self): + """ + Pass through to the underlying C{__iter__}. + + This is called in a WSGI application thread, not the I/O thread. + """ + return iter(self._wrapped) + + + +class _WSGIResponse: + """ + Helper for L{WSGIResource} which drives the WSGI application using a + threadpool and hooks it up to the L{Request}. + + @ivar started: A C{bool} indicating whether or not the response status and + headers have been written to the request yet. This may only be read or + written in the WSGI application thread. + + @ivar reactor: An L{IReactorThreads} provider which is used to call methods + on the request in the I/O thread. + + @ivar threadpool: A L{ThreadPool} which is used to call the WSGI + application object in a non-I/O thread. + + @ivar application: The WSGI application object. + + @ivar request: The L{Request} upon which the WSGI environment is based and + to which the application's output will be sent. + + @ivar environ: The WSGI environment C{dict}. + + @ivar status: The HTTP response status C{str} supplied to the WSGI + I{start_response} callable by the application. + + @ivar headers: A list of HTTP response headers supplied to the WSGI + I{start_response} callable by the application. + + @ivar _requestFinished: A flag which indicates whether it is possible to + generate more response data or not. This is C{False} until + L{Request.notifyFinish} tells us the request is done, then C{True}. + """ + + _requestFinished = False + + def __init__(self, reactor, threadpool, application, request): + self.started = False + self.reactor = reactor + self.threadpool = threadpool + self.application = application + self.request = request + self.request.notifyFinish().addBoth(self._finished) + + if request.prepath: + scriptName = '/' + '/'.join(request.prepath) + else: + scriptName = '' + + if request.postpath: + pathInfo = '/' + '/'.join(request.postpath) + else: + pathInfo = '' + + parts = request.uri.split('?', 1) + if len(parts) == 1: + queryString = '' + else: + queryString = parts[1] + + self.environ = { + 'REQUEST_METHOD': request.method, + 'REMOTE_ADDR': request.getClientIP(), + 'SCRIPT_NAME': scriptName, + 'PATH_INFO': pathInfo, + 'QUERY_STRING': queryString, + 'CONTENT_TYPE': request.getHeader('content-type') or '', + 'CONTENT_LENGTH': request.getHeader('content-length') or '', + 'SERVER_NAME': request.getRequestHostname(), + 'SERVER_PORT': str(request.getHost().port), + 'SERVER_PROTOCOL': request.clientproto} + + for name, values in request.requestHeaders.getAllRawHeaders(): + name = 'HTTP_' + name.upper().replace('-', '_') + # It might be preferable for http.HTTPChannel to clear out + # newlines. + self.environ[name] = ','.join([ + v.replace('\n', ' ') for v in values]) + + self.environ.update({ + 'wsgi.version': (1, 0), + 'wsgi.url_scheme': request.isSecure() and 'https' or 'http', + 'wsgi.run_once': False, + 'wsgi.multithread': True, + 'wsgi.multiprocess': False, + 'wsgi.errors': _ErrorStream(), + # Attend: request.content was owned by the I/O thread up until + # this point. By wrapping it and putting the result into the + # environment dictionary, it is effectively being given to + # another thread. This means that whatever it is, it has to be + # safe to access it from two different threads. The access + # *should* all be serialized (first the I/O thread writes to + # it, then the WSGI thread reads from it, then the I/O thread + # closes it). However, since the request is made available to + # arbitrary application code during resource traversal, it's + # possible that some other code might decide to use it in the + # I/O thread concurrently with its use in the WSGI thread. + # More likely than not, this will break. This seems like an + # unlikely possibility to me, but if it is to be allowed, + # something here needs to change. -exarkun + 'wsgi.input': _InputStream(request.content)}) + + + def _finished(self, ignored): + """ + Record the end of the response generation for the request being + serviced. + """ + self._requestFinished = True + + + def startResponse(self, status, headers, excInfo=None): + """ + The WSGI I{start_response} callable. The given values are saved until + they are needed to generate the response. + + This will be called in a non-I/O thread. + """ + if self.started and excInfo is not None: + raise excInfo[0], excInfo[1], excInfo[2] + self.status = status + self.headers = headers + return self.write + + + def write(self, bytes): + """ + The WSGI I{write} callable returned by the I{start_response} callable. + The given bytes will be written to the response body, possibly flushing + the status and headers first. + + This will be called in a non-I/O thread. + """ + def wsgiWrite(started): + if not started: + self._sendResponseHeaders() + self.request.write(bytes) + self.reactor.callFromThread(wsgiWrite, self.started) + self.started = True + + + def _sendResponseHeaders(self): + """ + Set the response code and response headers on the request object, but + do not flush them. The caller is responsible for doing a write in + order for anything to actually be written out in response to the + request. + + This must be called in the I/O thread. + """ + code, message = self.status.split(None, 1) + code = int(code) + self.request.setResponseCode(code, message) + + # twisted.web.server.Request.process always addes a content-type + # response header. That's not appropriate for us. + self.request.responseHeaders.removeHeader('content-type') + + for name, value in self.headers: + # Don't allow the application to control these required headers. + if name.lower() not in ('server', 'date'): + self.request.responseHeaders.addRawHeader(name, value) + + + def start(self): + """ + Start the WSGI application in the threadpool. + + This must be called in the I/O thread. + """ + self.threadpool.callInThread(self.run) + + + def run(self): + """ + Call the WSGI application object, iterate it, and handle its output. + + This must be called in a non-I/O thread (ie, a WSGI application + thread). + """ + try: + appIterator = self.application(self.environ, self.startResponse) + for elem in appIterator: + if elem: + self.write(elem) + if self._requestFinished: + break + close = getattr(appIterator, 'close', None) + if close is not None: + close() + except: + def wsgiError(started, type, value, traceback): + err(Failure(value, type, traceback), "WSGI application error") + if started: + self.request.transport.loseConnection() + else: + self.request.setResponseCode(INTERNAL_SERVER_ERROR) + self.request.finish() + self.reactor.callFromThread(wsgiError, self.started, *exc_info()) + else: + def wsgiFinish(started): + if not self._requestFinished: + if not started: + self._sendResponseHeaders() + self.request.finish() + self.reactor.callFromThread(wsgiFinish, self.started) + self.started = True + + + +class WSGIResource: + """ + An L{IResource} implementation which delegates responsibility for all + resources hierarchically inferior to it to a WSGI application. + + @ivar _reactor: An L{IReactorThreads} provider which will be passed on to + L{_WSGIResponse} to schedule calls in the I/O thread. + + @ivar _threadpool: A L{ThreadPool} which will be passed on to + L{_WSGIResponse} to run the WSGI application object. + + @ivar _application: The WSGI application object. + """ + implements(IResource) + + # Further resource segments are left up to the WSGI application object to + # handle. + isLeaf = True + + def __init__(self, reactor, threadpool, application): + self._reactor = reactor + self._threadpool = threadpool + self._application = application + + + def render(self, request): + """ + Turn the request into the appropriate C{environ} C{dict} suitable to be + passed to the WSGI application object and then pass it on. + + The WSGI application object is given almost complete control of the + rendering process. C{NOT_DONE_YET} will always be returned in order + and response completion will be dictated by the application object, as + will the status, headers, and the response body. + """ + response = _WSGIResponse( + self._reactor, self._threadpool, self._application, request) + response.start() + return NOT_DONE_YET + + + def getChildWithDefault(self, name, request): + """ + Reject attempts to retrieve a child resource. All path segments beyond + the one which refers to this resource are handled by the WSGI + application object. + """ + raise RuntimeError("Cannot get IResource children from WSGIResource") + + + def putChild(self, path, child): + """ + Reject attempts to add a child resource to this resource. The WSGI + application object handles all path segments beneath this resource, so + L{IResource} children can never be found. + """ + raise RuntimeError("Cannot put IResource children under WSGIResource") + + +__all__ = ['WSGIResource'] |