| 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 |