summaryrefslogtreecommitdiff
path: root/vendor/Twisted-10.0.0/twisted/web/wsgi.py
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/Twisted-10.0.0/twisted/web/wsgi.py')
-rw-r--r--vendor/Twisted-10.0.0/twisted/web/wsgi.py401
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']