Source code for spyne.server.twisted

#
# spyne - Copyright (C) Spyne contributors.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301
#

"""The ``spyne.server.twisted`` module contains a server transport compatible
with the Twisted event loop. It uses the TwistedWebResource object as transport.

Also see the twisted examples in the examples directory of the source
distribution.

If you want to have a hard-coded URL in the wsdl document, this is how to do
it: ::

    resource = TwistedWebResource(...)
    resource.http_transport.doc.wsdl11.build_interface_document("http://example.com")

This is not strictly necessary -- if you don't do this, Spyne will get the
URL from the first request, build the wsdl on-the-fly and cache it as a
string in memory for later requests. However, if you want to make sure
you only have this url on the WSDL, this is how to do it. Note that if
your client takes the information in wsdl seriously, all requests will go
to the designated url above which can make testing a bit difficult. Use
in moderation.

This module is EXPERIMENTAL. Your mileage may vary. Patches are welcome.
"""


from __future__ import absolute_import

import logging
logger = logging.getLogger(__name__)

from twisted.python.log import err
from twisted.internet.interfaces import IPullProducer
from twisted.internet.defer import Deferred
from twisted.web.iweb import UNKNOWN_LENGTH
from twisted.web.resource import Resource
from twisted.web.server import NOT_DONE_YET

from zope.interface import implements

from spyne.auxproc import process_contexts
from spyne.server.http import HttpMethodContext
from spyne.server.http import HttpBase

from spyne.const.ansi_color import LIGHT_GREEN
from spyne.const.ansi_color import END_COLOR
from spyne.const.http import HTTP_404
from spyne.const.http import HTTP_405


def _reconstruct_url(request):
    server_name = request.getRequestHostname()
    server_port = request.getHost().port
    if (bool(request.isSecure()), server_port) not in [(True, 443), (False, 80)]:
        server_name = '%s:%d' % (server_name, server_port)

    if request.isSecure():
        url_scheme = 'https'
    else:
        url_scheme = 'http'

    return ''.join([url_scheme, "://", server_name, request.uri])


class _Producer(object):
    implements(IPullProducer)

    deferred = None

    def __init__(self, body, consumer):
        """:param body: an iterable of strings"""

        # check to see if we can determine the length
        try:
            len(body) # iterator?
            self.length = sum([len(fragment) for fragment in body])
            self.body = iter(body)

        except TypeError:
            self.length = UNKNOWN_LENGTH
            self.body = body

        self.deferred = Deferred()

        self.consumer = consumer

    def resumeProducing(self):
        try:
            chunk = self.body.next()

        except StopIteration, e:
            self.consumer.unregisterProducer()
            if self.deferred is not None:
                self.deferred.callback(self.consumer)
                self.deferred = None
            return

        self.consumer.write(chunk)

    def pauseProducing(self):
        pass

    def stopProducing(self):
        if self.deferred is not None:
            self.deferred.errback(
                               Exception("Consumer asked us to stop producing"))
        self.deferred = None


class TwistedHttpTransport(HttpBase):
[docs] @staticmethod def decompose_incoming_envelope(prot, ctx, message):
[docs] """This function is only called by the HttpRpc protocol to have the twisted web's Request object is parsed into ``ctx.in_body_doc`` and ``ctx.in_header_doc``. """ request = ctx.in_document ctx.method_request_string = '{%s}%s' % (prot.app.interface.get_tns(), request.path.split('/')[-1]) logger.debug("%sMethod name: %r%s" % (LIGHT_GREEN, ctx.method_request_string, END_COLOR)) ctx.in_header_doc = request.headers ctx.in_body_doc = request.args class TwistedWebResource(Resource):
[docs] """A server transport that exposes the application as a twisted web Resource. """ isLeaf = True def __init__(self, app, chunked=False, max_content_length=2 * 1024 * 1024, block_length=8 * 1024): Resource.__init__(self) self.http_transport = TwistedHttpTransport(app, chunked, max_content_length, block_length) self._wsdl = None def render_GET(self, request):
[docs] _ahv = self.http_transport._allowed_http_verbs if request.uri.endswith('.wsdl') or request.uri.endswith('?wsdl'): return self.__handle_wsdl_request(request) elif not (_ahv is None or "GET" in _ahv): request.setResponseCode(405) return HTTP_405 else: return self.handle_rpc(request) def render_POST(self, request):
[docs] return self.handle_rpc(request) def handle_error(self, p_ctx, others, error, request):
[docs] resp_code = self.http_transport.app.out_protocol \ .fault_to_http_response_code(error) request.setResponseCode(int(resp_code[:3])) p_ctx.out_object = error self.http_transport.get_out_string(p_ctx) process_contexts(self.http_transport, others, p_ctx, error=error) retval = ''.join(p_ctx.out_string) p_ctx.close() return retval def handle_rpc(self, request):
[docs] initial_ctx = HttpMethodContext(self.http_transport, request, self.http_transport.app.out_protocol.mime_type) initial_ctx.in_string = [request.content.getvalue()] contexts = self.http_transport.generate_contexts(initial_ctx) p_ctx, others = contexts[0], contexts[1:] if p_ctx.in_error: return self.handle_error(p_ctx, others, p_ctx.in_error, request) else: self.http_transport.get_in_object(p_ctx) if p_ctx.in_error: return self.handle_error(p_ctx, others, p_ctx.in_error, request) else: self.http_transport.get_out_object(p_ctx) if p_ctx.out_error: return self.handle_error(p_ctx, others, p_ctx.out_error, request) def _cb_request_finished(request): request.finish() p_ctx.close() def _eb_request_finished(request): err(request) p_ctx.close() def _cb_deferred(retval, request, cb=True): if cb and len(p_ctx.descriptor.out_message._type_info) <= 1: p_ctx.out_object = [retval] else: p_ctx.out_object = retval self.http_transport.get_out_string(p_ctx) process_contexts(self.http_transport, others, p_ctx) producer = _Producer(p_ctx.out_string, request) producer.deferred.addCallbacks(_cb_request_finished, _eb_request_finished) request.registerProducer(producer, False) def _eb_deferred(retval, request): p_ctx.out_error = retval return self.handle_error(p_ctx, others, p_ctx.out_error, request) ret = p_ctx.out_object[0] if isinstance(ret, Deferred): ret.addCallback(_cb_deferred, request) ret.addErrback(_eb_deferred, request) else: _cb_deferred(p_ctx.out_object, request, cb=False) return NOT_DONE_YET def __handle_wsdl_request(self, request):
ctx = HttpMethodContext(self.http_transport, request, "text/xml; charset=utf-8") url = _reconstruct_url(request) if self.doc.wsdl11 is None: return HTTP_404 if self._wsdl is None: self._wsdl = self.http_transport.doc.wsdl11.get_interface_document() ctx.transport.wsdl = self._wsdl try: if self._wsdl is None: self.http_transport.doc.wsdl11.build_interface_document(url) ctx.transport.wsdl = self._wsdl = \ self.http_transport.doc.wsdl11.get_interface_document() assert ctx.transport.wsdl is not None self.http_transport.event_manager.fire_event('wsdl', ctx) return ctx.transport.wsdl except Exception, e: ctx.transport.wsdl_error = e self.http_transport.event_manager.fire_event('wsdl_exception', ctx) raise finally: ctx.close()