OLD | NEW |
| (Empty) |
1 # -*- test-case-name: twisted.web.test.test_web -*- | |
2 | |
3 # Copyright (c) 2001-2004 Twisted Matrix Laboratories. | |
4 # See LICENSE for details. | |
5 | |
6 | |
7 """Distributed web servers. | |
8 | |
9 This is going to have to be refactored so that argument parsing is done | |
10 by each subprocess and not by the main web server (i.e. GET, POST etc.). | |
11 """ | |
12 | |
13 # System Imports | |
14 import types, os, copy, string, cStringIO | |
15 if (os.sys.platform != 'win32') and (os.name != 'java'): | |
16 import pwd | |
17 | |
18 # Twisted Imports | |
19 from twisted.spread import pb | |
20 from twisted.web import http | |
21 from twisted.python import log | |
22 from twisted.persisted import styles | |
23 from twisted.web.woven import page | |
24 from twisted.internet import address, reactor | |
25 | |
26 # Sibling Imports | |
27 import resource | |
28 import server | |
29 import error | |
30 import html | |
31 import static | |
32 from server import NOT_DONE_YET | |
33 | |
34 class _ReferenceableProducerWrapper(pb.Referenceable): | |
35 def __init__(self, producer): | |
36 self.producer = producer | |
37 | |
38 def remote_resumeProducing(self): | |
39 self.producer.resumeProducing() | |
40 | |
41 def remote_pauseProducing(self): | |
42 self.producer.pauseProducing() | |
43 | |
44 def remote_stopProducing(self): | |
45 self.producer.stopProducing() | |
46 | |
47 | |
48 class Request(pb.RemoteCopy, server.Request): | |
49 def setCopyableState(self, state): | |
50 for k in 'host', 'client': | |
51 tup = state[k] | |
52 addrdesc = {'INET': 'TCP', 'UNIX': 'UNIX'}[tup[0]] | |
53 addr = {'TCP': lambda: address.IPv4Address(addrdesc, | |
54 tup[1], tup[2], | |
55 _bwHack='INET'), | |
56 'UNIX': lambda: address.UNIXAddress(tup[1])}[addrdesc]() | |
57 state[k] = addr | |
58 pb.RemoteCopy.setCopyableState(self, state) | |
59 # Emulate the local request interface -- | |
60 self.content = cStringIO.StringIO(self.content_data) | |
61 self.write = self.remote.remoteMethod('write') | |
62 self.finish = self.remote.remoteMethod('finish') | |
63 self.setHeader = self.remote.remoteMethod('setHeader') | |
64 self.addCookie = self.remote.remoteMethod('addCookie') | |
65 self.setETag = self.remote.remoteMethod('setETag') | |
66 self.setResponseCode = self.remote.remoteMethod('setResponseCode') | |
67 self.setLastModified = self.remote.remoteMethod('setLastModified') | |
68 | |
69 def registerProducer(self, producer, streaming): | |
70 self.remote.callRemote("registerProducer", | |
71 _ReferenceableProducerWrapper(producer), | |
72 streaming).addErrback(self.fail) | |
73 | |
74 def unregisterProducer(self): | |
75 self.remote.callRemote("unregisterProducer").addErrback(self.fail) | |
76 | |
77 def fail(self, failure): | |
78 log.err(failure) | |
79 | |
80 | |
81 pb.setCopierForClass(server.Request, Request) | |
82 | |
83 class Issue: | |
84 def __init__(self, request): | |
85 self.request = request | |
86 | |
87 def finished(self, result): | |
88 if result != NOT_DONE_YET: | |
89 assert isinstance(result, types.StringType),\ | |
90 "return value not a string" | |
91 self.request.write(result) | |
92 self.request.finish() | |
93 | |
94 def failed(self, failure): | |
95 #XXX: Argh. FIXME. | |
96 failure = str(failure) | |
97 self.request.write( | |
98 error.ErrorPage(http.INTERNAL_SERVER_ERROR, | |
99 "Server Connection Lost", | |
100 "Connection to distributed server lost:" + | |
101 html.PRE(failure)). | |
102 render(self.request)) | |
103 self.request.finish() | |
104 log.msg(failure) | |
105 | |
106 | |
107 class ResourceSubscription(resource.Resource): | |
108 isLeaf = 1 | |
109 waiting = 0 | |
110 def __init__(self, host, port): | |
111 resource.Resource.__init__(self) | |
112 self.host = host | |
113 self.port = port | |
114 self.pending = [] | |
115 self.publisher = None | |
116 | |
117 def __getstate__(self): | |
118 """Get persistent state for this ResourceSubscription. | |
119 """ | |
120 # When I unserialize, | |
121 state = copy.copy(self.__dict__) | |
122 # Publisher won't be connected... | |
123 state['publisher'] = None | |
124 # I won't be making a connection | |
125 state['waiting'] = 0 | |
126 # There will be no pending requests. | |
127 state['pending'] = [] | |
128 return state | |
129 | |
130 def connected(self, publisher): | |
131 """I've connected to a publisher; I'll now send all my requests. | |
132 """ | |
133 log.msg('connected to publisher') | |
134 publisher.broker.notifyOnDisconnect(self.booted) | |
135 self.publisher = publisher | |
136 self.waiting = 0 | |
137 for request in self.pending: | |
138 self.render(request) | |
139 self.pending = [] | |
140 | |
141 def notConnected(self, msg): | |
142 """I can't connect to a publisher; I'll now reply to all pending | |
143 requests. | |
144 """ | |
145 log.msg("could not connect to distributed web service: %s" % msg) | |
146 self.waiting = 0 | |
147 self.publisher = None | |
148 for request in self.pending: | |
149 request.write("Unable to connect to distributed server.") | |
150 request.finish() | |
151 self.pending = [] | |
152 | |
153 def booted(self): | |
154 self.notConnected("connection dropped") | |
155 | |
156 def render(self, request): | |
157 """Render this request, from my server. | |
158 | |
159 This will always be asynchronous, and therefore return NOT_DONE_YET. | |
160 It spins off a request to the pb client, and either adds it to the list | |
161 of pending issues or requests it immediately, depending on if the | |
162 client is already connected. | |
163 """ | |
164 if not self.publisher: | |
165 self.pending.append(request) | |
166 if not self.waiting: | |
167 self.waiting = 1 | |
168 bf = pb.PBClientFactory() | |
169 timeout = 10 | |
170 if self.host == "unix": | |
171 reactor.connectUNIX(self.port, bf, timeout) | |
172 else: | |
173 reactor.connectTCP(self.host, self.port, bf, timeout) | |
174 d = bf.getRootObject() | |
175 d.addCallbacks(self.connected, self.notConnected) | |
176 | |
177 else: | |
178 i = Issue(request) | |
179 self.publisher.callRemote('request', request).addCallbacks(i.finishe
d, i.failed) | |
180 return NOT_DONE_YET | |
181 | |
182 class ResourcePublisher(pb.Root, styles.Versioned): | |
183 def __init__(self, site): | |
184 self.site = site | |
185 | |
186 persistenceVersion = 2 | |
187 | |
188 def upgradeToVersion2(self): | |
189 self.application.authorizer.removeIdentity("web") | |
190 del self.application.services[self.serviceName] | |
191 del self.serviceName | |
192 del self.application | |
193 del self.perspectiveName | |
194 | |
195 def getPerspectiveNamed(self, name): | |
196 return self | |
197 | |
198 def remote_request(self, request): | |
199 res = self.site.getResourceFor(request) | |
200 log.msg( request ) | |
201 return res.render(request) | |
202 | |
203 class UserDirectory(page.Page): | |
204 userDirName = 'public_html' | |
205 userSocketName = '.twistd-web-pb' | |
206 | |
207 template = """ | |
208 <html> | |
209 <head> | |
210 <title>twisted.web.distrib.UserDirectory</title> | |
211 <style> | |
212 | |
213 a | |
214 { | |
215 font-family: Lucida, Verdana, Helvetica, Arial, sans-serif; | |
216 color: #369; | |
217 text-decoration: none; | |
218 } | |
219 | |
220 th | |
221 { | |
222 font-family: Lucida, Verdana, Helvetica, Arial, sans-serif; | |
223 font-weight: bold; | |
224 text-decoration: none; | |
225 text-align: left; | |
226 } | |
227 | |
228 pre, code | |
229 { | |
230 font-family: "Courier New", Courier, monospace; | |
231 } | |
232 | |
233 p, body, td, ol, ul, menu, blockquote, div | |
234 { | |
235 font-family: Lucida, Verdana, Helvetica, Arial, sans-serif; | |
236 color: #000; | |
237 } | |
238 | |
239 </style> | |
240 <base view="Attributes" model="base" /> | |
241 </head> | |
242 | |
243 <body> | |
244 <h1>twisted.web.distrib.UserDirectory</h1> | |
245 | |
246 <ul view="List" model="directory"> | |
247 <li pattern="listItem"><a view="Link" /> </li> | |
248 </ul> | |
249 </body> | |
250 </html> | |
251 """ | |
252 | |
253 def wmfactory_base(self, request): | |
254 return {'href':request.prePathURL()} | |
255 | |
256 def wmfactory_directory(self, request): | |
257 m = [] | |
258 for user in pwd.getpwall(): | |
259 pw_name, pw_passwd, pw_uid, pw_gid, pw_gecos, pw_dir, pw_shell \ | |
260 = user | |
261 realname = string.split(pw_gecos,',')[0] | |
262 if not realname: | |
263 realname = pw_name | |
264 if os.path.exists(os.path.join(pw_dir, self.userDirName)): | |
265 m.append({ | |
266 'href':'%s/'%pw_name, | |
267 'text':'%s (file)'%realname | |
268 }) | |
269 twistdsock = os.path.join(pw_dir, self.userSocketName) | |
270 if os.path.exists(twistdsock): | |
271 linknm = '%s.twistd' % pw_name | |
272 m.append({ | |
273 'href':'%s/'%linknm, | |
274 'text':'%s (twistd)'%realname}) | |
275 return m | |
276 | |
277 def getChild(self, name, request): | |
278 if name == '': | |
279 return self | |
280 | |
281 td = '.twistd' | |
282 | |
283 if name[-len(td):] == td: | |
284 username = name[:-len(td)] | |
285 sub = 1 | |
286 else: | |
287 username = name | |
288 sub = 0 | |
289 try: | |
290 pw_name, pw_passwd, pw_uid, pw_gid, pw_gecos, pw_dir, pw_shell \ | |
291 = pwd.getpwnam(username) | |
292 except KeyError: | |
293 return error.NoResource() | |
294 if sub: | |
295 twistdsock = os.path.join(pw_dir, self.userSocketName) | |
296 rs = ResourceSubscription('unix',twistdsock) | |
297 self.putChild(name, rs) | |
298 return rs | |
299 else: | |
300 path = os.path.join(pw_dir, self.userDirName) | |
301 if not os.path.exists(path): | |
302 return error.NoResource() | |
303 return static.File(path) | |
OLD | NEW |