Changeset 7021 for ceda_http_fileserver


Ignore:
Timestamp:
14/06/10 14:34:44 (9 years ago)
Author:
pjkersha
Message:

Incomplete - task 9: Data Browser Replacement:

  • Added URL encoding of file names for GET
  • Uses mimetypes package
  • tested returning file in variable length blocks with FileRespone?.iter
Location:
ceda_http_fileserver/trunk/ceda_http_fileserver/ceda/server/wsgi/fileserver
Files:
4 added
4 edited

Legend:

Unmodified
Added
Removed
  • ceda_http_fileserver/trunk/ceda_http_fileserver/ceda/server/wsgi/fileserver/app.py

    r7009 r7021  
    2222#   limitations under the License. 
    2323 
    24 from urlparse import urlparse 
     24from urlparse import urlparse, urljoin 
    2525import httplib 
     26import urllib 
    2627import os 
     28import traceback 
    2729import logging 
     30import mimetypes 
    2831 
    2932log = logging.getLogger(__name__) 
    3033 
    3134# Content type sources taken from http://en.wikipedia.org/wiki/MIME_type 
    32 content_type_table = { 
     35_types_map = { 
    3336    'js': 'application/x-javascript',  
    3437    'html': 'text/html; charset=utf-8', 
     
    9295    def __call__(self, fileObj, fileName): 
    9396        self.fileSize = os.path.getsize(fileName) 
    94         self.fileObj = fileObj         
     97        self.fileObj = fileObj 
     98         
     99        return self   
    95100         
    96101    def __iter__(self): 
     
    124129        '__readBlkSize', 
    125130        '__httpMethodMap', 
    126         'path', 
    127         'mount_point' 
     131        'fileSysPath', 
     132        'mountPoint', 
     133        'mimeTypes' 
    128134    ) 
    129135     
    130     def __init__(self, root_path, mount_point=None): 
     136    def __init__(self, root_path, mountPoint=None): 
    131137         
    132138        self.__fileResponse = FileResponse() 
     
    134140        self.__httpMethodMap = None 
    135141         
    136         self.path = os.path.abspath(os.path.expanduser(root_path)) 
    137         self.mount_point = mount_point 
     142        self.fileSysPath = os.path.abspath(os.path.expanduser(root_path)) 
     143        self.mountPoint = mountPoint 
    138144         
    139145        # Set from property methods to apply validation - referencing  
     
    148154        # Map HTTP method name to a method of this class 
    149155        self.httpMethodMap = self.__class__.DEFAULT_HTTP_METHOD_MAP 
     156         
     157        # MIME types 
     158        self.mimeTypes = mimetypes.MimeTypes() 
    150159 
    151160    def _getReadBlkSize(self): 
     
    213222        url = urlparse(reconstruct_url(environ)) 
    214223         
    215         if self.mount_point is not None: 
    216             #split_url = url.path.split(self.mount_point, 1) 
    217             split_url = url[2].split(self.mount_point, 1) 
    218             serve_file = split_url[1] 
     224        if self.mountPoint is not None: 
     225            #split_url = url.path.split(self.mountPoint, 1) 
     226            split_url = url[2].split(self.mountPoint, 1) 
     227            relativeURI = split_url[1] 
    219228        else: 
    220             #serve_file = url.path 
    221             serve_file = url[2] 
    222          
    223         serve_file = serve_file.replace('%20', ' ') 
     229            relativeURI = url[2] 
     230         
     231        fileSysRelPath = urllib.url2pathname(relativeURI) 
    224232         
    225233        requestMethodName = environ['REQUEST_METHOD'] 
     
    235243            return [response] 
    236244             
    237         return requestMethod(self, serve_file, environ, start_response) 
    238          
    239     def do_get(self, serve_file, environ, start_response): 
     245        return requestMethod(self, fileSysRelPath, environ,  
     246                             start_response) 
     247         
     248    def do_get(self, fileSysRelPath, environ, start_response): 
    240249        # This if statement stops os.path.join doing a join with the  
    241250        # absolute path '/'.  If this is done, the first argument is  
    242251        # obliterated and the result is '/' exposing the root file system to 
    243252        # the web client!! 
    244         if serve_file == '/': 
    245             filePath = self.path 
     253        if fileSysRelPath == '/': 
     254            filePath = self.fileSysPath 
    246255        else: 
    247             filePath = os.path.join(self.path, serve_file) 
    248          
    249         from urlparse import urljoin 
     256            if fileSysRelPath.startswith('/'): 
     257                fileSysRelPath = fileSysRelPath[1:] 
     258                 
     259            filePath = os.path.join(self.fileSysPath, fileSysRelPath) 
     260         
    250261        isDir = os.path.isdir(filePath) 
    251         if serve_file.endswith('/') or isDir: 
    252             if isDir: 
    253                 dirContents = os.listdir(filePath) 
    254 #                     
    255 #                    lines = [ 
    256 #                        '<a href="%s/%s">%s</a>' %  
    257 #                        (serve_file.replace(filename, ''), filename, filename)  
    258 #                        for filename in dirContents] 
    259  
    260                 lines = ['<a href="%s">%s</a>' %  
    261                          (urljoin(serve_file, filename), filename)  
    262                          for filename in dirContents] 
    263  
    264                 response = '<html>' + '<br>'.join(lines)+ '</html>' 
    265                 start_response('200 OK',  
    266                                [('Cache-Control','no-cache'),  
    267                                 ('Pragma','no-cache'), 
    268                                 ('Content-Type', 'text/html; charset=utf-8'), 
    269                                 ('Content-Length', str(len(response)))]) 
    270                  
     262        if isDir: 
     263            dirContents = os.listdir(filePath) 
     264 
     265            lines = [ 
     266                '<a href="%s">%s</a>' %  
     267                (urljoin(fileSysRelPath, urllib.pathname2url(filename)), 
     268                filename)  
     269                for filename in dirContents 
     270            ] 
     271 
     272            response = '<html>' + '<br>'.join(lines)+ '</html>' 
     273            start_response('200 OK',  
     274                           [('Cache-Control','no-cache'),  
     275                            ('Pragma','no-cache'), 
     276                            ('Content-Type', 'text/html; charset=utf-8'), 
     277                            ('Content-Length', str(len(response)))]) 
     278             
     279            return [response] 
     280        else:         
     281            try: 
     282                fileObj = open(filePath, 'rb') 
     283                log.debug('opened file %s', filePath) 
     284            except IOError: 
     285                log.error('failed to open file %r: %s', filePath,  
     286                          traceback.format_exc()) 
     287                response = '404 Not Found' 
     288                start_response('404 Not found',  
     289                               [('Content-Type', 'text/plain'), 
     290                                ('Content-length', str(len(response)))]) 
    271291                return [response] 
    272             else: 
    273                 log.error('failed to list directory %s/%s', self.path,  
    274                           serve_file) 
    275                 start_response('404 Not found',  
    276                                [('Content-Type', 'text/plain')]) 
    277                 return ['404 Not Found'] 
    278          
    279         try: 
    280             f = open(os.path.join(self.path, serve_file), 'rb') 
    281             log.debug('opened file %s' % serve_file) 
    282         except IOError: 
    283             log.error('failed to open file %s/%s' % (self.path, serve_file)) 
    284             start_response('404 Not found', [('Content-Type', 'text/plain')]) 
    285             return ['404 Not Found'] 
    286          
    287         response = self.__fileResponse(f, os.path.join(self.path, serve_file)) 
    288         start_response('200 OK',  
    289                        [('Cache-Control','no-cache'),  
    290                         ('Pragma','no-cache'),  
    291                         ('Content-Length', str(response.size),), 
    292                         ('Content-Type',  
    293                          self.guess_content_type(environ['PATH_INFO']))]) 
    294         return response 
    295          
    296     def do_put(self, serve_file, environ, start_response): 
     292             
     293            response = self.__fileResponse(fileObj, filePath) 
     294            start_response('200 OK',  
     295                           [('Cache-Control','no-cache'),  
     296                            ('Pragma','no-cache'),  
     297                            ('Content-Length', str(response.fileSize)), 
     298                            ('Content-Type',  
     299                             self.getContentType(environ['PATH_INFO']))]) 
     300            return response 
     301         
     302    def do_put(self, fileSysRelPath, environ, start_response): 
    297303        #Write file 
    298304        try: 
    299             f = open(os.path.join(self.path, serve_file), 'w') 
    300             log.debug('opened file for writing %s' % serve_file) 
     305            filePath = os.path.join(self.fileSysPath, fileSysRelPath) 
     306            f = open(filePath, 'w') 
     307            log.debug('opened file for writing %s' % filePath) 
    301308        except: 
    302             log.error('failed to open file for writing %s/%s', self.path,  
    303                       serve_file) 
    304             start_response('403 Forbidden', [('Content-Type', 'text/plain')]) 
    305             return ['403 Forbidden'] 
    306          
    307         self.readBlkSize = 1024 
     309            log.error('failed to open file for writing %r', filePath) 
     310            response = '403 Forbidden' 
     311            start_response('403 Forbidden',  
     312                           [('Content-Type', 'text/plain'), 
     313                            ('Content-length', str(len(response)))]) 
     314            return [response] 
     315         
    308316        inputLength = environ['CONTENT_LENGTH'] 
    309317        inputStream = environ['wsgi.input'] 
     
    318326            inputBlk = inputStream.read(length) 
    319327            f.write(inputBlk) 
    320          
    321     def do_mkcollection(self, serve_file, environ, start_response): 
    322         pass 
    323              
     328 
     329    def getContentType(self, path_info): 
     330        """Make a best guess at the content type""" 
     331        contentType = self.mimeTypes.guess_type(path_info)[0] 
     332        if contentType is not None: 
     333            return contentType 
     334        else: 
     335            return self.mimeTypes.types_map['fallback'] 
     336             
     337    def __call__(self, environ, start_response): 
     338        return self.handler(environ, start_response) 
     339         
    324340    DEFAULT_HTTP_METHOD_MAP = { 
    325341        'GET':          do_get,  
    326342        'PUT':          do_put,  
    327         'MKCOLLECTION': do_mkcollection 
    328343    } 
    329  
    330     def guess_content_type(self, path_info): 
    331         """Make a best guess at the content type""" 
    332         extention_split = path_info.split('.') 
    333  
    334         if content_type_table.has_key(extention_split[-1]): 
    335             return content_type_table[extention_split[-1]] 
    336         else: 
    337             return content_type_table['fallback'] 
    338              
    339     def __call__(self, environ, start_response): 
    340         return self.handler(environ, start_response) 
  • ceda_http_fileserver/trunk/ceda_http_fileserver/ceda/server/wsgi/fileserver/test/test_fileserver.py

    r7009 r7021  
    1717 
    1818import httplib 
     19import urllib 
    1920 
    2021import paste.fixture 
     
    2829    HTDOCS_DIRNAME = 'htdocs' 
    2930    HTDOCS_DIR = path.join(THIS_DIR, HTDOCS_DIRNAME) 
    30  
     31    PNG_FILENAME = 'a png with uppercase suffix.PNG' 
     32    JPEG_FILENAME = 'my test jpeg.jpg' 
     33    PLAIN_TEXT_FILENAME = 'plain-text.file.txt' 
     34    HTML_FILENAME = 'my file.html' 
     35    PDF_FILENAME = 'a test PDF.pdf' 
     36     
    3137    def __init__(self, *args, **kwargs): 
    3238        self.fileServerApp = FileServerApp(self.__class__.HTDOCS_DIR) 
     
    5662        self.fileServerApp.httpMethodMap = {} 
    5763        response = self.app.get('/', status=httplib.METHOD_NOT_ALLOWED) 
    58      
     64 
     65    def test05GetJPEG(self): 
     66        # Test jpeg is returned with correct content type 
     67        response = self.app.get(urllib.pathname2url('/my test jpeg.jpg'),  
     68                                status=httplib.OK) 
     69        print(response.headers) 
     70        print(response.body) 
     71        self.assert_(response.header_dict['content-type'] == 'image/jpeg') 
     72 
     73    def test06GetPngWithUppercaseSuffix(self): 
     74        # Test PNG is returned with correct content type 
     75        response = self.app.get(urllib.pathname2url( 
     76                                            '/a png with uppercase suffix.PNG'),  
     77                                status=httplib.OK) 
     78        print(response.headers) 
     79        print(response.body) 
     80        self.assert_(response.header_dict['content-type'] == 'image/png') 
     81 
     82    def test07WriteBlocks(self): 
     83        # Test writing output from server in blocks 
     84        self.fileServerApp.writeBlkSize = 8 
     85        localFilePath = path.join(self.__class__.HTDOCS_DIR,  
     86                                'a png with uppercase suffix.PNG') 
     87        fileContent = open(localFilePath, 'rb').read() 
     88        response = self.app.get(urllib.pathname2url( 
     89                                            '/a png with uppercase suffix.PNG'),  
     90                                status=httplib.OK) 
     91        print(response.headers) 
     92        print(response.body) 
     93        self.assert_(response.header_dict['content-type'] == 'image/png') 
     94        self.assert_(response.body == fileContent) 
     95 
    5996     
    6097class FileServerAppTestCaseBase(unittest.TestCase):   
     
    69106                 
    70107        unittest.TestCase.__init__(self, *args, **kwargs)   
    71     
     108 
     109 
    72110if __name__ == "__main__": 
    73111    unittest.main() 
Note: See TracChangeset for help on using the changeset viewer.