diff -r 9353c509132d foolscap/pb.py
a
|
b
|
|
26 | 26 | |
27 | 27 | |
28 | 28 | Listeners = [] |
29 | | class Listener(protocol.ServerFactory): |
| 29 | class Listener(protocol.ServerFactory, service.MultiService): |
30 | 30 | """I am responsible for a single listening port, which may connect to |
31 | 31 | multiple Tubs. I have a strports-based Service, which I will attach as a |
32 | 32 | child of one of my Tubs. If that Tub disconnects, I will reparent the |
… |
… |
|
39 | 39 | |
40 | 40 | # this also serves as the ServerFactory |
41 | 41 | |
42 | | def __init__(self, port, options={}, |
| 42 | def __init__(self, port=None, portFile=None, options={}, |
43 | 43 | negotiationClass=negotiate.Negotiation): |
44 | 44 | """ |
45 | 45 | @type port: string |
46 | 46 | @param port: a L{twisted.application.strports} -style description. |
47 | 47 | """ |
48 | | name, args, kw = strports.parse(port, None) |
49 | | assert name in ("TCP", "UNIX") # TODO: IPv6 |
| 48 | service.MultiService.__init__(self) |
| 49 | assert port or portFile |
| 50 | self.portFile = portFile |
| 51 | if not port: |
| 52 | # get it from the portfile (on second and later runs) |
| 53 | try: |
| 54 | port = open(self.portFile, "rb").read().strip() |
| 55 | except EnvironmentError: |
| 56 | # must be the first run. We'll use "0". |
| 57 | pass |
| 58 | if port: |
| 59 | name, args, kw = strports.parse(port, None) |
| 60 | assert name in ("TCP", "UNIX") # TODO: IPv6 |
50 | 61 | self.port = port |
51 | 62 | self.options = options |
52 | 63 | self.negotiationClass = negotiationClass |
53 | 64 | self.parentTub = None |
54 | 65 | self.tubs = {} |
55 | 66 | self.redirects = {} |
56 | | self.s = strports.service(port, self) |
57 | 67 | Listeners.append(self) |
58 | 68 | |
59 | 69 | def getPortnum(self): |
… |
… |
|
69 | 79 | """ |
70 | 80 | |
71 | 81 | assert self.s.running |
72 | | name, args, kw = strports.parse(self.port, None) |
73 | | assert name in ("TCP",) |
74 | 82 | return self.s._port.getHost().port |
75 | 83 | |
76 | 84 | def __repr__(self): |
… |
… |
|
82 | 90 | return "<Listener at 0x%x on %s with no tubs>" % (abs(id(self)), |
83 | 91 | self.port) |
84 | 92 | |
| 93 | def startService(self): |
| 94 | port = self.port |
| 95 | need_to_write_portfile = False |
| 96 | if not port: |
| 97 | # the portFile was empty |
| 98 | need_to_write_portfile = True |
| 99 | port = "0" |
| 100 | self.s = strports.service(port, self) |
| 101 | self.s.setServiceParent(self) |
| 102 | service.MultiService.startService(self) |
| 103 | # now the new port should be listening, so we can grab its portnum |
| 104 | if self.portFile: |
| 105 | open(self.portFile, "wb").write("%d\n" % self.getPortnum()) |
| 106 | |
85 | 107 | def addTub(self, tub): |
86 | 108 | if tub.tubID in self.tubs: |
87 | 109 | if tub.tubID is None: |
… |
… |
|
93 | 115 | self.tubs[tub.tubID] = tub |
94 | 116 | if self.parentTub is None: |
95 | 117 | self.parentTub = tub |
96 | | self.s.setServiceParent(self.parentTub) |
| 118 | self.setServiceParent(self.parentTub) |
97 | 119 | |
98 | 120 | def removeTub(self, tub): |
99 | 121 | # this might return a Deferred, since the removal might cause the |
… |
… |
|
108 | 130 | # disownServiceParent, so the port remains listening. Can we |
109 | 131 | # do this? It looks like setServiceParent does |
110 | 132 | # disownServiceParent first, so it may glitch. |
111 | | self.s.setServiceParent(self.parentTub) |
| 133 | self.setServiceParent(self.parentTub) |
112 | 134 | else: |
113 | 135 | # no more tubs, this Listener will go away now |
114 | | d = self.s.disownServiceParent() |
| 136 | d = self.disownServiceParent() |
115 | 137 | Listeners.remove(self) |
116 | 138 | return d |
117 | 139 | return None |
118 | 140 | |
119 | | def getService(self): |
120 | | return self.s |
121 | | |
122 | 141 | def addRedirect(self, tubID, location): |
123 | 142 | assert tubID is not None # unauthenticated Tubs don't get redirects |
124 | 143 | self.redirects[tubID] = location |
… |
… |
|
529 | 548 | d.addCallback(_got_local_ip) |
530 | 549 | return d |
531 | 550 | |
532 | | def listenOn(self, what, options={}): |
| 551 | def listenOn(self, what=None, portFile=None, options={}): |
533 | 552 | """Start listening for connections. |
534 | 553 | |
535 | | @type what: string or Listener instance |
| 554 | @type what: string or Listener instance, or None to use portFile= |
536 | 555 | @param what: a L{twisted.application.strports} -style description, |
537 | 556 | or a L{Listener} instance returned by a previous call to |
538 | 557 | listenOn. |
| 558 | @param portFile: a string, or None to use what=. If provided, the |
| 559 | Listener will use this file to store the port on |
| 560 | which it listens. If the file does not exist when |
| 561 | the Listener is started, it will pick an arbitrary |
| 562 | free port (on all interfaces) to use, and will store |
| 563 | it in the file. If this file does exist, it will |
| 564 | read the port number from this file. |
539 | 565 | @param options: a dictionary of options that can influence connection |
540 | 566 | negotiation before the target Tub has been determined |
541 | 567 | |
| 568 | You must provide either what= or portFile=, not both. |
| 569 | |
542 | 570 | @return: The Listener object that was created. This can be used to |
543 | 571 | stop listening later on, to have another Tub listen on the same port, |
544 | 572 | and to figure out which port was allocated when you used a strports |
545 | 573 | specification of 'tcp:0'. """ |
546 | 574 | |
547 | | if type(what) is str: |
548 | | l = Listener(what, options, self.negotiationClass) |
549 | | else: |
| 575 | assert what or portFile |
| 576 | if isinstance(what, Listener): |
550 | 577 | assert not options |
551 | 578 | l = what |
| 579 | else: |
| 580 | l = Listener(what, portFile, options, self.negotiationClass) |
552 | 581 | assert l not in self.listeners |
553 | 582 | l.addTub(self) |
554 | 583 | self.listeners.append(l) |
diff -r 9353c509132d foolscap/test/test_tub.py
a
|
b
|
|
117 | 117 | d.addCallback(_check) |
118 | 118 | return d |
119 | 119 | |
| 120 | class PortFile(unittest.TestCase): |
| 121 | |
| 122 | def setUp(self): |
| 123 | self.s = service.MultiService() |
| 124 | self.s.startService() |
| 125 | |
| 126 | def tearDown(self): |
| 127 | d = self.s.stopService() |
| 128 | d.addCallback(flushEventualQueue) |
| 129 | return d |
| 130 | |
| 131 | def test_portfile(self): |
| 132 | basedir = "tub/PortFile/portfile" |
| 133 | os.makedirs(basedir) |
| 134 | fn = os.path.join(basedir, "port") |
| 135 | t = GoodEnoughTub() |
| 136 | l = t.listenOn(portFile=fn) |
| 137 | self.failIf(os.path.exists(fn)) |
| 138 | t.setServiceParent(self.s) |
| 139 | self.failUnless(os.path.exists(fn)) |
| 140 | p = int(open(fn, "rb").read().strip()) |
| 141 | self.failUnlessEqual(p, l.getPortnum()) |
| 142 | d = t.disownServiceParent() |
| 143 | |
| 144 | def _next(ign): |
| 145 | t2 = GoodEnoughTub() |
| 146 | l2 = t2.listenOn(portFile=fn) |
| 147 | t2.setServiceParent(self.s) |
| 148 | self.failUnlessEqual(p, l2.getPortnum()) |
| 149 | t2.disownServiceParent() |
| 150 | d.addCallback(_next) |
| 151 | return d |
120 | 152 | |
121 | 153 | |
122 | 154 | class FurlFile(unittest.TestCase): |