gdritter repos melvil / ca686f1
Basic working (but buggy and incomplete) functionality Getty Ritter 4 years ago
2 changed file(s) with 234 addition(s) and 0 deletion(s). Collapse all Expand all
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 yaml
6 import routeconf
7
8
9 class Sample(Resource):
10 isLeaf = True
11
12 def __init__(self, routes, agent):
13 Resource.__init__(self)
14 self.routes = routes
15 self.agent = agent
16
17 def render_GET(self, request):
18 routeconf.domain_for(request)
19 return self.routes.handle(request, self.agent)
20
21 if __name__ == '__main__':
22 routes = routeconf.load_routes('sample')
23 agent = Agent(reactor)
24 print routes.pretty()
25 reactor.listenTCP(8080, Site(Sample(routes, agent)))
26 reactor.run()
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)