diff --git a/debian/control b/debian/control index 018067b..f60f4c7 100644 --- a/debian/control +++ b/debian/control @@ -1,35 +1,35 @@ Source: sylkserver Section: net Priority: optional Maintainer: Saul Ibarra Uploaders: Dan Pascu , Adrian Georgescu Build-Depends: debhelper (>= 7.3.5), dh-python, dh-systemd, python-all (>= 2.7) Standards-Version: 3.9.6 Package: sylkserver Architecture: all -Depends: ${python:Depends}, ${misc:Depends}, python-application (>= 1.4.0), python-eventlib, python-lxml, python-sipsimple (>= 2.6.0), python-twisted-web, python-werkzeug +Depends: ${python:Depends}, ${misc:Depends}, python-application (>= 1.4.0), python-eventlib, python-lxml, python-sipsimple (>= 2.6.0), python-twisted, python-klein Suggests: libavahi-compat-libdnssd1, python-twisted-words, python-wokkel (>= 0.7.0), sylkserver-webrtc-gateway Recommends: sylkserver-sounds Description: A state of the art, extensible SIP Application Server SylkServer is an application server that can be programmed to perform SIP end-point applications and act as a gateway between SIP and XMPP domains. Package: sylkserver-sounds Architecture: all Depends: ${misc:Depends}, sylkserver Description: A state of the art, extensible SIP Application Server SylkServer is an application server that can be programmed to perform SIP end-point applications and act as a gateway between SIP and XMPP domains. . This package contains sounds used by SylkServer. Package: sylkserver-webrtc-gateway Architecture: all Depends: ${misc:Depends}, sylkserver, janus (>= 0.1.0), python-autobahn (>= 0.10.3) Description: A state of the art, extensible SIP Application Server SylkServer is an application server that can be programmed to perform SIP end-point applications and act as a gateway between SIP and XMPP domains. . This is a meta-package containing the dependencies required to run the WebRTC gateway application. diff --git a/sylk/web/__init__.py b/sylk/web.py similarity index 98% rename from sylk/web/__init__.py rename to sylk/web.py index c4815b5..4ddff03 100644 --- a/sylk/web/__init__.py +++ b/sylk/web.py @@ -1,81 +1,81 @@ __all__ = ['Klein', 'StaticFileResource', 'WebServer', 'server'] import os from application import log from application.python.types import Singleton +from klein import Klein from twisted.internet import reactor from twisted.internet.ssl import DefaultOpenSSLContextFactory from twisted.web.resource import Resource, NoResource from twisted.web.server import Site from twisted.web.static import File from sylk import __version__ from sylk.configuration import WebServerConfig -from sylk.web.klein import Klein # Set the 'Server' header string which Twisted Web will use import twisted.web.server twisted.web.server.version = b'SylkServer/%s' % __version__ class StaticFileResource(File): def directoryListing(self): return NoResource('Directory listing not available') class RootResource(Resource): isLeaf = True def render_GET(self, request): request.setHeader('Content-Type', 'text/plain') return 'Welcome to SylkServer!' class WebServer(object): __metaclass__ = Singleton def __init__(self): self.base = Resource() self.base.putChild('', RootResource()) self.site = Site(self.base, logPath=os.devnull) self.site.noisy = False self.listener = None @property def url(self): return self.__dict__.get('url', '') def register_resource(self, path, resource): self.base.putChild(path, resource) def start(self): interface = WebServerConfig.local_ip port = WebServerConfig.local_port cert_path = WebServerConfig.certificate.normalized if WebServerConfig.certificate else None if cert_path is not None: if not os.path.isfile(cert_path): log.error('Certificate file %s could not be found' % cert_path) return try: ssl_context = DefaultOpenSSLContextFactory(cert_path, cert_path) except Exception: log.exception('Creating SSL context') log.err() return self.listener = reactor.listenSSL(port, self.site, ssl_context, backlog=511, interface=interface) scheme = 'https' else: self.listener = reactor.listenTCP(port, self.site, backlog=511, interface=interface) scheme = 'http' port = self.listener.getHost().port self.__dict__['url'] = '%s://%s:%d' % (scheme, WebServerConfig.hostname or interface.normalized, port) log.msg('Web server listening for requests on: %s' % self.url) def stop(self): if self.listener is not None: self.listener.stopListening() server = WebServer() diff --git a/sylk/web/klein/__init__.py b/sylk/web/klein/__init__.py deleted file mode 100644 index 78fbc82..0000000 --- a/sylk/web/klein/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ - -# Vendored Klein from: https://github.com/twisted/klein -# Changes from original version: -# - Removed global Klein app -# - Removed Klein.run method -# - Fixed imports to work inside SylkServer - - -from sylk.web.klein.app import Klein - -__all__ = ['Klein'] - diff --git a/sylk/web/klein/app.py b/sylk/web/klein/app.py deleted file mode 100644 index 4f69392..0000000 --- a/sylk/web/klein/app.py +++ /dev/null @@ -1,254 +0,0 @@ -""" -Applications are great. Lets have more of them. -""" -import weakref - -from functools import wraps -from twisted.python.components import registerAdapter -from twisted.web.server import Request -from werkzeug.routing import Map, Rule -from zope.interface import implements - -from sylk.web.klein.resource import KleinResource -from sylk.web.klein.interfaces import IKleinRequest - -__all__ = ['Klein'] - - -def _call(instance, f, *args, **kwargs): - if instance is None: - return f(*args, **kwargs) - - return f(instance, *args, **kwargs) - - -class KleinRequest(object): - implements(IKleinRequest) - - def __init__(self, request): - self.branch_segments = [''] - self.mapper = None - - def url_for(self, *args, **kwargs): - return self.mapper.build(*args, **kwargs) - - -registerAdapter(KleinRequest, Request, IKleinRequest) - - -class Klein(object): - """ - L{Klein} is an object which is responsible for maintaining the routing - configuration of our application. - - @ivar _url_map: A C{werkzeug.routing.Map} object which will be used for - routing resolution. - @ivar _endpoints: A C{dict} mapping endpoint names to handler functions. - """ - - _bound_klein_instances = weakref.WeakKeyDictionary() - - def __init__(self): - self._url_map = Map() - self._endpoints = {} - self._error_handlers = [] - self._instance = None - - - def __eq__(self, other): - if isinstance(other, Klein): - return vars(self) == vars(other) - return NotImplemented - - - def __ne__(self, other): - result = self.__eq__(other) - if result is NotImplemented: - return result - return not result - - - @property - def url_map(self): - """ - Read only property exposing L{Klein._url_map}. - """ - return self._url_map - - - @property - def endpoints(self): - """ - Read only property exposing L{Klein._endpoints}. - """ - return self._endpoints - - - def execute_endpoint(self, endpoint, *args, **kwargs): - """ - Execute the named endpoint with all arguments and possibly a bound - instance. - """ - endpoint_f = self._endpoints[endpoint] - return endpoint_f(self._instance, *args, **kwargs) - - - def execute_error_handler(self, handler, request, failure): - """ - Execute the passed error handler, possibly with a bound instance. - """ - return handler(self._instance, request, failure) - - - def resource(self): - """ - Return an L{IResource} which suitably wraps this app. - - @returns: An L{IResource} - """ - - return KleinResource(self) - - - def __get__(self, instance, owner): - """ - Get an instance of L{Klein} bound to C{instance}. - """ - if instance is None: - return self - - k = self._bound_klein_instances.get(instance) - - if k is None: - k = self.__class__() - k._url_map = self._url_map - k._endpoints = self._endpoints - k._error_handlers = self._error_handlers - k._instance = instance - self._bound_klein_instances[instance] = k - - return k - - - def route(self, url, *args, **kwargs): - """ - Add a new handler for C{url} passing C{args} and C{kwargs} directly to - C{werkzeug.routing.Rule}. The handler function will be passed at least - one argument an L{twisted.web.server.Request} and any keyword arguments - taken from the C{url} pattern. - - :: - @app.route("/") - def index(request): - return "Hello" - - @param url: A werkzeug URL pattern given to C{werkzeug.routing.Rule}. - @type url: str - - @param branch: A bool indiciated if a branch endpoint should - be added that allows all child path segments that don't - match some other route to be consumed. Default C{False}. - @type branch: bool - - - @returns: decorated handler function. - """ - segment_count = url.count('/') - if url.endswith('/'): - segment_count -= 1 - - def deco(f): - kwargs.setdefault('endpoint', f.__name__) - if kwargs.pop('branch', False): - branchKwargs = kwargs.copy() - branchKwargs['endpoint'] = branchKwargs['endpoint'] + '_branch' - - @wraps(f) - def branch_f(instance, request, *a, **kw): - IKleinRequest(request).branch_segments = kw.pop('__rest__', '').split('/') - return _call(instance, f, request, *a, **kw) - - branch_f.segment_count = segment_count - - self._endpoints[branchKwargs['endpoint']] = branch_f - self._url_map.add(Rule(url.rstrip('/') + '/' + '', *args, **branchKwargs)) - - @wraps(f) - def _f(instance, request, *a, **kw): - return _call(instance, f, request, *a, **kw) - - _f.segment_count = segment_count - - self._endpoints[kwargs['endpoint']] = _f - self._url_map.add(Rule(url, *args, **kwargs)) - return f - - return deco - - - def handle_errors(self, f_or_exception, *additional_exceptions): - """ - Register an error handler. This decorator supports two syntaxes. The - simpler of these can be used to register a handler for all C{Exception} - types:: - - @app.handle_errors - def error_handler(request, failure): - request.setResponseCode(500) - return 'Uh oh' - - Alternately, a handler can be registered for one or more specific - C{Exception} tyes:: - - @app.handle_errors(EncodingError, ValidationError): - def error_handler(request, failure) - request.setResponseCode(400) - return failure.getTraceback() - - The handler will be passed a L{twisted.web.server.Request} as well as a - L{twisted.python.failure.Failure} instance. Error handlers may return a - deferred, a failure or a response body. - - If more than one error handler is registered, the handlers will be - executed in the order in which they are defined, until a handler is - encountered which completes successfully. If no handler completes - successfully, L{twisted.web.server.Request}'s processingFailed() method - will be called. - - In addition to handling errors that occur within a route handler, error - handlers also handle any C{werkzeug.exceptions.HTTPException} which is - raised during routing. In particular, C{werkzeug.exceptions.NotFound} - will be raised if no matching route is found, so to return a custom 404 - users can do the following:: - - @app.handle_errors(NotFound) - def error_handler(request, failure): - request.setResponseCode(404) - return 'Not found' - - @param f_or_exception: An error handler function, or an C{Exception} - subclass to scope the decorated handler to. - @type f_or_exception: C{function} or C{Exception} - - @param additional_exceptions Additional C{Exception} subclasses to - scope the decorated function to. - @type additional_exceptions C{list} of C{Exception}s - - @returns: decorated error handler function. - """ - # Try to detect calls using the "simple" @app.handle_error syntax by - # introspecting the first argument - if it isn't a type which - # subclasses Exception we assume the simple syntax was used. - if not isinstance(f_or_exception, type) or not issubclass(f_or_exception, Exception): - return self.handle_errors(Exception)(f_or_exception) - - def deco(f): - @wraps(f) - def _f(instance, request, failure): - return _call(instance, f, request, failure) - - self._error_handlers.append(([f_or_exception] + list(additional_exceptions), _f)) - return _f - - return deco - diff --git a/sylk/web/klein/interfaces.py b/sylk/web/klein/interfaces.py deleted file mode 100644 index f8fc92c..0000000 --- a/sylk/web/klein/interfaces.py +++ /dev/null @@ -1,12 +0,0 @@ - -from zope.interface import Interface, Attribute - - -class IKleinRequest(Interface): - branch_segments = Attribute("Segments consumed by a branch route.") - mapper = Attribute("L{werkzeug.routing.MapAdapter}") - - def url_for(self, endpoint, values=None, method=None, force_external=False, append_unknown=True): - """ - L{werkzeug.routing.MapAdapter.build} - """ diff --git a/sylk/web/klein/resource.py b/sylk/web/klein/resource.py deleted file mode 100644 index 52fc230..0000000 --- a/sylk/web/klein/resource.py +++ /dev/null @@ -1,262 +0,0 @@ -from twisted.internet import defer -from twisted.python import log, failure -from twisted.web import server -from twisted.web.iweb import IRenderable -from twisted.web.resource import Resource, IResource, getChildForRequest -from twisted.web.template import flattenString -from werkzeug.exceptions import HTTPException - -from sylk.web.klein.interfaces import IKleinRequest - - - -__all__ = ["KleinResource", "ensure_utf8_bytes"] - - - -def ensure_utf8_bytes(v): - """ - Coerces a value which is either a C{unicode} or C{str} to a C{str}. - If ``v`` is a C{unicode} object it is encoded as utf-8. - """ - if isinstance(v, unicode): - v = v.encode("utf-8") - return v - - - -class _StandInResource(object): - """ - A standin for a Resource. - - This is a sentinel value for L{KleinResource}, to say that we are rendering - a L{Resource}, which may close the connection itself later. - """ - - - -class _URLDecodeError(Exception): - """ - Raised if one or more string parts of the URL could not be decoded. - """ - __slots__ = ["errors"] - - def __init__(self, errors): - """ - @param errors: List of decoding errors. - @type errors: L{list} of L{tuple} of L{str}, - L{twisted.python.failure.Failure} - """ - self.errors = errors - - def __repr__(self): - return "".format(self.errors) - - - -def _extractURLparts(request): - """ - Extracts and decodes URI parts from C{request}. - - All strings must be UTF8-decodable. - - @param request: A Twisted Web request. - @type request: L{twisted.web.iweb.IRequest} - - @raise URLDecodeError: If one of the parts could not be decoded as UTF-8. - - @return: L{tuple} of the URL scheme, the server name, the server port, the - path info and the script name. - @rtype: L{tuple} of L{unicode}, L{unicode}, L{int}, L{unicode}, L{unicode} - """ - 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) - script_name = '' - if request.prepath: - script_name = '/'.join(request.prepath) - - if not script_name.startswith('/'): - script_name = '/' + script_name - - path_info = '' - if request.postpath: - path_info = '/'.join(request.postpath) - - if not path_info.startswith('/'): - path_info = '/' + path_info - - url_scheme = u'https' if request.isSecure() else u'http' - - utf8Failures = [] - try: - server_name = server_name.decode("utf-8") - except UnicodeDecodeError: - utf8Failures.append(("SERVER_NAME", failure.Failure())) - try: - path_info = path_info.decode("utf-8") - except UnicodeDecodeError: - utf8Failures.append(("PATH_INFO", failure.Failure())) - try: - script_name = script_name.decode("utf-8") - except UnicodeDecodeError: - utf8Failures.append(("SCRIPT_NAME", failure.Failure())) - - if utf8Failures: - raise _URLDecodeError(utf8Failures) - - return url_scheme, server_name, server_port, path_info, script_name - - - -class KleinResource(Resource): - """ - A ``Resource`` that can do URL routing. - """ - isLeaf = True - - - def __init__(self, app): - Resource.__init__(self) - self._app = app - - - def __eq__(self, other): - if isinstance(other, KleinResource): - return vars(self) == vars(other) - return NotImplemented - - - def __ne__(self, other): - result = self.__eq__(other) - if result is NotImplemented: - return result - return not result - - - def render(self, request): - # Stuff we need to know for the mapper. - try: - url_scheme, server_name, server_port, path_info, script_name = \ - _extractURLparts(request) - except _URLDecodeError as e: - for what, fail in e.errors: - log.err(fail, "Invalid encoding in {what}.".format(what=what)) - request.setResponseCode(400) - return b"Non-UTF-8 encoding in URL." - - # Bind our mapper. - mapper = self._app.url_map.bind( - server_name, - script_name, - path_info=path_info, - default_method=request.method, - url_scheme=url_scheme, - ) - # Make the mapper available to the view. - kleinRequest = IKleinRequest(request) - kleinRequest.mapper = mapper - - # Make sure we'll notice when the connection goes away unambiguously. - request_finished = [False] - - def _finish(result): - request_finished[0] = True - - def _execute(): - # Actually doing the match right here. This can cause an exception - # to percolate up. If that happens it will be handled below in - # processing_failed, either by a user-registered error handler or - # one of our defaults. - (rule, kwargs) = mapper.match(return_rule=True) - endpoint = rule.endpoint - - # Try pretty hard to fix up prepath and postpath. - segment_count = self._app.endpoints[endpoint].segment_count - request.prepath.extend(request.postpath[:segment_count]) - request.postpath = request.postpath[segment_count:] - - request.notifyFinish().addBoth(_finish) - - # Standard Twisted Web stuff. Defer the method action, giving us - # something renderable or printable. Return NOT_DONE_YET and set up - # the incremental renderer. - d = defer.maybeDeferred(self._app.execute_endpoint, - endpoint, - request, - **kwargs) - - request.notifyFinish().addErrback(lambda _: d.cancel()) - - return d - - d = defer.maybeDeferred(_execute) - - def write_response(r): - if r is not _StandInResource: - if isinstance(r, unicode): - r = r.encode('utf-8') - - if r is not None: - request.write(r) - - if not request_finished[0]: - request.finish() - - def process(r): - if IResource.providedBy(r): - request.render(getChildForRequest(r, request)) - return _StandInResource - - if IRenderable.providedBy(r): - return flattenString(request, r).addCallback(process) - - return r - - d.addCallback(process) - - def processing_failed(failure, error_handlers): - # The failure processor writes to the request. If the - # request is already finished we should suppress failure - # processing. We don't return failure here because there - # is no way to surface this failure to the user if the - # request is finished. - if request_finished[0]: - if not failure.check(defer.CancelledError): - log.err(failure, "Unhandled Error Processing Request.") - return - - # If there are no more registered handlers, apply some defaults - if len(error_handlers) == 0: - if failure.check(HTTPException): - he = failure.value - request.setResponseCode(he.code) - resp = he.get_response({}) - - for header, value in resp.headers: - request.setHeader(ensure_utf8_bytes(header), ensure_utf8_bytes(value)) - - return ensure_utf8_bytes(he.get_body({})) - else: - request.processingFailed(failure) - return - - error_handler = error_handlers[0] - - # Each error handler is a tuple of (list_of_exception_types, handler_fn) - if failure.check(*error_handler[0]): - d = defer.maybeDeferred(self._app.execute_error_handler, - error_handler[1], - request, - failure) - - return d.addErrback(processing_failed, error_handlers[1:]) - - return processing_failed(failure, error_handlers[1:]) - - - d.addErrback(processing_failed, self._app._error_handlers) - d.addCallback(write_response).addErrback(log.err, _why="Unhandled Error writing response") - return server.NOT_DONE_YET