Ticket #6325: 6325_2.patch

File 6325_2.patch, 11.8 KB (added by eddie, 9 years ago)
  • twisted/web/tap.py

     
    66"""
    77
    88import os
     9import warnings
    910
    1011# Twisted Imports
    1112from twisted.web import server, static, twcgi, script, demo, distrib, wsgi
    12 from twisted.internet import interfaces, reactor
     13from twisted.internet import interfaces, reactor, endpoints
    1314from twisted.python import usage, reflect, threadpool
    1415from twisted.spread import pb
    15 from twisted.application import internet, service, strports
     16from twisted.application import internet, service
    1617
    1718
    1819class Options(usage.Options):
     
    2122    """
    2223    synopsis = "[web options]"
    2324
    24     optParameters = [["port", "p", None, "strports description of the port to "
    25                       "start the server on."],
     25    optParameters = [["port", "p", None,
     26                      "The bare port to start the server on. "
     27                      "DEPRECATED: use '--dsc tcp:port'"],
    2628                     ["logfile", "l", None, "Path to web CLF (Combined Log Format) log file."],
    27                      ["https", None, None, "Port to listen on for Secure HTTP."],
    28                      ["certificate", "c", "server.pem", "SSL certificate to use for HTTPS. "],
    29                      ["privkey", "k", "server.pem", "SSL certificate to use for HTTPS."],
     29                     ["https", None, None,
     30                      "Port to listen on for Secure HTTP. "
     31                      "DEPRECATED: use "
     32                      "'--dsc ssl:port:privateKey=pkey.pem:certKey=cert.pem'"],
     33                     ["certificate", "c", "server.pem",
     34                      "SSL certificate to use for HTTPS. "
     35                      "DEPRECATED: use "
     36                      "'--dsc ssl:port:privateKey=pkey.pem:certKey=cert.pem'"],
     37                     ["privkey", "k", "server.pem",
     38                      "SSL certificate to use for HTTPS. "
     39                      "DEPRECATED: use "
     40                      "'--dsc ssl:port:privateKey=pkey.pem:certKey=cert.pem'"],
    3041                     ]
    3142
    3243    optFlags = [["personal", "",
    3344                 "Instead of generating a webserver, generate a "
    34                  "ResourcePublisher which listens on  the port given by "
    35                  "--port, or ~/%s " % (distrib.UserDirectory.userSocketName,) +
    36                  "if --port is not specified."],
     45                 "ResourcePublisher which listens on  the port according to "
     46                 "the endpoint description given by --dsc, or ~/%s " % (
     47                     distrib.UserDirectory.userSocketName,) +
     48                 "if --dsc is not specified."],
    3749                ["notracebacks", "n", "Do not display tracebacks in broken web pages. " +
    38                  "Displaying tracebacks to users may be security risk!"],
     50                                      "Displaying tracebacks to users may be security risk!"],
    3951                ]
    4052
    4153    compData = usage.Completions(
    42                    optActions={"logfile" : usage.CompleteFiles("*.log"),
    43                                "certificate" : usage.CompleteFiles("*.pem"),
    44                                "privkey" : usage.CompleteFiles("*.pem")}
    45                    )
     54        optActions={"logfile" : usage.CompleteFiles("*.log"),
     55                    "certificate" : usage.CompleteFiles("*.pem"),
     56                    "privkey" : usage.CompleteFiles("*.pem")}
     57    )
    4658
    4759    longdesc = """\
    4860This starts a webserver.  If you specify no arguments, it will be a
     
    5264        usage.Options.__init__(self)
    5365        self['indexes'] = []
    5466        self['root'] = None
     67        self.endpoints = []
    5568
    5669
     70    def addEndpoint(self, description, privateKey=None, certificate=None):
     71        """
     72        Add an endpoint according to the description
     73        """
     74        self.endpoints.append(
     75            _toEndpoint(description, privateKey=privateKey, certificate=certificate))
     76
     77
     78    def opt_dsc(self, description):
     79        """
     80        Add a specified endpoint. You can add multiple endpoints by specifying
     81        multiple --dsc options. For backwards compatibility, a bare TCP port number
     82        can be specified, but this is deprecated.
     83        [TCP Example: tcp:port]
     84        [SSL Example: ssl:port:privateKey=mycert.pem]
     85        """
     86        self.addEndpoint(description)
     87
     88    opt_d = opt_dsc
     89
     90
    5791    def opt_index(self, indexName):
    5892        """
    5993        Add the name of a file used to check for directory indexes.
     
    169203        If no server port was supplied, select a default appropriate for the
    170204        other options supplied.
    171205        """
    172         if self['https']:
    173             try:
    174                 from twisted.internet.ssl import DefaultOpenSSLContextFactory
    175             except ImportError:
    176                 raise usage.UsageError("SSL support not installed")
    177         if self['port'] is None:
    178             if self['personal']:
     206        if self['personal']:
     207            if not self.endpoints:
    179208                path = os.path.expanduser(
    180209                    os.path.join('~', distrib.UserDirectory.userSocketName))
    181                 self['port'] = 'unix:' + path
    182             else:
    183                 self['port'] = 'tcp:8080'
     210                self.addEndpoint('unix:' + path)
     211        else:
     212            if self['https']:
     213                try:
     214                    from twisted.internet.ssl import DefaultOpenSSLContextFactory
     215                except ImportError:
     216                    raise usage.UsageError("SSL support not installed")
     217                self.addEndpoint(self['https'], privateKey=self['privkey'], certificate=self['certificate'])
    184218
     219            if self['port']:
     220                self.addEndpoint(self['port'])
    185221
     222        if not self.endpoints:
     223            self.addEndpoint("tcp:8080")
    186224
     225
     226
     227def _toEndpoint(description, privateKey=None, certificate=None):
     228    """
     229    Tries to guess whether a description is a bare TCP port or a endpoint.  If a
     230    bare port is specified and a certificate file is present, returns an
     231    SSL4ServerEndpoint and otherwise returns a TCP4ServerEndpoint.
     232    """
     233    try:
     234        port = int(description)
     235    except ValueError:
     236        return endpoints.serverFromString(reactor, description)
     237
     238    warnings.warn(
     239        "Specifying plain ports and/or a certificate is deprecated since "
     240        "Twisted 11.0; use endpoint descriptions instead.",
     241        category=DeprecationWarning, stacklevel=3)
     242
     243    if certificate:
     244        from twisted.internet.ssl import DefaultOpenSSLContextFactory
     245        ctx = DefaultOpenSSLContextFactory(privateKey, certificate)
     246        return endpoints.SSL4ServerEndpoint(reactor, port, ctx)
     247    return endpoints.TCP4ServerEndpoint(reactor, port)
     248
     249
     250
    187251def makePersonalServerFactory(site):
    188252    """
    189253    Create and return a factory which will respond to I{distrib} requests
     
    217281    site.displayTracebacks = not config["notracebacks"]
    218282
    219283    if config['personal']:
    220         personal = strports.service(
    221             config['port'], makePersonalServerFactory(site))
    222         personal.setServiceParent(s)
     284        siteFactory = makePersonalServerFactory(site)
    223285    else:
    224         if config['https']:
    225             from twisted.internet.ssl import DefaultOpenSSLContextFactory
    226             i = internet.SSLServer(int(config['https']), site,
    227                           DefaultOpenSSLContextFactory(config['privkey'],
    228                                                        config['certificate']))
    229             i.setServiceParent(s)
    230         strports.service(config['port'], site).setServiceParent(s)
     286        siteFactory = site
    231287
     288    for endpoint in config.endpoints:
     289        svc = internet.StreamServerEndpointService(endpoint, siteFactory)
     290        svc._raiseSynchronously = True
     291        svc.setServiceParent(s)
     292
    232293    return s
  • twisted/web/test/test_tap.py

     
    1010from twisted.python.usage import UsageError
    1111from twisted.python.filepath import FilePath
    1212from twisted.internet.interfaces import IReactorUNIX
    13 from twisted.internet import reactor
     13from twisted.internet import reactor, defer, endpoints
    1414from twisted.python.threadpool import ThreadPool
    1515from twisted.trial.unittest import TestCase
    16 from twisted.application import strports
    1716
    1817from twisted.web.server import Site
    1918from twisted.web.static import Data, File
     
    2827
    2928application = object()
    3029
     30
     31class SpyEndpoint(object):
     32    """
     33    SpyEndpoint remembers what factory it is told to listen with.
     34    """
     35    listeningWith = None
     36    def listen(self, factory):
     37        self.listeningWith = factory
     38        return defer.succeed(None)
     39
     40
    3141class ServiceTests(TestCase):
    3242    """
    3343    Tests for the service creation APIs in L{twisted.web.tap}.
     
    110120        self.assertIdentical(serverFactory.root.site, site)
    111121
    112122
     123    def test_dscMultipleEndpoints(self):
     124        """
     125        If one or more endpoints is included in the configuration passed to
     126        L{makeService}, a service for starting a server is constructed
     127        for each of them and attached to the returned service.
     128        """
     129        cleartext = SpyEndpoint()
     130        secure = SpyEndpoint()
     131        config = Options()
     132        config.endpoints = [cleartext, secure]
     133        service = makeService(config)
     134        service.privilegedStartService()
     135        service.startService()
     136        self.addCleanup(service.stopService)
     137        self.assertIsInstance(cleartext.listeningWith, Site)
     138        self.assertIsInstance(secure.listeningWith, Site)
     139
     140
     141    def test_dscBarePort(self):
     142        """
     143        For backwards compatibility, the I{--dsc} option supports the deprecated
     144        bare port option like '--dsc 8080'.
     145        """
     146        options = Options()
     147        options.parseOptions(['--dsc', '8080'])
     148        self.assertEqual(options.endpoints[0]._port, 8080)
     149
     150
     151
    113152    def test_personalServer(self):
    114153        """
    115154        The I{--personal} option to L{makeService} causes it to return a
    116         service which will listen on the server address given by the I{--port}
     155        service which will listen on the server address given by the I{--dsc}
    117156        option.
    118157        """
    119158        port = self.mktemp()
    120159        options = Options()
    121         options.parseOptions(['--port', 'unix:' + port, '--personal'])
     160        options.parseOptions(['--dsc', 'unix:' + port, '--personal'])
    122161        service = makeService(options)
    123162        service.startService()
    124163        self.addCleanup(service.stopService)
     
    132171
    133172    def test_defaultPersonalPath(self):
    134173        """
    135         If the I{--port} option not specified but the I{--personal} option is,
     174        If the I{--dsc} option not specified but the I{--personal} option is,
    136175        L{Options} defaults the port to C{UserDirectory.userSocketName} in the
    137176        user's home directory.
    138177        """
     
    140179        options.parseOptions(['--personal'])
    141180        path = os.path.expanduser(
    142181            os.path.join('~', UserDirectory.userSocketName))
    143         self.assertEqual(
    144             strports.parse(options['port'], None)[:2],
    145             ('UNIX', (path, None)))
     182        self.assertEqual(options.endpoints[0]._address, path)
    146183
     184
    147185    if not IReactorUNIX.providedBy(reactor):
    148186        test_defaultPersonalPath.skip = (
    149187            "The reactor does not support UNIX domain sockets")
     
    151189
    152190    def test_defaultPort(self):
    153191        """
    154         If the I{--port} option is not specified, L{Options} defaults the port
     192        If the I{--dsc} option is not specified, L{Options} defaults the port
    155193        to C{8080}.
    156194        """
    157195        options = Options()
    158196        options.parseOptions([])
    159         self.assertEqual(
    160             strports.parse(options['port'], None)[:2],
    161             ('TCP', (8080, None)))
     197        self.assertEqual(options.endpoints[0]._port, 8080)
    162198
    163199
    164200    def test_wsgi(self):