etwist/server.py
branchtls-sprint
changeset 1420 25c13e5b12bd
parent 1263 01152fffd593
child 1520 b097057e629d
equal deleted inserted replaced
1419:7ff24154351d 1420:25c13e5b12bd
    16 from twisted.internet.defer import maybeDeferred
    16 from twisted.internet.defer import maybeDeferred
    17 from twisted.web2 import channel, http, server, iweb
    17 from twisted.web2 import channel, http, server, iweb
    18 from twisted.web2 import static, resource, responsecode
    18 from twisted.web2 import static, resource, responsecode
    19 
    19 
    20 from cubicweb import ObjectNotFound
    20 from cubicweb import ObjectNotFound
    21 from cubicweb.web import (AuthenticationError, NotFound, Redirect, 
    21 from cubicweb.web import (AuthenticationError, NotFound, Redirect,
    22                        RemoteCallFailed, DirectResponse, StatusResponse,
    22                        RemoteCallFailed, DirectResponse, StatusResponse,
    23                        ExplicitLogin)
    23                        ExplicitLogin)
    24 from cubicweb.web.application import CubicWebPublisher
    24 from cubicweb.web.application import CubicWebPublisher
    25 
    25 
    26 from cubicweb.etwist.request import CubicWebTwistedRequestAdapter
    26 from cubicweb.etwist.request import CubicWebTwistedRequestAdapter
    40             except:
    40             except:
    41                 repo.exception('error in looping task')
    41                 repo.exception('error in looping task')
    42         start_task(interval, catch_error_func)
    42         start_task(interval, catch_error_func)
    43     # ensure no tasks will be further added
    43     # ensure no tasks will be further added
    44     repo._looping_tasks = ()
    44     repo._looping_tasks = ()
    45     
    45 
    46 
    46 
    47 class LongTimeExpiringFile(static.File):
    47 class LongTimeExpiringFile(static.File):
    48     """overrides static.File and sets a far futre ``Expires`` date
    48     """overrides static.File and sets a far futre ``Expires`` date
    49     on the resouce.
    49     on the resouce.
    50 
    50 
    69         return d.addCallback(setExpireHeader)
    69         return d.addCallback(setExpireHeader)
    70 
    70 
    71 
    71 
    72 class CubicWebRootResource(resource.PostableResource):
    72 class CubicWebRootResource(resource.PostableResource):
    73     addSlash = False
    73     addSlash = False
    74     
    74 
    75     def __init__(self, config, debug=None):
    75     def __init__(self, config, debug=None):
    76         self.appli = CubicWebPublisher(config, debug=debug)
    76         self.appli = CubicWebPublisher(config, debug=debug)
    77         self.debugmode = debug
    77         self.debugmode = debug
    78         self.config = config
    78         self.config = config
    79         self.base_url = config['base-url'] or config.default_base_url()
    79         self.base_url = config['base-url'] or config.default_base_url()
   101         except ObjectNotFound:
   101         except ObjectNotFound:
   102             self.url_rewriter = None
   102             self.url_rewriter = None
   103         interval = min(config['cleanup-session-time'] or 120,
   103         interval = min(config['cleanup-session-time'] or 120,
   104                        config['cleanup-anonymous-session-time'] or 720) / 2.
   104                        config['cleanup-anonymous-session-time'] or 720) / 2.
   105         start_task(interval, self.appli.session_handler.clean_sessions)
   105         start_task(interval, self.appli.session_handler.clean_sessions)
   106         
   106 
   107     def shutdown_event(self):
   107     def shutdown_event(self):
   108         """callback fired when the server is shutting down to properly
   108         """callback fired when the server is shutting down to properly
   109         clean opened sessions
   109         clean opened sessions
   110         """
   110         """
   111         self.appli.repo.shutdown()
   111         self.appli.repo.shutdown()
   114         """listen for pyro events"""
   114         """listen for pyro events"""
   115         try:
   115         try:
   116             self.pyro_daemon.handleRequests(self.pyro_listen_timeout)
   116             self.pyro_daemon.handleRequests(self.pyro_listen_timeout)
   117         except select.error:
   117         except select.error:
   118             return
   118             return
   119         
   119 
   120     def locateChild(self, request, segments):
   120     def locateChild(self, request, segments):
   121         """Indicate which resource to use to process down the URL's path"""
   121         """Indicate which resource to use to process down the URL's path"""
   122         if segments:
   122         if segments:
   123             if segments[0] == 'https':
   123             if segments[0] == 'https':
   124                 segments = segments[1:]
   124                 segments = segments[1:]
   141                 elif segments[0] == 'fckeditor':
   141                 elif segments[0] == 'fckeditor':
   142                     fckeditordir = self.config.ext_resources['FCKEDITOR_PATH']
   142                     fckeditordir = self.config.ext_resources['FCKEDITOR_PATH']
   143                     return static.File(fckeditordir), segments[1:]
   143                     return static.File(fckeditordir), segments[1:]
   144         # Otherwise we use this single resource
   144         # Otherwise we use this single resource
   145         return self, ()
   145         return self, ()
   146     
   146 
   147     def render(self, request):
   147     def render(self, request):
   148         """Render a page from the root resource"""
   148         """Render a page from the root resource"""
   149         # reload modified files (only in development or debug mode)
   149         # reload modified files (only in development or debug mode)
   150         if self.config.mode == 'dev' or self.debugmode:
   150         if self.config.mode == 'dev' or self.debugmode:
   151             self.appli.vreg.register_objects(self.config.vregistry_path())
   151             self.appli.vreg.register_objects(self.config.vregistry_path())
   152         if self.config['profile']: # default profiler don't trace threads
   152         if self.config['profile']: # default profiler don't trace threads
   153             return self.render_request(request)
   153             return self.render_request(request)
   154         else:
   154         else:
   155             return threads.deferToThread(self.render_request, request)
   155             return threads.deferToThread(self.render_request, request)
   156             
   156 
   157     def render_request(self, request):
   157     def render_request(self, request):
   158         origpath = request.path
   158         origpath = request.path
   159         host = request.host
   159         host = request.host
   160         # dual http/https access handling: expect a rewrite rule to prepend
   160         # dual http/https access handling: expect a rewrite rule to prepend
   161         # 'https' to the path to detect https access
   161         # 'https' to the path to detect https access
   162         if origpath.split('/', 2)[1] == 'https':
   162         if origpath.split('/', 2)[1] == 'https':
   163             origpath = origpath[6:]
   163             origpath = origpath[6:]
   164             request.uri = request.uri[6:]
   164             request.uri = request.uri[6:]
   165             https = True
   165             https = True
   166             baseurl = self.https_url or self.base_url 
   166             baseurl = self.https_url or self.base_url
   167         else:
   167         else:
   168             https = False
   168             https = False
   169             baseurl = self.base_url
   169             baseurl = self.base_url
   170         req = CubicWebTwistedRequestAdapter(request, self.appli.vreg, https, baseurl)
   170         req = CubicWebTwistedRequestAdapter(request, self.appli.vreg, https, baseurl)
   171         if req.authmode == 'http':
   171         if req.authmode == 'http':
   178             return self.request_auth(req)
   178             return self.request_auth(req)
   179         except Redirect, ex:
   179         except Redirect, ex:
   180             return self.redirect(req, ex.location)
   180             return self.redirect(req, ex.location)
   181         if https and req.cnx.anonymous_connection:
   181         if https and req.cnx.anonymous_connection:
   182             # don't allow anonymous on https connection
   182             # don't allow anonymous on https connection
   183             return self.request_auth(req)            
   183             return self.request_auth(req)
   184         if self.url_rewriter is not None:
   184         if self.url_rewriter is not None:
   185             # XXX should occur before authentication?
   185             # XXX should occur before authentication?
   186             try:
   186             try:
   187                 path = self.url_rewriter.rewrite(host, origpath)
   187                 path = self.url_rewriter.rewrite(host, origpath)
   188             except Redirect, ex:
   188             except Redirect, ex:
   224                 # in http we have to request auth to flush current http auth
   224                 # in http we have to request auth to flush current http auth
   225                 # information
   225                 # information
   226                 return self.request_auth(req, loggedout=True)
   226                 return self.request_auth(req, loggedout=True)
   227         except Redirect, ex:
   227         except Redirect, ex:
   228             return self.redirect(req, ex.location)
   228             return self.redirect(req, ex.location)
   229         if not result:
       
   230             # no result, something went wrong...
       
   231             self.error('no data (%s)', req)
       
   232             # 500 Internal server error
       
   233             return self.redirect(req, req.build_url('error'))
       
   234         # request may be referenced by "onetime callback", so clear its entity
   229         # request may be referenced by "onetime callback", so clear its entity
   235         # cache to avoid memory usage
   230         # cache to avoid memory usage
   236         req.drop_entity_cache()
   231         req.drop_entity_cache()
   237         return http.Response(stream=result, code=responsecode.OK,
   232         return http.Response(stream=result, code=responsecode.OK,
   238                              headers=req.headers_out or None)
   233                              headers=req.headers_out or None)
   240     def redirect(self, req, location):
   235     def redirect(self, req, location):
   241         req.headers_out.setHeader('location', str(location))
   236         req.headers_out.setHeader('location', str(location))
   242         self.debug('redirecting to %s', location)
   237         self.debug('redirecting to %s', location)
   243         # 303 See other
   238         # 303 See other
   244         return http.Response(code=303, headers=req.headers_out)
   239         return http.Response(code=303, headers=req.headers_out)
   245         
   240 
   246     def request_auth(self, req, loggedout=False):
   241     def request_auth(self, req, loggedout=False):
   247         if self.https_url and req.base_url() != self.https_url:
   242         if self.https_url and req.base_url() != self.https_url:
   248             req.headers_out.setHeader('location', self.https_url + 'login')
   243             req.headers_out.setHeader('location', self.https_url + 'login')
   249             return http.Response(code=303, headers=req.headers_out)            
   244             return http.Response(code=303, headers=req.headers_out)
   250         if self.config['auth-mode'] == 'http':
   245         if self.config['auth-mode'] == 'http':
   251             code = responsecode.UNAUTHORIZED
   246             code = responsecode.UNAUTHORIZED
   252         else:
   247         else:
   253             code = responsecode.FORBIDDEN
   248             code = responsecode.FORBIDDEN
   254         if loggedout:
   249         if loggedout:
   258             content = self.appli.loggedout_content(req)
   253             content = self.appli.loggedout_content(req)
   259         else:
   254         else:
   260             content = self.appli.need_login_content(req)
   255             content = self.appli.need_login_content(req)
   261         return http.Response(code, req.headers_out, content)
   256         return http.Response(code, req.headers_out, content)
   262 
   257 
   263     
   258 
   264 # This part gets run when you run this file via: "twistd -noy demo.py"
   259 # This part gets run when you run this file via: "twistd -noy demo.py"
   265 def main(appid, cfgname):
   260 def main(appid, cfgname):
   266     """Starts an cubicweb  twisted server for an application
   261     """Starts an cubicweb  twisted server for an application
   267 
   262 
   268     appid: application's identifier
   263     appid: application's identifier
   293 from twisted.internet import defer
   288 from twisted.internet import defer
   294 from twisted.web2 import fileupload
   289 from twisted.web2 import fileupload
   295 
   290 
   296 # XXX set max file size to 100Mo: put max upload size in the configuration
   291 # XXX set max file size to 100Mo: put max upload size in the configuration
   297 # line below for twisted >= 8.0, default param value for earlier version
   292 # line below for twisted >= 8.0, default param value for earlier version
   298 resource.PostableResource.maxSize = 100*1024*1024 
   293 resource.PostableResource.maxSize = 100*1024*1024
   299 def parsePOSTData(request, maxMem=100*1024, maxFields=1024,
   294 def parsePOSTData(request, maxMem=100*1024, maxFields=1024,
   300                   maxSize=100*1024*1024):
   295                   maxSize=100*1024*1024):
   301     if request.stream.length == 0:
   296     if request.stream.length == 0:
   302         return defer.succeed(None)
   297         return defer.succeed(None)
   303     
   298 
   304     ctype = request.headers.getHeader('content-type')
   299     ctype = request.headers.getHeader('content-type')
   305 
   300 
   306     if ctype is None:
   301     if ctype is None:
   307         return defer.succeed(None)
   302         return defer.succeed(None)
   308 
   303 
   316         request.files.update(files)
   311         request.files.update(files)
   317 
   312 
   318     def error(f):
   313     def error(f):
   319         f.trap(fileupload.MimeFormatError)
   314         f.trap(fileupload.MimeFormatError)
   320         raise http.HTTPError(responsecode.BAD_REQUEST)
   315         raise http.HTTPError(responsecode.BAD_REQUEST)
   321     
   316 
   322     if ctype.mediaType == 'application' and ctype.mediaSubtype == 'x-www-form-urlencoded':
   317     if ctype.mediaType == 'application' and ctype.mediaSubtype == 'x-www-form-urlencoded':
   323         d = fileupload.parse_urlencoded(request.stream, keep_blank_values=True)
   318         d = fileupload.parse_urlencoded(request.stream, keep_blank_values=True)
   324         d.addCallbacks(updateArgs, error)
   319         d.addCallbacks(updateArgs, error)
   325         return d
   320         return d
   326     elif ctype.mediaType == 'multipart' and ctype.mediaSubtype == 'form-data':
   321     elif ctype.mediaType == 'multipart' and ctype.mediaSubtype == 'form-data':