3
# This Source Code Form is subject to the terms of the Mozilla Public
4
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
5
# You can obtain one at http://mozilla.org/MPL/2.0/.
8
import SimpleHTTPServer
21
from SocketServer import ThreadingMixIn
23
class EasyServer(ThreadingMixIn, BaseHTTPServer.HTTPServer):
24
allow_reuse_address = True
25
acceptable_errors = (errno.EPIPE, errno.ECONNABORTED)
27
def handle_error(self, request, client_address):
30
if ((isinstance(error, socket.error) and
31
isinstance(error.args, tuple) and
32
error.args[0] in self.acceptable_errors)
34
(isinstance(error, IOError) and
35
error.errno in self.acceptable_errors)):
36
pass # remote hang up before the result is sent
41
class Request(object):
42
"""Details of a request."""
44
# attributes from urlsplit that this class also sets
45
uri_attrs = ('scheme', 'netloc', 'path', 'query', 'fragment')
47
def __init__(self, uri, headers, rfile=None):
49
self.headers = headers
50
parsed = urlparse.urlsplit(uri)
51
for i, attr in enumerate(self.uri_attrs):
52
setattr(self, attr, parsed[i])
54
body_len = int(self.headers.get('Content-length', 0))
57
if body_len and rfile:
58
self.body = rfile.read(body_len)
63
class RequestHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
65
docroot = os.getcwd() # current working directory at time of import
66
proxy_host_dirs = False
71
def _try_handler(self, method):
73
self.request_log.append({ 'method': method,
74
'path': self.request.path,
75
'time': time.time() })
77
handlers = [handler for handler in self.urlhandlers
78
if handler['method'] == method]
79
for handler in handlers:
80
m = re.match(handler['path'], self.request.path)
82
(response_code, headerdict, data) = \
83
handler['function'](self.request, *m.groups())
84
self.send_response(response_code)
85
for (keyword, value) in headerdict.iteritems():
86
self.send_header(keyword, value)
88
self.wfile.write(data)
94
def parse_request(self):
95
retval = SimpleHTTPServer.SimpleHTTPRequestHandler.parse_request(self)
96
self.request = Request(self.path, self.headers, self.rfile)
100
if not self._try_handler('GET'):
102
# don't include query string and fragment, and prepend
103
# host directory if required.
104
if self.request.netloc and self.proxy_host_dirs:
105
self.path = '/' + self.request.netloc + \
108
self.path = self.request.path
109
SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
111
self.send_response(404)
116
# if we don't have a match, we always fall through to 404 (this may
117
# not be "technically" correct if we have a local file at the same
118
# path as the resource but... meh)
119
if not self._try_handler('POST'):
120
self.send_response(404)
125
# if we don't have a match, we always fall through to 404 (this may
126
# not be "technically" correct if we have a local file at the same
127
# path as the resource but... meh)
128
if not self._try_handler('DEL'):
129
self.send_response(404)
133
def translate_path(self, path):
134
# this is taken from SimpleHTTPRequestHandler.translate_path(),
135
# except we serve from self.docroot instead of os.getcwd(), and
136
# parse_request()/do_GET() have already stripped the query string and
137
# fragment and mangled the path for proxying, if required.
138
path = posixpath.normpath(urllib.unquote(self.path))
139
words = path.split('/')
140
words = filter(None, words)
143
drive, word = os.path.splitdrive(word)
144
head, word = os.path.split(word)
145
if word in (os.curdir, os.pardir): continue
146
path = os.path.join(path, word)
150
# I found on my local network that calls to this were timing out
151
# I believe all of these calls are from log_message
152
def address_string(self):
155
# This produces a LOT of noise
156
def log_message(self, format, *args):
160
class MozHttpd(object):
162
Very basic HTTP server class. Takes a docroot (path on the filesystem)
163
and a set of urlhandler dictionaries of the form:
166
'method': HTTP method (string): GET, POST, or DEL,
167
'path': PATH_INFO (regular expression string),
168
'function': function of form fn(arg1, arg2, arg3, ..., request)
171
and serves HTTP. For each request, MozHttpd will either return a file
172
off the docroot, or dispatch to a handler function (if both path and
175
Note that one of docroot or urlhandlers may be None (in which case no
176
local files or handlers, respectively, will be used). If both docroot or
177
urlhandlers are None then MozHttpd will default to serving just the local
180
MozHttpd also handles proxy requests (i.e. with a full URI on the request
181
line). By default files are served from docroot according to the request
182
URI's path component, but if proxy_host_dirs is True, files are served
183
from <self.docroot>/<host>/.
185
For example, the request "GET http://foo.bar/dir/file.html" would
186
(assuming no handlers match) serve <docroot>/dir/file.html if
187
proxy_host_dirs is False, or <docroot>/foo.bar/dir/file.html if it is
191
def __init__(self, host="127.0.0.1", port=8888, docroot=None,
192
urlhandlers=None, proxy_host_dirs=False, log_requests=False):
194
self.port = int(port)
195
self.docroot = docroot
196
if not urlhandlers and not docroot:
197
self.docroot = os.getcwd()
198
self.proxy_host_dirs = proxy_host_dirs
200
self.urlhandlers = urlhandlers or []
201
self.log_requests = log_requests
202
self.request_log = []
204
class RequestHandlerInstance(RequestHandler):
205
docroot = self.docroot
206
urlhandlers = self.urlhandlers
207
proxy_host_dirs = self.proxy_host_dirs
208
request_log = self.request_log
209
log_requests = self.log_requests
211
self.handler_class = RequestHandlerInstance
213
def start(self, block=False):
215
Start the server. If block is True, the call will not return.
216
If block is False, the server will be started on a separate thread that
217
can be terminated by a call to .stop()
219
self.httpd = EasyServer((self.host, self.port), self.handler_class)
221
self.httpd.serve_forever()
223
self.server = threading.Thread(target=self.httpd.serve_forever)
224
self.server.setDaemon(True) # don't hang on exit
229
### FIXME: There is no shutdown() method in Python 2.4...
231
self.httpd.shutdown()
232
except AttributeError:
239
def main(args=sys.argv[1:]):
241
# parse command line options
242
from optparse import OptionParser
243
parser = OptionParser()
244
parser.add_option('-p', '--port', dest='port',
245
type="int", default=8888,
246
help="port to run the server on [DEFAULT: %default]")
247
parser.add_option('-H', '--host', dest='host',
249
help="host [DEFAULT: %default]")
250
parser.add_option('-i', '--external-ip', action="store_true",
251
dest='external_ip', default=False,
252
help="find and use external ip for host")
253
parser.add_option('-d', '--docroot', dest='docroot',
255
help="directory to serve files from [DEFAULT: %default]")
256
options, args = parser.parse_args(args)
258
parser.error("mozhttpd does not take any arguments")
260
if options.external_ip:
261
host = iface.get_lan_ip()
266
server = MozHttpd(host=host, port=options.port, docroot=options.docroot)
268
print "Serving '%s' at %s:%s" % (server.docroot, server.host, server.port)
269
server.start(block=True)
271
if __name__ == '__main__':