#!/usr/bin/env python
#-----------------------------------------------------------------------------
# program httpd.py
# purpose Light weight modular forking HTTP daemon
# usage Run this in the directory to be served.
#-----------------------------------------------------------------------------
license = """
Copyright (C) 2012, by Phil D. Howard - all other rights reserved
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA, OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE, OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
The author may be contacted using the short first name, middle initial,
and last name, separated by periods, with gmail dot com as the host part
of an email address.
"""
import os
import sys
import time
import BaseHTTPServer
import SocketServer
from urlparse import urlparse
reject_paths = ['httpd', 'httpsd']
#-----------------------------------------------------------------------------
# Map file extension to content type. You might need to expand this.
#-----------------------------------------------------------------------------
file_extension_list = {
'txt': 'text/text',
'html': 'text/html',
'ico': 'image/vnd.microsoft.icon',
'gif': 'image/gif',
'jpg': 'image/jpeg',
'png': 'image/png',
}
def ext_to_type(e):
e = e.split('/')[-1].split('.')[-1]
if len(e) > 0 and e in file_extension_list: return file_extension_list[e]
return None
#-----------------------------------------------------------------------------
# Output the main index page here.
#-----------------------------------------------------------------------------
def handle_default(c):
o = c.wfile.write
c.send_response(200)
c.send_header("Content-type", "text/html")
c.end_headers()
msg = 'You connected from IP address %s port %u' % c.client_address[0:2]
o('<html>\r\n')
o('<head><title>%s</title></head>\r\n' % msg)
o('<body>\r\n')
o('%s\r\n' % msg)
o('</body></html>\r\n')
return
#-----------------------------------------------------------------------------
# Output a rejection page here.
#-----------------------------------------------------------------------------
def request_fail(c):
o = c.wfile.write
c.send_response(400)
c.send_header("Content-type", "text/html")
c.end_headers()
o("<html><head><title>D'oh!</title></head><body><h1>D'oh!</h1></body></html>\r\n")
return
#-----------------------------------------------------------------------------
# Output head request response codes here (no content).
#-----------------------------------------------------------------------------
def head_response(c,r):
c.send_response(r)
c.end_headers()
return
def head_fail(c): return head_respons(c,400)
def head_pass(c): return head_respons(c,200)
#-----------------------------------------------------------------------------
# Import the selected module and call its handle_http_request() method with
# the following arguments passed to it:
# 1: c = connection object
# 2: d = directory parent of module
# 3: f = file name from origina directory
# 4: r = remaining path info
# 5: q = query string
#-----------------------------------------------------------------------------
def handle_module(c,d,n,r='',q=''):
sys.path.insert(0,'')
m = __import__( n )
if m != None:
if len( d ) > 0:
try:
os.chdir( d )
except:
request_fail(c)
if getattr( m, "handle_http_request", None ):
m.handle_http_request(c,d,n,r,q)
else:
request_fail(c)
return
#-----------------------------------------------------------------------------
# Handle a plain file.
#-----------------------------------------------------------------------------
def handle_file(c,d,n):
o = c.wfile.write
t = ext_to_type(n)
if not t: return request_fail(c)
if t[0:4] == 'text': f = open(n,'r',0)
else: f = open(n,'rb',0)
if not f: return request_fail(c)
d = f.read()
if len(d) > 0:
c.send_response(200)
c.send_header("Content-type",t)
c.send_header("Content-length",len(d))
c.end_headers()
o(d)
else:
request_fail(c)
f.close()
return
#-----------------------------------------------------------------------------
# Log additional information that BaseHTTPServer does not.
#-----------------------------------------------------------------------------
def log_client_address(c,m):
print '[%s]:%s %s %s' % ( c.client_address[0:2] + (m,c.path) )
return
#-----------------------------------------------------------------------------
# Class used by forking server module to to invoke methods for HTTP requests.
#-----------------------------------------------------------------------------
class my_handle_http_request(BaseHTTPServer.BaseHTTPRequestHandler):
def do_PUT(c):
p = c.path
log_client_address(c,'PUT')
return
def do_POST(c):
p = c.path
log_client_address(c,'POST')
return
def do_HEAD(c):
p = c.path
log_client_address(c,'HEAD')
while len(p) > 0 and p[0] == '/': p = p[1:]
if len(p) < 1: p = 'index'
if len(p) < 1: return head_pass(c)
p,q = urlparse('http://hostname/'+p)[2:5:2]
p = p[1:]
ps = p.split("/")
for s in ps:
if len(s) < 1: return head_fail(c)
if s[0] == '.': return head_fail(c)
for i in range(len(ps)-1,-1,-1):
n = "/".join( ps[0:i] )
if n in reject_paths: return head_fail(c)
if os.access( n + ".py", os.X_OK ): return head_pass(c)
if os.access( n, os.R_OK ): return head_pass(c)
return head_fail(c)
def do_GET(c):
p = c.path
log_client_address(c,'GET')
while len(p) > 0 and p[0] == '/': p = p[1:]
if len(p) < 1: p = 'index'
if len(p) < 1: return handle_default(c)
p,q = urlparse('http://hostname/'+p)[2:5:2]
p = p[1:]
ps = p.split("/")
for s in ps:
if len(s) < 1: return request_fail(c)
if s[0] == '.': return request_fail(c)
for i in range(len(ps),-1,-1):
d = "/".join( ps[0:i-1] )
n = "/".join( ps[0:i] )
r = "/".join( ps[i:] )
if len(d) < 1: d='.'
if n in reject_paths: return request_fail(c)
if os.access( n + ".py", os.X_OK ): return handle_module(c,d,n,r,q)
if os.access( n, os.R_OK ): return handle_file(c,d,n)
return request_fail(c)
#-----------------------------------------------------------------------------
# Mixed class with HTTPServer and ForkingMixIn.
#-----------------------------------------------------------------------------
class my_forking_server(SocketServer.ForkingMixIn, BaseHTTPServer.HTTPServer):
pass
#-----------------------------------------------------------------------------
# Main server start.
#-----------------------------------------------------------------------------
def server( listen_host = None, listen_port = None ):
if not listen_host: listen_host = "127.0.0.1"
if not listen_port: listen_port = 8080
listen_host_port = ( listen_host, listen_port )
httpd = my_forking_server( listen_host_port, my_handle_http_request )
print time.asctime(), "Server Starts - %s:%s" % listen_host_port
try: httpd.serve_forever()
except KeyboardInterrupt: pass
httpd.server_close()
print "\n"+time.asctime(), "Server Stops - %s:%s" % listen_host_port
return 0
def main( argv ):
listen_host = None
listen_port = None
if len(argv) > 1: listen_host = argv[1]
if len(argv) > 2: listen_port = int( argv[2] )
return server( listen_host, listen_port )
if __name__ == '__main__':
main( sys.argv )