Mercurial > hg > FileServer
annotate fileserver/web.py @ 34:aca8cb6bfd63 default tip
fix documentation + bump version
author | Jeff Hammel <jhammel@mozilla.com> |
---|---|
date | Mon, 05 Mar 2012 14:09:43 -0800 |
parents | 0edb831061f5 |
children |
rev | line source |
---|---|
0 | 1 #!/usr/bin/env python |
2 | |
3 """ | |
4 WSGI app for FileServer | |
5 | |
6 Reference: | |
7 - http://docs.webob.org/en/latest/file-example.html | |
8 """ | |
9 | |
10 import mimetypes | |
1 | 11 import optparse |
0 | 12 import os |
1 | 13 import sys |
0 | 14 from webob import Request, Response, exc |
1 | 15 from wsgiref.simple_server import make_server |
16 | |
20 | 17 __all__ = ['get_mimetype', 'file_response', 'FileApp', 'DirectoryServer', 'main'] |
0 | 18 |
26
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
19 ### classes for iterating over files |
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
20 |
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
21 class FileIterable(object): |
32
0edb831061f5
test not modified response
Jeff Hammel <jhammel@mozilla.com>
parents:
26
diff
changeset
|
22 def __init__(self, filename, start=None, stop=None): |
26
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
23 self.filename = filename |
32
0edb831061f5
test not modified response
Jeff Hammel <jhammel@mozilla.com>
parents:
26
diff
changeset
|
24 self.start = start |
0edb831061f5
test not modified response
Jeff Hammel <jhammel@mozilla.com>
parents:
26
diff
changeset
|
25 self.stop = stop |
26
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
26 def __iter__(self): |
32
0edb831061f5
test not modified response
Jeff Hammel <jhammel@mozilla.com>
parents:
26
diff
changeset
|
27 return FileIterator(self.filename, self.start, self.stop) |
26
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
28 class FileIterator(object): |
32
0edb831061f5
test not modified response
Jeff Hammel <jhammel@mozilla.com>
parents:
26
diff
changeset
|
29 def __init__(self, filename, start, stop, chunk_size=4096): |
26
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
30 self.filename = filename |
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
31 self.chunk_size = chunk_size |
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
32 self.fileobj = open(self.filename, 'rb') |
32
0edb831061f5
test not modified response
Jeff Hammel <jhammel@mozilla.com>
parents:
26
diff
changeset
|
33 if start: |
0edb831061f5
test not modified response
Jeff Hammel <jhammel@mozilla.com>
parents:
26
diff
changeset
|
34 self.fileobj.seek(start) |
0edb831061f5
test not modified response
Jeff Hammel <jhammel@mozilla.com>
parents:
26
diff
changeset
|
35 if stop is not None: |
0edb831061f5
test not modified response
Jeff Hammel <jhammel@mozilla.com>
parents:
26
diff
changeset
|
36 self.length = stop - start |
0edb831061f5
test not modified response
Jeff Hammel <jhammel@mozilla.com>
parents:
26
diff
changeset
|
37 else: |
0edb831061f5
test not modified response
Jeff Hammel <jhammel@mozilla.com>
parents:
26
diff
changeset
|
38 self.length = None |
26
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
39 def __iter__(self): |
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
40 return self |
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
41 def next(self): |
32
0edb831061f5
test not modified response
Jeff Hammel <jhammel@mozilla.com>
parents:
26
diff
changeset
|
42 if self.length is not None and self.length <= 0: |
0edb831061f5
test not modified response
Jeff Hammel <jhammel@mozilla.com>
parents:
26
diff
changeset
|
43 raise StopIteration |
26
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
44 chunk = self.fileobj.read(self.chunk_size) |
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
45 if not chunk: |
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
46 raise StopIteration |
32
0edb831061f5
test not modified response
Jeff Hammel <jhammel@mozilla.com>
parents:
26
diff
changeset
|
47 if self.length is not None: |
0edb831061f5
test not modified response
Jeff Hammel <jhammel@mozilla.com>
parents:
26
diff
changeset
|
48 self.length -= len(chunk) |
0edb831061f5
test not modified response
Jeff Hammel <jhammel@mozilla.com>
parents:
26
diff
changeset
|
49 if self.length < 0: |
0edb831061f5
test not modified response
Jeff Hammel <jhammel@mozilla.com>
parents:
26
diff
changeset
|
50 # Chop off the extra: |
0edb831061f5
test not modified response
Jeff Hammel <jhammel@mozilla.com>
parents:
26
diff
changeset
|
51 chunk = chunk[:self.length] |
26
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
52 return chunk |
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
53 __next__ = next # py3 compat |
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
54 |
32
0edb831061f5
test not modified response
Jeff Hammel <jhammel@mozilla.com>
parents:
26
diff
changeset
|
55 |
0edb831061f5
test not modified response
Jeff Hammel <jhammel@mozilla.com>
parents:
26
diff
changeset
|
56 ### attributes for serving static files |
0edb831061f5
test not modified response
Jeff Hammel <jhammel@mozilla.com>
parents:
26
diff
changeset
|
57 |
0 | 58 def get_mimetype(filename): |
59 type, encoding = mimetypes.guess_type(filename) | |
60 # We'll ignore encoding, even though we shouldn't really | |
61 return type or 'application/octet-stream' | |
62 | |
63 def file_response(filename): | |
20 | 64 """return a webob response object appropriate to a file name""" |
26
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
65 res = Response(content_type=get_mimetype(filename), |
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
66 conditional_response=True) |
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
67 res.app_iter = FileIterable(filename) |
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
68 res.content_length = os.path.getsize(filename) |
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
69 res.last_modified = os.path.getmtime(filename) |
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
70 res.etag = '%s-%s-%s' % (os.path.getmtime(filename), |
395c6744bcd9
upgrading with suggestions from http://docs.webob.org/en/latest/file-example.html
Jeff Hammel <jhammel@mozilla.com>
parents:
24
diff
changeset
|
71 os.path.getsize(filename), hash(filename)) |
0 | 72 return res |
73 | |
74 class FileApp(object): | |
75 """ | |
76 serve static files | |
77 """ | |
78 | |
79 def __init__(self, filename): | |
80 self.filename = filename | |
81 | |
82 def __call__(self, environ, start_response): | |
83 res = file_response(self.filename) | |
84 return res(environ, start_response) | |
85 | |
32
0edb831061f5
test not modified response
Jeff Hammel <jhammel@mozilla.com>
parents:
26
diff
changeset
|
86 |
0edb831061f5
test not modified response
Jeff Hammel <jhammel@mozilla.com>
parents:
26
diff
changeset
|
87 ### class for serving directory indices |
0edb831061f5
test not modified response
Jeff Hammel <jhammel@mozilla.com>
parents:
26
diff
changeset
|
88 |
0 | 89 class DirectoryServer(object): |
90 | |
24 | 91 def __init__(self, directory, sort=True): |
0 | 92 assert os.path.exists(directory), "'%s' does not exist" % directory |
93 assert os.path.isdir(directory), "'%s' is not a directory" % directory | |
2
8fb047af207a
this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents:
1
diff
changeset
|
94 self.directory = self.normpath(directory) |
24 | 95 self.sort = sort |
0 | 96 |
97 @staticmethod | |
98 def normpath(path): | |
99 return os.path.normcase(os.path.abspath(path)) | |
100 | |
17 | 101 def check_path(self, path): |
102 """ | |
103 if under the root directory, returns the full path | |
104 otherwise, returns None | |
105 """ | |
106 path = self.normpath(path) | |
107 if path == self.directory or path.startswith(self.directory + os.path.sep): | |
108 return path | |
109 | |
2
8fb047af207a
this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents:
1
diff
changeset
|
110 def index(self, directory): |
8fb047af207a
this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents:
1
diff
changeset
|
111 """ |
8fb047af207a
this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents:
1
diff
changeset
|
112 generate a directory listing for a given directory |
8fb047af207a
this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents:
1
diff
changeset
|
113 """ |
8fb047af207a
this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents:
1
diff
changeset
|
114 parts = ['<html><head><title>Simple Index</title></head><body>'] |
8fb047af207a
this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents:
1
diff
changeset
|
115 listings = os.listdir(directory) |
24 | 116 if self.sort: |
117 listings.sort() | |
2
8fb047af207a
this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents:
1
diff
changeset
|
118 listings = [(os.path.isdir(os.path.join(directory, entry)) and entry + '/' or entry, entry) |
8fb047af207a
this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents:
1
diff
changeset
|
119 for entry in listings] |
8fb047af207a
this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents:
1
diff
changeset
|
120 for link, entry in listings: |
8fb047af207a
this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents:
1
diff
changeset
|
121 parts.append('<a href="%s">%s</a><br/>' % (link, entry)) |
8fb047af207a
this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents:
1
diff
changeset
|
122 parts.append('</body></html>') |
8fb047af207a
this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents:
1
diff
changeset
|
123 return '\n'.join(parts) |
8fb047af207a
this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents:
1
diff
changeset
|
124 |
0 | 125 def __call__(self, environ, start_response): |
126 request = Request(environ) | |
127 # TODO method_not_allowed: Allow: GET, HEAD | |
128 path_info = request.path_info | |
129 if not path_info: | |
13 | 130 response = exc.HTTPMovedPermanently(add_slash=True) |
131 return response(environ, start_response) | |
17 | 132 full = self.check_path(os.path.join(self.directory, path_info.strip('/'))) |
2
8fb047af207a
this now actually serves things
Jeff Hammel <jhammel@mozilla.com>
parents:
1
diff
changeset
|
133 |
17 | 134 if full is None: |
0 | 135 # Out of bounds |
136 return exc.HTTPNotFound()(environ, start_response) | |
137 if not os.path.exists(full): | |
138 return exc.HTTPNotFound()(environ, start_response) | |
139 | |
140 if os.path.isdir(full): | |
141 # serve directory index | |
142 if not path_info.endswith('/'): | |
12 | 143 response = exc.HTTPMovedPermanently(add_slash=True) |
144 return response(environ, start_response) | |
0 | 145 index = self.index(full) |
146 response = Response(index, content_type='text/html') | |
147 return response(environ, start_response) | |
148 | |
149 # serve file | |
150 if path_info.endswith('/'): | |
151 # we create the `full` filename above by stripping off | |
152 # '/' from both sides; so we correct here | |
153 return exc.HTTPNotFound()(environ, start_response) | |
154 response = file_response(full) | |
155 return response(environ, start_response) | |
156 | |
1 | 157 def main(args=sys.argv[1:]): |
158 | |
159 # parse command line arguments | |
160 usage = '%prog [options] directory' | |
161 class PlainDescriptionFormatter(optparse.IndentedHelpFormatter): | |
162 """description formatter""" | |
163 def format_description(self, description): | |
164 if description: | |
165 return description + '\n' | |
166 else: | |
167 return '' | |
168 parser = optparse.OptionParser(usage=usage, description=__doc__, formatter=PlainDescriptionFormatter()) | |
169 parser.add_option('-p', '--port', dest='port', | |
170 type='int', default=9999, | |
171 help='port [DEFAULT: %default]') | |
172 parser.add_option('-H', '--host', dest='host', default='0.0.0.0', | |
173 help='host [DEFAULT: %default]') | |
174 options, args = parser.parse_args(args) | |
175 | |
176 # get the directory | |
177 if not len(args) == 1: | |
178 parser.print_help() | |
179 sys.exit(1) | |
180 directory = args[0] | |
181 if not os.path.exists(directory): | |
182 parser.error("'%s' not found" % directory) | |
183 if not os.path.isdir(directory): | |
184 parser.error("'%s' not a directory" % directory) | |
185 | |
186 # serve | |
187 app = DirectoryServer(directory) | |
188 try: | |
189 print 'http://%s:%s/' % (options.host, options.port) | |
190 make_server(options.host, options.port, app).serve_forever() | |
191 except KeyboardInterrupt, ki: | |
192 print "Cio, baby!" | |
193 except BaseException, e: | |
194 sys.exit("Problem initializing server: %s" % e) | |
195 | |
0 | 196 if __name__ == '__main__': |
1 | 197 main() |