root/xcap/server.py

Revision 482, 7.9 kB (checked in by Denis Bilenko <denis@ag-projects.com>, 3 weeks ago)

fix for "global name 'reactor' is not defined" when starting tls

Line 
1# Copyright (C) 2007 AG Projects.
2#
3
4"""HTTP handling for the XCAP server"""
5
6import sys
7
8from application.configuration.datatypes import StringList
9from application import log
10
11from twisted.web2 import channel, resource, http, responsecode, http_headers, server
12from twisted.cred.portal import Portal
13from twisted.web2.auth import digest, basic
14from twisted.python import failure
15
16from xcap.config import ConfigFile, ConfigSection
17from xcap import authentication
18from xcap.appusage import getApplicationForURI, Backend
19from xcap.resource import XCAPDocument, XCAPElement, XCAPAttribute, XCAPNamespaceBinding
20from xcap.uri import AttributeSelector, NamespaceSelector
21from xcap import __version__ as version
22from xcap.logutil import log_access, log_error
23
24server.VERSION = "OpenXCAP/%s" % version
25
26class AuthenticationConfig(ConfigSection):
27    _datatypes = {'trusted_peers': StringList,
28                  'default_realm': str}
29    type = 'basic'
30    cleartext_passwords = True
31    default_realm = None
32    trusted_peers = []
33
34class ServerConfig(ConfigSection):
35    _datatypes = {'backend': Backend}
36    port = 8000
37    address = '0.0.0.0'
38    root = 'http://127.0.0.1/'
39    backend = Backend('Database')
40
41
42## We use this to overwrite some of the settings above on a local basis if needed
43configuration = ConfigFile()
44configuration.read_settings('Authentication', AuthenticationConfig)
45configuration.read_settings('Server', ServerConfig)
46
47
48class XCAPRoot(resource.Resource, resource.LeafResource):
49    addSlash = True
50
51    def allowedMethods(self):
52        # not used , but methods were already checked by XCAPAuthResource
53        return ('GET', 'PUT', 'DELETE')
54
55    def resourceForURI(self, xcap_uri):
56        application = getApplicationForURI(xcap_uri)
57        if not xcap_uri.node_selector:
58            return XCAPDocument(xcap_uri, application)
59        else:
60            terminal_selector = xcap_uri.node_selector.terminal_selector
61            if isinstance(terminal_selector, AttributeSelector):
62                return XCAPAttribute(xcap_uri, application)
63            elif isinstance(terminal_selector, NamespaceSelector):
64                return XCAPNamespaceBinding(xcap_uri, application)
65            else:
66                return XCAPElement(xcap_uri, application)
67
68    def renderHTTP(self, request):
69        application = getApplicationForURI(request.xcap_uri)
70        if not application:
71            return http.Response(responsecode.NOT_FOUND, stream="Application not supported")
72        resource = self.resourceForURI(request.xcap_uri)
73        return resource.renderHTTP(request)
74
75
76def get_response_body(exc):
77    if hasattr(exc, 'stream') and hasattr(exc.stream, 'mem'):
78        return exc.stream.mem
79    else:
80        return str(exc)
81
82class Request(server.Request):
83
84    def __init__(self, *args, **kw):
85        server.Request.__init__(self, *args, **kw)
86
87    def writeResponse(self, response):
88        reason = getattr(self, '_reason', None)
89        log_access(self, response, reason)
90        try:
91            return server.Request.writeResponse(self, response)
92        finally:
93            if reason is not None:
94                del self._reason
95
96    def _processingFailed(self, reason):
97        # save the reason, it will be used for the stacktrace
98        self._reason = reason
99
100        exc = getattr(reason, 'value', None)
101        if exc:
102            # if the exception has 'http_error' and it is HTTPError, we use it to generate the response.
103            # this allows us to attach http_error to non-HTTPError errors (as opposed to
104            # re-raising HTTPError-derived exception) and enjoy the original stacktraces in the log
105            if not isinstance(exc, http.HTTPError) and hasattr(exc, 'http_error'):
106                http_error = exc.http_error
107                if isinstance(http_error, http.HTTPError):
108                    return server.Request._processingFailed(self, failure.Failure(http_error))
109                elif isinstance(http_error, int):
110                    s = get_response_body(exc)
111                    response = http.Response(http_error,
112                                             {'content-type': http_headers.MimeType('text','plain')},
113                                             stream=s)
114                    fail = failure.Failure(http.HTTPError(response))
115                    return server.Request._processingFailed(self, fail)
116
117        return server.Request._processingFailed(self, reason)
118
119    def renderHTTP_exception(self, req, reason):
120        response = http.Response(
121            responsecode.INTERNAL_SERVER_ERROR,
122            {'content-type': http_headers.MimeType('text','plain')},
123            ("An error occurred while processing the request. "
124             "More information is available in the server log."))
125
126        log_error(req, response, reason)
127        return response
128
129
130class HTTPChannelRequest(channel.http.HTTPChannelRequest):
131    _base = channel.http.HTTPChannelRequest
132
133    def gotInitialLine(self, line):
134        self._initial_line = line
135        return self._base.gotInitialLine(self, line)
136
137    def createRequest(self):
138        self._base.createRequest(self)
139        self.request._initial_line = self._initial_line
140
141
142class HTTPChannel(channel.http.HTTPChannel):
143    chanRequestFactory = HTTPChannelRequest
144
145
146class HTTPFactory(channel.HTTPFactory):
147    noisy = False
148    protocol = HTTPChannel
149
150
151class XCAPSite(server.Site):
152
153    def __call__(self, *args, **kwargs):
154        return Request(site=self, *args, **kwargs)
155
156
157class XCAPServer:
158
159    def __init__(self):
160        portal = Portal(authentication.XCAPAuthRealm())
161        if AuthenticationConfig.cleartext_passwords:
162            http_checker = ServerConfig.backend.PlainPasswordChecker()
163        else:
164            http_checker = ServerConfig.backend.HashPasswordChecker()
165        portal.registerChecker(http_checker)
166        trusted_peers = AuthenticationConfig.trusted_peers
167        if trusted_peers:
168            log.info("Trusted peers: %s" % ", ".join(trusted_peers))
169        portal.registerChecker(authentication.TrustedPeerChecker(trusted_peers))
170
171        auth_type = AuthenticationConfig.type
172        if auth_type == 'basic':
173            credential_factory = basic.BasicCredentialFactory(auth_type)
174        elif auth_type == 'digest':
175            credential_factory = digest.DigestCredentialFactory('MD5', auth_type)
176        else:
177            raise ValueError("Invalid authentication type: '%s'. Please check the configuration." % auth_type)
178
179        root = authentication.XCAPAuthResource(XCAPRoot(),
180                                               (credential_factory,),
181                                               portal, (authentication.IAuthUser,))
182        self.site = XCAPSite(root)
183
184    def _start_https(self, reactor):
185        from xcap.tls import Certificate, PrivateKey
186        class TLSConfig(ConfigSection):
187            _datatypes = {'certificate': Certificate, 'private_key': PrivateKey}
188            certificate = None
189            private_key = None
190
191        configuration.read_settings('TLS', TLSConfig)
192
193        from gnutls.interfaces.twisted import X509Credentials
194        cert, pKey = TLSConfig.certificate, TLSConfig.private_key
195        if cert is None or pKey is None:
196            log.fatal("the TLS certificates or the private key could not be loaded")
197            sys.exit(1)
198        credentials = X509Credentials(cert, pKey)
199        reactor.listenTLS(ServerConfig.port, HTTPFactory(self.site), credentials, interface=ServerConfig.address)
200        log.msg("TLS started")
201
202    def start(self):
203        if 'twisted.internet.reactor' not in sys.modules:
204            from twisted.internet import pollreactor; pollreactor.install()
205        from twisted.internet import reactor
206
207        if ServerConfig.root.startswith('https'):
208            self._start_https(reactor)
209        else:
210            reactor.listenTCP(ServerConfig.port, HTTPFactory(self.site), interface=ServerConfig.address)
211        self.run(reactor)
212
213    def run(self, reactor):
214        reactor.run(installSignalHandlers=ServerConfig.backend.installSignalHandlers)
Note: See TracBrowser for help on using the browser.