gdritter repos melvil / 2a5b87f
Renamed project to Melvil Getty Ritter 4 years ago
5 changed file(s) with 255 addition(s) and 248 deletion(s). Collapse all Expand all
+0
-38
aloysius/aloysius.py less more
1 from twisted.web.client import Agent
2 from twisted.web.server import Site
3 from twisted.web.resource import Resource
4 from twisted.internet import reactor
5 import os
6 import sys
7 import yaml
8 import routeconf
9
10
11 class Sample(Resource):
12 isLeaf = True
13
14 def __init__(self, routes, agent):
15 Resource.__init__(self)
16 self.routes = routes
17 self.agent = agent
18
19 def render_GET(self, request):
20 routeconf.domain_for(request)
21 return self.routes.handle(request, self.agent)
22
23
24 def main():
25 if len(sys.argv) > 1:
26 conf_dir = sys.argv[1]
27 else:
28 conf_dir = os.getenv('ALOYS_DIR', os.getcwd())
29 port = int(os.getenv('PORT', 8080))
30
31 routes = routeconf.load_routes(conf_dir)
32 agent = Agent(reactor)
33 sys.stderr.write(routes.pretty())
34 reactor.listenTCP(port, Site(Sample(routes, agent)))
35 reactor.run()
36
37 if __name__ == '__main__':
38 main()
+0
-208
aloysius/routeconf.py less more
1 import os
2 import sys
3 from twisted.web.server import NOT_DONE_YET
4 from twisted.web.client import readBody
5
6
7 def load_routes(location):
8 routes = []
9 if not os.path.isdir(location):
10 raise Exception(
11 'Cannot find specified configure directory: {0}', location)
12 for site in sorted(os.listdir(location)):
13 try:
14 routes.append(load_site(os.path.join(location, site)))
15 except Exception as e:
16 sys.stderr.write('Unable to load {0}; skipping\n'.format(
17 os.path.abspath(os.path.join(location, site))))
18 sys.stderr.write(' {0}\n\n'.format(e))
19 return RouteList(routes)
20
21
22 def try_load(filename, default=None):
23 if not os.path.exists(filename):
24 return default
25 if not os.path.isfile(os.path.abspath(filename)):
26 raise Exception(
27 'Cannot load {0}: is not a file'.format(filename))
28 with open(filename) as f:
29 return f.read().strip()
30
31
32 def domain_for(request):
33 return request.getHeader('host')
34
35
36 def load_site(loc):
37 if not os.path.isdir(loc):
38 raise Exception('Site spec not a directory: {0}'.format(loc))
39
40 if (not os.path.exists(os.path.join(loc, 'domain')) and
41 not os.path.exists(os.path.join(loc, 'path'))):
42 raise Exception(
43 'The site {0} does not specify a domain or a path'.format(loc))
44 domain = try_load(os.path.join(loc, 'domain'), '*')
45 path = try_load(os.path.join(loc, 'path'), '*').lstrip('/')
46 mode = try_load(os.path.join(loc, 'mode'), 'http')
47 if mode == 'http':
48 dispatch = HTTPDispatch(
49 host=try_load(os.path.join(loc, 'host'), 'localhost'),
50 port=int(try_load(os.path.join(loc, 'port'), 80)))
51 elif mode == 'aloys':
52 dispatch = AloysDispatch(
53 loc=try_load(os.path.join(loc, 'conf'), '/dev/null'))
54 elif mode == 'redir':
55 dispatch = RedirDispatch(
56 resp=int(try_load(os.path.join(loc, 'resp'), 303)),
57 location=try_load(os.path.join(loc, 'location'),
58 'http://localhost/'))
59 else:
60 raise Exception('Unknown mode: {0}'.format(repr(mode)))
61 return Route(domain=domain, path=path, dispatch=dispatch, disk_loc=loc)
62
63
64 def wildcard_match(spec, string):
65 loc_stack = [(0, 0)]
66 while loc_stack:
67 (i, j) = loc_stack.pop()
68 if i >= len(spec) and j >= len(string):
69 return True
70 elif i >= len(spec) or j >= len(string):
71 continue
72 elif spec[i] == string[j]:
73 loc_stack.append((i + 1, j + 1))
74 elif spec[i] == '*':
75 loc_stack.append((i + 1, j + 1))
76 loc_stack.append((i, j + 1))
77 else:
78 continue
79 return False
80
81
82 class RouteList:
83 '''
84 An object which represents an ordered set of possible routing
85 options.
86 '''
87
88 def __init__(self, routes):
89 self.routes = routes
90
91 def handle(self, request, agent):
92 for route in self.routes:
93 if route.matches(request):
94 return route.handle(request, agent)
95 return "unable to handle request"
96
97 def __repr__(self):
98 return 'RouteList([{0}])'.format(
99 ', '.join(repr(r) for r in self.routes))
100
101 def pretty(self, level=0):
102 return ''.join(
103 ' ' * level + r.pretty(level=level)
104 for r in self.routes)
105
106
107 class Route:
108
109 def __init__(self, domain, path, dispatch, disk_loc=''):
110 self.domain = domain
111 self.path = path
112 self.dispatch = dispatch
113 self.disk_loc = disk_loc
114
115 def matches(self, request):
116 return (wildcard_match(self.path, '/' + request.path) and
117 wildcard_match(self.domain, domain_for(request)))
118
119 def handle(self, request, agent):
120 print 'handled by {0}'.format(self.disk_loc)
121 return self.dispatch.handle(request, agent)
122
123 def __repr__(self):
124 return 'Route({0}, {1}, {2}, disk_loc={3})'.format(
125 repr(self.domain),
126 repr(self.path),
127 self.dispatch,
128 repr(self.disk_loc))
129
130 def pretty(self, level=0):
131 return '{0}/{1} => {2}'.format(self.domain,
132 self.path,
133 self.dispatch.pretty(level=level))
134
135
136 class Dispatch:
137
138 def handle(self, request, agent):
139 return '[no route matched your request]'
140
141 def __repr__(self):
142 return '{name}({args})'.format(
143 name=self.__class__.__name__,
144 args=', '.join('{0}={1}'.format(k, repr(v))
145 for (k, v) in self.__dict__.items()))
146
147
148 class HTTPDispatch(Dispatch):
149
150 def __init__(self, host='localhost', port=80):
151 self.host = host
152 self.port = port
153
154 def handle(self, request, agent):
155 def callback(response):
156 for (k, v) in response.headers.getAllRawHeaders():
157 request.setHeader(k, v[0])
158 r = readBody(response)
159 r.addCallback(handle_body)
160 return r
161
162 def handle_body(body):
163 request.write(body)
164 request.finish()
165
166 url = 'http://{0}:{1}{2}'.format(
167 self.host, self.port, request.path)
168 print url
169 agent.request('GET', url, request.requestHeaders, None) \
170 .addCallback(callback)
171 return NOT_DONE_YET
172
173 def pretty(self, level=0):
174 return '{0}:{1}\n'.format(self.host, self.port)
175
176
177 class AloysDispatch(Dispatch):
178 '''
179 Dispatch the route down to a different configuration directory.
180 '''
181
182 def __init__(self, loc):
183 self.routes = load_routes(loc)
184
185 def handle(self, request, agent):
186 return self.routes.handle(request)
187
188 def pretty(self, level=0):
189 return '\n' + self.routes.pretty(level=level + 2)
190
191
192 class RedirDispatch(Dispatch):
193 '''
194 Use a redirect code (probably 303) to manually redirect to a
195 different location.
196 '''
197
198 def __init__(self, resp=303, location='http://localhost/'):
199 self.resp = resp
200 self.location = location
201
202 def handle(self, request, agent):
203 request.setResponseCode(self.resp)
204 request.setHeader('location', self.location)
205 return ''
206
207 def pretty(self, level=0):
208 return 'redir({0})\n'.format(self.location)
1 from twisted.web.client import Agent
2 from twisted.web.server import Site
3 from twisted.web.resource import Resource
4 from twisted.internet import reactor
5 import os
6 import sys
7 import routeconf
8
9
10 class Melvil(Resource):
11 '''
12 The Twisted resource representing a set of redirects. This
13 includes a RouteList object, which represents a set of possible
14 routes.
15 '''
16 isLeaf = True
17
18 def __init__(self, routes, agent):
19 Resource.__init__(self)
20 self.routes = routes
21 self.agent = agent
22
23 def render_GET(self, request):
24 routeconf.domain_for(request)
25 return self.routes.handle(request, self.agent)
26
27 @staticmethod
28 def run_melvil():
29 '''
30 Run a Melvil instance.
31 '''
32 if len(sys.argv) > 1:
33 conf_dir = sys.argv[1]
34 else:
35 conf_dir = os.getenv('MELVIL_DIR', os.getcwd())
36 port = int(os.getenv('PORT', 8080))
37
38 routes = routeconf.load_routes(conf_dir)
39 agent = Agent(reactor)
40 sys.stderr.write(routes.pretty())
41 reactor.listenTCP(port, Site(Melvil(routes, agent)))
42 reactor.run()
43
44 if __name__ == '__main__':
45 Melvil.run_melvil()
1 import os
2 import sys
3 from twisted.web.server import NOT_DONE_YET
4 from twisted.web.client import readBody
5
6
7 def load_routes(location):
8 routes = []
9 if not os.path.isdir(location):
10 raise Exception(
11 'Cannot find specified configure directory: {0}', location)
12 for site in sorted(os.listdir(location)):
13 try:
14 routes.append(load_site(os.path.join(location, site)))
15 except Exception as e:
16 sys.stderr.write('Unable to load {0}; skipping\n'.format(
17 os.path.abspath(os.path.join(location, site))))
18 sys.stderr.write(' {0}\n\n'.format(e))
19 return RouteList(routes)
20
21
22 def try_load(filename, default=None):
23 if not os.path.exists(filename):
24 return default
25 if not os.path.isfile(os.path.abspath(filename)):
26 raise Exception(
27 'Cannot load {0}: is not a file'.format(filename))
28 with open(filename) as f:
29 return f.read().strip()
30
31
32 def domain_for(request):
33 return request.getHeader('host')
34
35
36 def load_site(loc):
37 if not os.path.isdir(loc):
38 raise Exception('Site spec not a directory: {0}'.format(loc))
39
40 if (not os.path.exists(os.path.join(loc, 'domain')) and
41 not os.path.exists(os.path.join(loc, 'path'))):
42 raise Exception(
43 'The site {0} does not specify a domain or a path'.format(loc))
44 domain = try_load(os.path.join(loc, 'domain'), '*')
45 path = try_load(os.path.join(loc, 'path'), '*').lstrip('/')
46 mode = try_load(os.path.join(loc, 'mode'), 'http')
47 if mode == 'http':
48 dispatch = HTTPDispatch(
49 host=try_load(os.path.join(loc, 'host'), 'localhost'),
50 port=int(try_load(os.path.join(loc, 'port'), 80)))
51 elif mode == 'melvil':
52 dispatch = MelvilDispatch(
53 loc=try_load(os.path.join(loc, 'conf'), '/dev/null'))
54 elif mode == 'redir':
55 dispatch = RedirDispatch(
56 resp=int(try_load(os.path.join(loc, 'resp'), 303)),
57 location=try_load(os.path.join(loc, 'location'),
58 'http://localhost/'))
59 else:
60 raise Exception('Unknown mode: {0}'.format(repr(mode)))
61 return Route(domain=domain, path=path, dispatch=dispatch, disk_loc=loc)
62
63
64 def wildcard_match(spec, string):
65 loc_stack = [(0, 0)]
66 while loc_stack:
67 (i, j) = loc_stack.pop()
68 if i >= len(spec) and j >= len(string):
69 return True
70 elif i >= len(spec) or j >= len(string):
71 continue
72 elif spec[i] == string[j]:
73 loc_stack.append((i + 1, j + 1))
74 elif spec[i] == '*':
75 loc_stack.append((i + 1, j + 1))
76 loc_stack.append((i, j + 1))
77 else:
78 continue
79 return False
80
81
82 class RouteList:
83 '''
84 An object which represents an ordered set of possible routing
85 options.
86 '''
87
88 def __init__(self, routes):
89 self.routes = routes
90
91 def handle(self, request, agent):
92 for route in self.routes:
93 if route.matches(request):
94 return route.handle(request, agent)
95 return "unable to handle request"
96
97 def __repr__(self):
98 return 'RouteList([{0}])'.format(
99 ', '.join(repr(r) for r in self.routes))
100
101 def pretty(self, level=0):
102 return ''.join(
103 ' ' * level + r.pretty(level=level)
104 for r in self.routes)
105
106
107 class Route:
108
109 def __init__(self, domain, path, dispatch, disk_loc=''):
110 self.domain = domain
111 self.path = path
112 self.dispatch = dispatch
113 self.disk_loc = disk_loc
114
115 def matches(self, request):
116 return (wildcard_match(self.path, '/' + request.path) and
117 wildcard_match(self.domain, domain_for(request)))
118
119 def handle(self, request, agent):
120 print 'handled by {0}'.format(self.disk_loc)
121 return self.dispatch.handle(request, agent)
122
123 def __repr__(self):
124 return 'Route({0}, {1}, {2}, disk_loc={3})'.format(
125 repr(self.domain),
126 repr(self.path),
127 self.dispatch,
128 repr(self.disk_loc))
129
130 def pretty(self, level=0):
131 return '{0}/{1} => {2}'.format(self.domain,
132 self.path,
133 self.dispatch.pretty(level=level))
134
135
136 class Dispatch:
137
138 def handle(self, request, agent):
139 return '[no route matched your request]'
140
141 def __repr__(self):
142 return '{name}({args})'.format(
143 name=self.__class__.__name__,
144 args=', '.join('{0}={1}'.format(k, repr(v))
145 for (k, v) in self.__dict__.items()))
146
147
148 class HTTPDispatch(Dispatch):
149
150 def __init__(self, host='localhost', port=80):
151 self.host = host
152 self.port = port
153
154 def handle(self, request, agent):
155 def callback(response):
156 for (k, v) in response.headers.getAllRawHeaders():
157 request.setHeader(k, v[0])
158 r = readBody(response)
159 r.addCallback(handle_body)
160 return r
161
162 def handle_body(body):
163 request.write(body)
164 request.finish()
165
166 url = 'http://{0}:{1}{2}'.format(
167 self.host, self.port, request.path)
168 print url
169 agent.request('GET', url, request.requestHeaders, None) \
170 .addCallback(callback)
171 return NOT_DONE_YET
172
173 def pretty(self, level=0):
174 return '{0}:{1}\n'.format(self.host, self.port)
175
176
177 class MelvilDispatch(Dispatch):
178 '''
179 Dispatch the route down to a different configuration directory.
180 '''
181
182 def __init__(self, loc):
183 self.routes = load_routes(loc)
184
185 def handle(self, request, agent):
186 return self.routes.handle(request)
187
188 def pretty(self, level=0):
189 return '\n' + self.routes.pretty(level=level + 2)
190
191
192 class RedirDispatch(Dispatch):
193 '''
194 Use a redirect code (probably 303) to manually redirect to a
195 different location.
196 '''
197
198 def __init__(self, resp=303, location='http://localhost/'):
199 self.resp = resp
200 self.location = location
201
202 def handle(self, request, agent):
203 request.setResponseCode(self.resp)
204 request.setHeader('location', self.location)
205 return ''
206
207 def pretty(self, level=0):
208 return 'redir({0})\n'.format(self.location)
1010 return f.read()
1111
1212 setup(
13 name='aloysius',
13 name='melvil',
1414 version='0.0.6',
1515 author='Getty Ritter',
1616 author_email='getty.ritter@gmail.com',
2323 long_description=read_file('README'),
2424 entry_points={
2525 'console_scripts': [
26 'aloysius = aloysius:main'
26 'melvil = melvil:main'
2727 ]
2828 },
2929 )