| 24 | | |
|---|
| 25 | | import struct, socket, new, sys |
|---|
| 26 | | from twisted.internet import interfaces, defer |
|---|
| 27 | | from twisted.spread import pb |
|---|
| 28 | | |
|---|
| 29 | | |
|---|
| 30 | | class DeferredTracker(object): |
|---|
| 31 | | """ |
|---|
| 32 | | I allow you to track and wait for deferreds without actually having |
|---|
| 33 | | received a reference to them. |
|---|
| 34 | | """ |
|---|
| 35 | | def __init__(self): |
|---|
| 36 | | self.list = [] |
|---|
| 37 | | |
|---|
| 38 | | def put(self, d): |
|---|
| 39 | | """ |
|---|
| 40 | | Put another deferred in the tracker. |
|---|
| 41 | | """ |
|---|
| 42 | | if not isinstance(d, defer.Deferred): |
|---|
| 43 | | raise TypeError("Object '%s' is not a deferred" % d) |
|---|
| 44 | | self.list.append(d) |
|---|
| 45 | | |
|---|
| 46 | | def deferToAll(self): |
|---|
| 47 | | """ |
|---|
| 48 | | Return a deferred that tracks all active deferreds that aren't yet |
|---|
| 49 | | being tracked. When the tracked deferreds fire, the returned deferred |
|---|
| 50 | | fires, too. |
|---|
| 51 | | """ |
|---|
| 52 | | if self.list: |
|---|
| 53 | | d = defer.DeferredList(self.list) |
|---|
| 54 | | self.list = [] |
|---|
| 55 | | elif hasattr(self, 'd_WFA') and not self.d_WFA.called(): |
|---|
| 56 | | d = defer.Deferred() |
|---|
| 57 | | self.d_WFA.chainDeferred(d) |
|---|
| 58 | | else: |
|---|
| 59 | | d = defer.succeed(None) |
|---|
| 60 | | return d |
|---|
| 61 | | |
|---|
| 62 | | def deferToLast(self): |
|---|
| 63 | | """ |
|---|
| 64 | | Return a deferred that tracks the deferred that was most recently put |
|---|
| 65 | | in the tracker. When the tracked deferred fires, the returned deferred |
|---|
| 66 | | fires, too. |
|---|
| 67 | | """ |
|---|
| 68 | | if self.list: |
|---|
| 69 | | d = defer.Deferred() |
|---|
| 70 | | self.list.pop().chainDeferred(d) |
|---|
| 71 | | elif hasattr(self, 'd_WFL') and not self.d_WFL.called(): |
|---|
| 72 | | d = defer.Deferred() |
|---|
| 73 | | self.d_WFL.chainDeferred(d) |
|---|
| 74 | | else: |
|---|
| 75 | | d = defer.succeed(None) |
|---|
| 76 | | return d |
|---|
| 77 | | |
|---|
| 78 | | |
|---|
| 79 | | class AddressRestrictorMixin: |
|---|
| 80 | | """ |
|---|
| 81 | | Mix me in with your normal L{twisted.internet.interfaces.IProtocolFactory} |
|---|
| 82 | | implementer to restrict the client addresses that are permitted to connect |
|---|
| 83 | | to your server. |
|---|
| 84 | | """ |
|---|
| 85 | | def addSubnet(self, subnet): |
|---|
| 86 | | """ |
|---|
| 87 | | Adds a subnet for permitted client connection addresses, e.g., |
|---|
| 88 | | '192.168.1.1/24'. |
|---|
| 89 | | """ |
|---|
| 90 | | subnetSequence = subnet.split("/") |
|---|
| 91 | | base = self._quadToInt(subnetSequence[0]) |
|---|
| 92 | | if len(subnetSequence) == 1: |
|---|
| 93 | | bits = 32 |
|---|
| 94 | | else: |
|---|
| 95 | | bits = subnetSequence[1] |
|---|
| 96 | | subnets = getattr(self, '_subnets', []) |
|---|
| 97 | | subnets.append((base, int(bits))) |
|---|
| 98 | | self._subnets = subnets |
|---|
| 99 | | |
|---|
| 100 | | def _quadToInt(self, quad): |
|---|
| 101 | | """ |
|---|
| 102 | | Converts the dotted-quad address string supplied as I{quad} into an |
|---|
| 103 | | integer. |
|---|
| 104 | | """ |
|---|
| 105 | | return struct.unpack(">L", socket.inet_aton(quad))[0] |
|---|
| 106 | | |
|---|
| 107 | | def testAddress(self, quad): |
|---|
| 108 | | """ |
|---|
| 109 | | Tests the supplied IP address for a match with the subnet(s) defined |
|---|
| 110 | | for acceptance. |
|---|
| 111 | | |
|---|
| 112 | | @param quad: A string representing an IPv4 address in dotted-quad |
|---|
| 113 | | format. |
|---|
| 114 | | |
|---|
| 115 | | """ |
|---|
| 116 | | addrInt = self._quadToInt(quad) |
|---|
| 117 | | for base, bits in self._subnets: |
|---|
| 118 | | maskString = '1'*bits + '0'*(32-bits) |
|---|
| 119 | | mask = int(maskString, 2) |
|---|
| 120 | | if (addrInt & mask) == (base & mask): |
|---|
| 121 | | return True |
|---|
| 122 | | return False |
|---|
| 123 | | |
|---|
| 124 | | def buildProtocol(self, addr): |
|---|
| 125 | | """ |
|---|
| 126 | | A protocol is only returned if the address matches my IP subnet. |
|---|
| 127 | | Otherwise, the connection immediately closes. |
|---|
| 128 | | """ |
|---|
| 129 | | if self.testAddress(addr.host): |
|---|
| 130 | | for BaseClass in self.__class__.__bases__: |
|---|
| 131 | | if interfaces.IProtocolFactory.implementedBy(BaseClass): |
|---|
| 132 | | return BaseClass.buildProtocol(self, addr) |
|---|
| 133 | | |
|---|
| 134 | | |
|---|
| 135 | | class PerspectiveFactory(object): |
|---|
| 136 | | """ |
|---|
| 137 | | I generate perspectives for clients with varying access |
|---|
| 138 | | privileges. Construct me with an I{interfaceMap} dict and a |
|---|
| 139 | | I{perspectiveList} sequence. |
|---|
| 140 | | |
|---|
| 141 | | Each entry of the interface map is keyed by the name of an access privilege |
|---|
| 142 | | and has as its value a sequence of one or more interfaces that must be |
|---|
| 143 | | provided by the perspective object in order to grant the client such |
|---|
| 144 | | access. |
|---|
| 145 | | |
|---|
| 146 | | The perspective list contains all of the perspective classes that implement |
|---|
| 147 | | the interfaces in the interface map. Each perspective that I generate will |
|---|
| 148 | | be an instance of a composite of those classes that implement a required |
|---|
| 149 | | interfaces. |
|---|
| 150 | | """ |
|---|
| 151 | | def __init__(self, interfaceMap, perspectiveList): |
|---|
| 152 | | self.interfaceMap = interfaceMap |
|---|
| 153 | | self.perspectiveList = perspectiveList |
|---|
| 154 | | self.implementorMap = {} |
|---|
| 155 | | |
|---|
| 156 | | def implementor(self, interface): |
|---|
| 157 | | """ |
|---|
| 158 | | Returns the first perspective class in my C{perspectiveList} sequence |
|---|
| 159 | | that implements the supplied I{interface}, raising an exception if none |
|---|
| 160 | | do so. |
|---|
| 161 | | """ |
|---|
| 162 | | result = self.implementorMap.get(interface, None) |
|---|
| 163 | | if result is None: |
|---|
| 164 | | for candidate in self.perspectiveList: |
|---|
| 165 | | if interface.implementedBy(candidate): |
|---|
| 166 | | result = self.implementorMap[interface] = candidate |
|---|
| 167 | | break |
|---|
| 168 | | else: |
|---|
| 169 | | raise NotImplementedError( |
|---|
| 170 | | "No perspective class implements '%s'" % repr(interface)) |
|---|
| 171 | | return result |
|---|
| 172 | | |
|---|
| 173 | | def perspective(self, privileges, **kw): |
|---|
| 174 | | """ |
|---|
| 175 | | Returns to the client a perspective instance that provides interfaces |
|---|
| 176 | | in accordance with the supplied list of I{privileges}. Any keywords |
|---|
| 177 | | supplied define the names and values of attributes that the instance is |
|---|
| 178 | | assigned. |
|---|
| 179 | | |
|---|
| 180 | | The interfaces provided by the perspective are mapped out in my |
|---|
| 181 | | I{interfaceMap} dict. |
|---|
| 182 | | """ |
|---|
| 183 | | baseClasses = [pb.Avatar] |
|---|
| 184 | | for thisPrivilege in privileges: |
|---|
| 185 | | requiredInterfaces = self.interfaceMap.get(thisPrivilege, []) |
|---|
| 186 | | for thisInterface in requiredInterfaces: |
|---|
| 187 | | implementor = self.implementor(thisInterface) |
|---|
| 188 | | if implementor is not None and implementor not in baseClasses: |
|---|
| 189 | | baseClasses.append(implementor) |
|---|
| 190 | | # Create and instantiate the user's custom perspective class |
|---|
| 191 | | Perspective = new.classobj('Perspective', tuple(baseClasses), {}) |
|---|
| 192 | | thisPerspective = Perspective() |
|---|
| 193 | | # Set the perspective's instance attributes |
|---|
| 194 | | for name, value in kw.iteritems(): |
|---|
| 195 | | setattr(thisPerspective, name, value) |
|---|
| 196 | | # The perspective is ready to give to the client |
|---|
| 197 | | return thisPerspective |
|---|
| 198 | | |
|---|
| 199 | | |
|---|
| 200 | | class TracerMixin(object): |
|---|
| 201 | | """ |
|---|
| 202 | | Mix me in to trace problems |
|---|
| 203 | | """ |
|---|
| 204 | | isTracing = False |
|---|
| 205 | | traceFrame = None |
|---|
| 206 | | traceStack = [ |
|---|
| 207 | | # Bottom frame on up, just like a stack. The following items just |
|---|
| 208 | | # represent an example that was used in Trac/WSGI debugging. |
|---|
| 209 | | ('cache.py', 'get_changes'), |
|---|
| 210 | | ('changeset.py', 'get_changes'), |
|---|
| 211 | | ('changeset.py', '_render_html') |
|---|
| 212 | | ] |
|---|
| 213 | | traceLevels = 5 |
|---|
| 214 | | |
|---|
| 215 | | def settrace(self): |
|---|
| 216 | | """ |
|---|
| 217 | | Call this method to start tracing. If you're using threads, you must |
|---|
| 218 | | call it within the same thread whose execution you want to trace. |
|---|
| 219 | | """ |
|---|
| 220 | | sys.settrace(self.trace) |
|---|
| 221 | | self.isTracing = True |
|---|
| 222 | | |
|---|
| 223 | | def trace(self, frame, event, arg): |
|---|
| 224 | | """ |
|---|
| 225 | | This is the actual trace function. |
|---|
| 226 | | """ |
|---|
| 227 | | def msg(level): |
|---|
| 228 | | values = ["." * level] |
|---|
| 229 | | values.extend([ |
|---|
| 230 | | getattr(frame.f_code, "co_%s" % x) |
|---|
| 231 | | for x in ('filename', 'firstlineno', 'name')]) |
|---|
| 232 | | if values != getattr(self, '_prevMsgValues', None): |
|---|
| 233 | | self._prevMsgValues = values |
|---|
| 234 | | print "%s %s (%04d): %s" % tuple(values) |
|---|
| 235 | | |
|---|
| 236 | | def isTraceEntry(k, thisFrame): |
|---|
| 237 | | if thisFrame.f_code.co_name == self.traceStack[k][1]: |
|---|
| 238 | | tail = "/%s" % self.traceStack[k][0] |
|---|
| 239 | | if thisFrame.f_code.co_filename.endswith(tail): |
|---|
| 240 | | return True |
|---|
| 241 | | |
|---|
| 242 | | def frameGenerator(N): |
|---|
| 243 | | nextFrame = frame |
|---|
| 244 | | for k in xrange(N): |
|---|
| 245 | | yield k, nextFrame |
|---|
| 246 | | nextFrame = nextFrame.f_back |
|---|
| 247 | | |
|---|
| 248 | | if not self.isTracing: |
|---|
| 249 | | return |
|---|
| 250 | | if event == 'call': |
|---|
| 251 | | level = 1 |
|---|
| 252 | | if self.traceFrame is None: |
|---|
| 253 | | N = len(self.traceStack) |
|---|
| 254 | | for k, thisFrame in frameGenerator(N): |
|---|
| 255 | | if not isTraceEntry(k, thisFrame): |
|---|
| 256 | | return |
|---|
| 257 | | else: |
|---|
| 258 | | print "-" * 40 |
|---|
| 259 | | self.traceFrame = frame |
|---|
| 260 | | else: |
|---|
| 261 | | for k, thisFrame in frameGenerator(self.traceLevels): |
|---|
| 262 | | level += 1 |
|---|
| 263 | | if thisFrame in (None, self.traceFrame): |
|---|
| 264 | | break |
|---|
| 265 | | else: |
|---|
| 266 | | return |
|---|
| 267 | | msg(level) |
|---|
| 268 | | return self.trace |
|---|
| 269 | | elif event == 'return' and frame == self.traceFrame: |
|---|
| 270 | | self.traceFrame = None |
|---|
| 271 | | print "->", arg |
|---|