Renamed project to Melvil
Getty Ritter
8 years ago
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() |
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) |
10 | 10 | return f.read() |
11 | 11 | |
12 | 12 | setup( |
13 |
name=' |
|
13 | name='melvil', | |
14 | 14 | version='0.0.6', |
15 | 15 | author='Getty Ritter', |
16 | 16 | author_email='getty.ritter@gmail.com', |
23 | 23 | long_description=read_file('README'), |
24 | 24 | entry_points={ |
25 | 25 | 'console_scripts': [ |
26 |
' |
|
26 | 'melvil = melvil:main' | |
27 | 27 | ] |
28 | 28 | }, |
29 | 29 | ) |