etwist/server.py
changeset 7273 a949fc438029
parent 6817 1959d97ebf2e
child 7275 bb3bb8104134
equal deleted inserted replaced
7260:2b1dce628d33 7273:a949fc438029
    19 
    19 
    20 __docformat__ = "restructuredtext en"
    20 __docformat__ = "restructuredtext en"
    21 
    21 
    22 import sys
    22 import sys
    23 import os
    23 import os
       
    24 import os.path as osp
    24 import select
    25 import select
    25 import errno
    26 import errno
    26 import traceback
    27 import traceback
    27 import threading
    28 import threading
    28 from os.path import join
    29 from os.path import join
    68     def render(self, request):
    69     def render(self, request):
    69         return HTTPResponse(twisted_request=request,
    70         return HTTPResponse(twisted_request=request,
    70                             code=http.FORBIDDEN,
    71                             code=http.FORBIDDEN,
    71                             stream='Access forbidden')
    72                             stream='Access forbidden')
    72 
    73 
    73 class File(static.File):
    74 
    74     """Prevent from listing directories"""
    75 class NoListingFile(static.File):
    75     def directoryListing(self):
    76     def directoryListing(self):
    76         return ForbiddenDirectoryLister()
    77         return ForbiddenDirectoryLister()
    77 
    78 
    78 
    79 
    79 class LongTimeExpiringFile(File):
    80 class DataLookupDirectory(NoListingFile):
       
    81     def __init__(self, config, path):
       
    82         NoListingFile.__init__(self, path)
       
    83         self.config = config
       
    84         self.here = path
       
    85         # backward-compatiblity: take care fckeditor may appears as
       
    86         # root directory or as a data subdirectory. XXX (adim) : why
       
    87         # that ?
       
    88         self.putChild('fckeditor', FCKEditorResource(self.config, ''))
       
    89 
       
    90     def getChild(self, path, request):
       
    91         if not path:
       
    92             return self.directoryListing()
       
    93         childpath = join(self.here, path)
       
    94         dirpath, rid = self.config.locate_resource(childpath)
       
    95         if dirpath is None:
       
    96             # resource not found
       
    97             return self.childNotFound
       
    98         filepath = os.path.join(dirpath, rid)
       
    99         if os.path.isdir(filepath):
       
   100             resource = DataLookupDirectory(self.config, childpath)
       
   101             # cache resource for this segment path to avoid recomputing
       
   102             # directory lookup
       
   103             self.putChild(path, resource)
       
   104             return resource
       
   105         else:
       
   106             return NoListingFile(filepath)
       
   107 
       
   108 
       
   109 class FCKEditorResource(NoListingFile):
       
   110     def __init__(self, config, path):
       
   111         NoListingFile.__init__(self, path)
       
   112         self.config = config
       
   113 
       
   114     def getChild(self, path, request):
       
   115         pre_path = request.path.split('/')[1:]
       
   116         if pre_path[0] == 'https':
       
   117             pre_path.pop(0)
       
   118             uiprops = self.config.https_uiprops
       
   119         else:
       
   120             uiprops = self.config.uiprops
       
   121         return static.File(osp.join(uiprops['FCKEDITOR_PATH'], path))
       
   122 
       
   123 
       
   124 class LongTimeExpiringFile(DataLookupDirectory):
    80     """overrides static.File and sets a far future ``Expires`` date
   125     """overrides static.File and sets a far future ``Expires`` date
    81     on the resouce.
   126     on the resouce.
    82 
   127 
    83     versions handling is done by serving static files by different
   128     versions handling is done by serving static files by different
    84     URLs for each version. For instance::
   129     URLs for each version. For instance::
    92         # XXX: Don't provide additional resource information to error responses
   137         # XXX: Don't provide additional resource information to error responses
    93         #
   138         #
    94         # the HTTP RFC recommands not going further than 1 year ahead
   139         # the HTTP RFC recommands not going further than 1 year ahead
    95         expires = date.today() + timedelta(days=6*30)
   140         expires = date.today() + timedelta(days=6*30)
    96         request.setHeader('Expires', generateDateTime(mktime(expires.timetuple())))
   141         request.setHeader('Expires', generateDateTime(mktime(expires.timetuple())))
    97         return File.render(self, request)
   142         return DataLookupDirectory.render(self, request)
    98 
   143 
    99 
   144 
   100 class CubicWebRootResource(resource.Resource):
   145 class CubicWebRootResource(resource.Resource):
   101     def __init__(self, config, vreg=None):
   146     def __init__(self, config, vreg=None):
       
   147         resource.Resource.__init__(self)
   102         self.config = config
   148         self.config = config
   103         # instantiate publisher here and not in init_publisher to get some
   149         # instantiate publisher here and not in init_publisher to get some
   104         # checks done before daemonization (eg versions consistency)
   150         # checks done before daemonization (eg versions consistency)
   105         self.appli = CubicWebPublisher(config, vreg=vreg)
   151         self.appli = CubicWebPublisher(config, vreg=vreg)
   106         self.base_url = config['base-url']
   152         self.base_url = config['base-url']
   107         self.https_url = config['https-url']
   153         self.https_url = config['https-url']
   108         self.children = {}
       
   109         self.static_directories = set(('data%s' % config.instance_md5_version(),
       
   110                                        'data', 'static', 'fckeditor'))
       
   111         global MAX_POST_LENGTH
   154         global MAX_POST_LENGTH
   112         MAX_POST_LENGTH = config['max-post-length']
   155         MAX_POST_LENGTH = config['max-post-length']
       
   156         self.putChild('static', NoListingFile(config.static_directory))
       
   157         self.putChild('fckeditor', FCKEditorResource(self.config, ''))
       
   158         self.putChild('data', DataLookupDirectory(self.config, ''))
       
   159         self.putChild('data%s' % config.instance_md5_version(),
       
   160                       LongTimeExpiringFile(self.config, ''))
   113 
   161 
   114     def init_publisher(self):
   162     def init_publisher(self):
   115         config = self.config
   163         config = self.config
   116         # when we have an in-memory repository, clean unused sessions every XX
   164         # when we have an in-memory repository, clean unused sessions every XX
   117         # seconds and properly shutdown the server
   165         # seconds and properly shutdown the server
   150         except select.error:
   198         except select.error:
   151             return
   199             return
   152 
   200 
   153     def getChild(self, path, request):
   201     def getChild(self, path, request):
   154         """Indicate which resource to use to process down the URL's path"""
   202         """Indicate which resource to use to process down the URL's path"""
   155         pre_path = request.path.split('/')[1:]
       
   156         if pre_path[0] == 'https':
       
   157             pre_path.pop(0)
       
   158             uiprops = self.config.https_uiprops
       
   159         else:
       
   160             uiprops = self.config.uiprops
       
   161         directory = pre_path[0]
       
   162         # Anything in data/, static/, fckeditor/ and the generated versioned
       
   163         # data directory is treated as static files
       
   164         if directory in self.static_directories:
       
   165             # take care fckeditor may appears as root directory or as a data
       
   166             # subdirectory
       
   167             if directory == 'static':
       
   168                 return File(self.config.static_directory)
       
   169             if directory == 'fckeditor':
       
   170                 return File(uiprops['FCKEDITOR_PATH'])
       
   171             if directory != 'data':
       
   172                 # versioned directory, use specific file with http cache
       
   173                 # headers so their are cached for a very long time
       
   174                 cls = LongTimeExpiringFile
       
   175             else:
       
   176                 cls = File
       
   177             if path == 'fckeditor':
       
   178                 return cls(uiprops['FCKEDITOR_PATH'])
       
   179             if path == directory: # recurse
       
   180                 return self
       
   181             datadir, path = self.config.locate_resource(path)
       
   182             if datadir is None:
       
   183                 return self # recurse
       
   184             self.debug('static file %s from %s', path, datadir)
       
   185             return cls(join(datadir, path))
       
   186         # Otherwise we use this single resource
       
   187         return self
   203         return self
   188 
   204 
   189     def render(self, request):
   205     def render(self, request):
   190         """Render a page from the root resource"""
   206         """Render a page from the root resource"""
   191         # reload modified files in debug mode
   207         # reload modified files in debug mode