source: ceda_http_fileserver/trunk/ceda_http_fileserver/ceda/server/wsgi/fileserver/app.py @ 6999

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/ceda_http_fileserver/trunk/ceda_http_fileserver/ceda/server/wsgi/fileserver/app.py@6999
Revision 6999, 8.3 KB checked in by pjkersha, 10 years ago (diff)

Incomplete - task 9: Data Browser Replacement

  • fixes to do_get
  • Property svn:keywords set to Id
Line 
1"""CEDA (Centre for Environmental Data Archival) File Server WSGI Application
2module
3"""
4__author__ = "P J Kershaw"
5__date__ = "11/06/10"
6__copyright__ = "(C) 2010 Science and Technology Facilities Council"
7__license__ = """http://www.apache.org/licenses/LICENSE-2.0"""
8__contact__ = "Philip.Kershaw@stfc.ac.uk"
9__revision__ = '$Id$'
10#   Copyright (c) 2006-2007 Open Source Applications Foundation
11#
12#   Licensed under the Apache License, Version 2.0 (the "License");
13#   you may not use this file except in compliance with the License.
14#   You may obtain a copy of the License at
15#
16#       http://www.apache.org/licenses/LICENSE-2.0
17#
18#   Unless required by applicable law or agreed to in writing, software
19#   distributed under the License is distributed on an "AS IS" BASIS,
20#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21#   See the License for the specific language governing permissions and
22#   limitations under the License.
23
24from urlparse import urlparse
25import os, sys
26import logging
27
28logger = logging.getLogger(__name__)
29
30# Content type sources taken from http://en.wikipedia.org/wiki/MIME_type
31content_type_table = {
32    'js': 'application/x-javascript', 
33    'html': 'text/html; charset=utf-8',
34    'fallback':'text/plain; charset=utf-8', 
35    'ogg': 'application/ogg', 
36    'xhtml':'text/html; charset=utf-8', 
37    'rm':'audio/vnd.rn-realaudio', 
38    'swf':'application/x-shockwave-flash', 
39    'mp3': 'audio/mpeg', 
40    'wma':'audio/x-ms-wma', 
41    'ra':'audio/vnd.rn-realaudio', 
42    'wav':'audio/x-wav', 
43    'gif':'image/gif', 
44    'jpeg':'image/jpeg',
45    'jpg':'image/jpeg', 
46    'png':'image/png', 
47    'tiff':'image/tiff', 
48    'css':'text/css; charset=utf-8',
49    'mpeg':'video/mpeg', 
50    'mp4':'video/mp4', 
51    'qt':'video/quicktime', 
52    'mov':'video/quicktime',
53    'wmv':'video/x-ms-wmv', 
54    'atom':'application/atom+xml; charset=utf-8',
55    'xslt':'application/xslt+xml', 
56    'svg':'image/svg+xml', 'mathml':'application/mathml+xml', 
57    'rss':'application/rss+xml; charset=utf-8',
58    'ics':'text/calendar; charset=utf-8 '
59}
60
61
62def reconstruct_url(environ):
63    # From WSGI spec, PEP 333
64    from urllib import quote
65    url = environ['wsgi.url_scheme']+'://'
66    if environ.get('HTTP_HOST'): url += environ['HTTP_HOST']
67    else:
68        url += environ['SERVER_NAME']
69        if environ['wsgi.url_scheme'] == 'https':
70            if environ['SERVER_PORT'] != '443':
71               url += ':' + environ['SERVER_PORT']
72        else:
73            if environ['SERVER_PORT'] != '80':
74               url += ':' + environ['SERVER_PORT']
75    url += quote(environ.get('SCRIPT_NAME',''))
76    url += quote(environ.get('PATH_INFO','')).replace(url.replace(':', '%3A'), '')
77    if environ.get('QUERY_STRING'):
78        url += '?' + environ['QUERY_STRING']
79    environ['reconstructed_url'] = url
80    return url
81   
82   
83class FileResponse(object):
84    readsize = 1024
85   
86    def __init__(self, f, filename):
87        self.size = os.path.getsize(filename)
88        self.f = f
89       
90    def __iter__(self):
91        output = '\n'
92        while len(output) is not 0:
93            output = self.f.read(self.readsize)
94            yield output
95
96
97class FileServerApp(object):
98    """Application to serve static content"""
99    PARAM_PREFIX = 'fileserver.'
100   
101    def __init__(self, root_path='', mount_point=None):
102        self.path = os.path.abspath(os.path.expanduser(root_path))
103        self.mount_point = mount_point
104
105    @classmethod
106    def app_factory(cls, global_conf, prefix=PARAM_PREFIX, **app_conf): 
107        """Function following Paste app factory signature
108       
109        @type global_conf: dict       
110        @param global_conf: PasteDeploy global configuration dictionary
111        @type prefix: basestring
112        @param prefix: prefix for configuration items
113        @type app_conf: dict       
114        @param app_conf: PasteDeploy application specific configuration
115        dictionary
116        """
117        # This app             
118        app = cls(**app_conf)
119
120        return app
121   
122    def handler(self, environ, start_response):
123        """Application to serve out windmill provided"""
124        url = urlparse(reconstruct_url(environ))
125       
126        if self.mount_point is not None:
127            #split_url = url.path.split(self.mount_point, 1)
128            split_url = url[2].split(self.mount_point, 1)
129            serve_file = split_url[1]
130        else:
131            #serve_file = url.path
132            serve_file = url[2]
133       
134        serve_file = serve_file.replace('%20', ' ')
135       
136        def do_get():
137            # This if statement stops os.path.join doing a join with the
138            # absolute path '/'.  If this is done, the first argument is
139            # obliterated and the result is '/' exposing the root file system to
140            # the web client!!
141            if serve_file == '/':
142                file = self.path
143            else:
144                file = os.path.join(self.path, serve_file)
145           
146            from urlparse import urljoin
147            isDir = os.path.isdir(file)
148            if serve_file.endswith('/') or isDir:
149                if isDir:
150                    dirContents = os.listdir(file)
151#                   
152#                    lines = [
153#                        '<a href="%s/%s">%s</a>' %
154#                        (serve_file.replace(filename, ''), filename, filename)
155#                        for filename in dirContents]
156
157                    lines = ['<a href="%s">%s</a>' % 
158                             (urljoin(serve_file, filename), filename) 
159                             for filename in dirContents]
160
161                    response = '<html>' + '<br>'.join(lines)+ '</html>'
162                    start_response('200 OK', 
163                               [('Cache-Control','no-cache'), 
164                                ('Pragma','no-cache'),
165                                ('Content-Type', 'text/html; charset=utf-8'),
166                                ('Content-Length', str(len(response)))])
167                   
168                    return [response]
169                else:
170                    logger.error('failed to list directory %s/%s' % (self.path, serve_file))
171                    start_response('404 Not found', [('Content-Type', 'text/plain')])
172                    return ['404 Not Found']
173           
174            try:
175                if os.name == 'nt' or sys.platform == 'cygwin':
176                    f = open(os.path.join(self.path, serve_file), 'rb')
177                else:
178                    f = open(os.path.join(self.path, serve_file), 'r')
179                logger.debug('opened file %s' % serve_file)
180            except IOError:
181                logger.error('failed to open file %s/%s' % (self.path, serve_file))
182                start_response('404 Not found', [('Content-Type', 'text/plain')])
183                return ['404 Not Found']
184           
185            response = FileResponse(f, os.path.join(self.path, serve_file))
186            start_response('200 OK', [('Cache-Control','no-cache'), ('Pragma','no-cache'), 
187                                      ('Content-Length', str(response.size),),
188                                      ('Content-Type', self.guess_content_type(environ['PATH_INFO']))])
189            return response
190           
191        def do_put():
192            #Write file
193            try:
194                f = open(os.path.join(self.path, serve_file), 'w')
195                logger.debug('opened file for writing %s' % serve_file)
196            except:
197                logger.error('failed to open file for writiing %s/%s' % (self.path, serve_file))
198                start_response('403 Forbidden', [('Content-Type', 'text/plain')])
199                return ['403 Forbidden']
200           
201            f.write(environ['wsgi.input'].read())
202           
203        def do_mkcollection():
204            pass
205           
206        http_method_map = {'GET':do_get, 'PUT':do_put, 'MKCOLLECTION':do_mkcollection}
207        return http_method_map[environ['REQUEST_METHOD']]()
208           
209
210    def guess_content_type(self, path_info):
211        """Make a best guess at the content type"""
212        extention_split = path_info.split('.')
213
214        if content_type_table.has_key(extention_split[-1]):
215            return content_type_table[extention_split[-1]]
216        else:
217            return content_type_table['fallback']
218           
219    def __call__(self, environ, start_response):
220        return self.handler(environ, start_response)
Note: See TracBrowser for help on using the repository browser.