import os
import sys
from twisted.web.server import NOT_DONE_YET
from twisted.web.client import readBody
def load_routes(location):
routes = []
if not os.path.isdir(location):
raise Exception(
'Cannot find specified configure directory: {0}', location)
for site in sorted(os.listdir(location)):
try:
routes.append(load_site(os.path.join(location, site)))
except Exception as e:
sys.stderr.write('Unable to load {0}; skipping\n'.format(
os.path.abspath(os.path.join(location, site))))
sys.stderr.write(' {0}\n\n'.format(e))
return RouteList(routes)
def try_load(filename, default=None):
if not os.path.exists(filename):
return default
if not os.path.isfile(os.path.abspath(filename)):
raise Exception(
'Cannot load {0}: is not a file'.format(filename))
with open(filename) as f:
return f.read().strip()
def domain_for(request):
return request.getHeader('host')
def load_site(loc):
if not os.path.isdir(loc):
raise Exception('Site spec not a directory: {0}'.format(loc))
if (not os.path.exists(os.path.join(loc, 'domain')) and
not os.path.exists(os.path.join(loc, 'path'))):
raise Exception(
'The site {0} does not specify a domain or a path'.format(loc))
domain = try_load(os.path.join(loc, 'domain'), '*')
path = try_load(os.path.join(loc, 'path'), '*').lstrip('/')
mode = try_load(os.path.join(loc, 'mode'), 'http')
if mode == 'http':
dispatch = HTTPDispatch(
host=try_load(os.path.join(loc, 'host'), 'localhost'),
port=int(try_load(os.path.join(loc, 'port'), 80)))
elif mode == 'melvil':
dispatch = MelvilDispatch(
loc=try_load(os.path.join(loc, 'conf'), '/dev/null'))
elif mode == 'redir':
dispatch = RedirDispatch(
resp=int(try_load(os.path.join(loc, 'resp'), 303)),
location=try_load(os.path.join(loc, 'location'),
'http://localhost/'))
else:
raise Exception('Unknown mode: {0}'.format(repr(mode)))
return Route(domain=domain, path=path, dispatch=dispatch, disk_loc=loc)
def wildcard_match(spec, string):
loc_stack = [(0, 0)]
while loc_stack:
(i, j) = loc_stack.pop()
if i >= len(spec) and j >= len(string):
return True
elif i >= len(spec) or j >= len(string):
continue
elif spec[i] == string[j]:
loc_stack.append((i + 1, j + 1))
elif spec[i] == '*':
loc_stack.append((i + 1, j + 1))
loc_stack.append((i, j + 1))
else:
continue
return False
class RouteList:
'''
An object which represents an ordered set of possible routing
options.
'''
def __init__(self, routes):
self.routes = routes
def handle(self, request, agent):
for route in self.routes:
if route.matches(request):
return route.handle(request, agent)
return "unable to handle request"
def __repr__(self):
return 'RouteList([{0}])'.format(
', '.join(repr(r) for r in self.routes))
def pretty(self, level=0):
return ''.join(
' ' * level + r.pretty(level=level)
for r in self.routes)
class Route:
def __init__(self, domain, path, dispatch, disk_loc=''):
self.domain = domain
self.path = path
self.dispatch = dispatch
self.disk_loc = disk_loc
def matches(self, request):
return (wildcard_match(self.path, '/' + request.path) and
wildcard_match(self.domain, domain_for(request)))
def handle(self, request, agent):
print 'handled by {0}'.format(self.disk_loc)
return self.dispatch.handle(request, agent)
def __repr__(self):
return 'Route({0}, {1}, {2}, disk_loc={3})'.format(
repr(self.domain),
repr(self.path),
self.dispatch,
repr(self.disk_loc))
def pretty(self, level=0):
return '{0}/{1} => {2}'.format(self.domain,
self.path,
self.dispatch.pretty(level=level))
class Dispatch:
def handle(self, request, agent):
return '[no route matched your request]'
def __repr__(self):
return '{name}({args})'.format(
name=self.__class__.__name__,
args=', '.join('{0}={1}'.format(k, repr(v))
for (k, v) in self.__dict__.items()))
class HTTPDispatch(Dispatch):
def __init__(self, host='localhost', port=80):
self.host = host
self.port = port
def handle(self, request, agent):
def callback(response):
for (k, v) in response.headers.getAllRawHeaders():
request.setHeader(k, v[0])
r = readBody(response)
r.addCallback(handle_body)
return r
def handle_body(body):
request.write(body)
request.finish()
url = 'http://{0}:{1}{2}'.format(
self.host, self.port, request.path)
print url
agent.request('GET', url, request.requestHeaders, None) \
.addCallback(callback)
return NOT_DONE_YET
def pretty(self, level=0):
return '{0}:{1}\n'.format(self.host, self.port)
class MelvilDispatch(Dispatch):
'''
Dispatch the route down to a different configuration directory.
'''
def __init__(self, loc):
self.routes = load_routes(loc)
def handle(self, request, agent):
return self.routes.handle(request)
def pretty(self, level=0):
return '\n' + self.routes.pretty(level=level + 2)
class RedirDispatch(Dispatch):
'''
Use a redirect code (probably 303) to manually redirect to a
different location.
'''
def __init__(self, resp=303, location='http://localhost/'):
self.resp = resp
self.location = location
def handle(self, request, agent):
request.setResponseCode(self.resp)
request.setHeader('location', self.location)
return ''
def pretty(self, level=0):
return 'redir({0})\n'.format(self.location)