Ticket #137: 137.diff

File 137.diff, 7.1 KB (added by Brian Warner, 14 years ago)

starting point for this feature

  • foolscap/pb.py

    diff -r 9353c509132d foolscap/pb.py
    a b  
    2626
    2727
    2828Listeners = []
    29 class Listener(protocol.ServerFactory):
     29class Listener(protocol.ServerFactory, service.MultiService):
    3030    """I am responsible for a single listening port, which may connect to
    3131    multiple Tubs. I have a strports-based Service, which I will attach as a
    3232    child of one of my Tubs. If that Tub disconnects, I will reparent the
     
    3939
    4040    # this also serves as the ServerFactory
    4141
    42     def __init__(self, port, options={},
     42    def __init__(self, port=None, portFile=None, options={},
    4343                 negotiationClass=negotiate.Negotiation):
    4444        """
    4545        @type port: string
    4646        @param port: a L{twisted.application.strports} -style description.
    4747        """
    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
    5061        self.port = port
    5162        self.options = options
    5263        self.negotiationClass = negotiationClass
    5364        self.parentTub = None
    5465        self.tubs = {}
    5566        self.redirects = {}
    56         self.s = strports.service(port, self)
    5767        Listeners.append(self)
    5868
    5969    def getPortnum(self):
     
    6979        """
    7080
    7181        assert self.s.running
    72         name, args, kw = strports.parse(self.port, None)
    73         assert name in ("TCP",)
    7482        return self.s._port.getHost().port
    7583
    7684    def __repr__(self):
     
    8290        return "<Listener at 0x%x on %s with no tubs>" % (abs(id(self)),
    8391                                                          self.port)
    8492
     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
    85107    def addTub(self, tub):
    86108        if tub.tubID in self.tubs:
    87109            if tub.tubID is None:
     
    93115        self.tubs[tub.tubID] = tub
    94116        if self.parentTub is None:
    95117            self.parentTub = tub
    96             self.s.setServiceParent(self.parentTub)
     118            self.setServiceParent(self.parentTub)
    97119
    98120    def removeTub(self, tub):
    99121        # this might return a Deferred, since the removal might cause the
     
    108130                # disownServiceParent, so the port remains listening. Can we
    109131                # do this? It looks like setServiceParent does
    110132                # disownServiceParent first, so it may glitch.
    111                 self.s.setServiceParent(self.parentTub)
     133                self.setServiceParent(self.parentTub)
    112134            else:
    113135                # no more tubs, this Listener will go away now
    114                 d = self.s.disownServiceParent()
     136                d = self.disownServiceParent()
    115137                Listeners.remove(self)
    116138                return d
    117139        return None
    118140
    119     def getService(self):
    120         return self.s
    121 
    122141    def addRedirect(self, tubID, location):
    123142        assert tubID is not None # unauthenticated Tubs don't get redirects
    124143        self.redirects[tubID] = location
     
    529548        d.addCallback(_got_local_ip)
    530549        return d
    531550
    532     def listenOn(self, what, options={}):
     551    def listenOn(self, what=None, portFile=None, options={}):
    533552        """Start listening for connections.
    534553
    535         @type  what: string or Listener instance
     554        @type  what: string or Listener instance, or None to use portFile=
    536555        @param what: a L{twisted.application.strports} -style description,
    537556                     or a L{Listener} instance returned by a previous call to
    538557                     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.
    539565        @param options: a dictionary of options that can influence connection
    540566                        negotiation before the target Tub has been determined
    541567
     568        You must provide either what= or portFile=, not both.
     569
    542570        @return: The Listener object that was created. This can be used to
    543571        stop listening later on, to have another Tub listen on the same port,
    544572        and to figure out which port was allocated when you used a strports
    545573        specification of 'tcp:0'. """
    546574
    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):
    550577            assert not options
    551578            l = what
     579        else:
     580            l = Listener(what, portFile, options, self.negotiationClass)
    552581        assert l not in self.listeners
    553582        l.addTub(self)
    554583        self.listeners.append(l)
  • foolscap/test/test_tub.py

    diff -r 9353c509132d foolscap/test/test_tub.py
    a b  
    117117        d.addCallback(_check)
    118118        return d
    119119
     120class 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
    120152
    121153
    122154class FurlFile(unittest.TestCase):