123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322 |
- # -*- test-case-name: twisted.web.test.test_tap -*-
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
- """
- Support for creating a service which runs a web server.
- """
- import os
- import warnings
- import incremental
- from twisted.application import service, strports
- from twisted.internet import interfaces, reactor
- from twisted.python import deprecate, reflect, threadpool, usage
- from twisted.spread import pb
- from twisted.web import demo, distrib, resource, script, server, static, twcgi, wsgi
- class Options(usage.Options):
- """
- Define the options accepted by the I{twistd web} plugin.
- """
- synopsis = "[web options]"
- optParameters = [
- ["logfile", "l", None, "Path to web CLF (Combined Log Format) log file."],
- [
- "certificate",
- "c",
- "server.pem",
- "(DEPRECATED: use --listen) " "SSL certificate to use for HTTPS. ",
- ],
- [
- "privkey",
- "k",
- "server.pem",
- "(DEPRECATED: use --listen) " "SSL certificate to use for HTTPS.",
- ],
- ]
- optFlags = [
- [
- "notracebacks",
- "n",
- (
- "(DEPRECATED: Tracebacks are disabled by default. "
- "See --enable-tracebacks to turn them on."
- ),
- ],
- [
- "display-tracebacks",
- "",
- (
- "Show uncaught exceptions during rendering tracebacks to "
- "the client. WARNING: This may be a security risk and "
- "expose private data!"
- ),
- ],
- ]
- optFlags.append(
- [
- "personal",
- "",
- "Instead of generating a webserver, generate a "
- "ResourcePublisher which listens on the port given by "
- "--listen, or ~/%s " % (distrib.UserDirectory.userSocketName,)
- + "if --listen is not specified.",
- ]
- )
- compData = usage.Completions(
- optActions={
- "logfile": usage.CompleteFiles("*.log"),
- "certificate": usage.CompleteFiles("*.pem"),
- "privkey": usage.CompleteFiles("*.pem"),
- }
- )
- longdesc = """\
- This starts a webserver. If you specify no arguments, it will be a
- demo webserver that has the Test class from twisted.web.demo in it."""
- def __init__(self):
- usage.Options.__init__(self)
- self["indexes"] = []
- self["root"] = None
- self["extraHeaders"] = []
- self["ports"] = []
- self["port"] = self["https"] = None
- def opt_port(self, port):
- """
- (DEPRECATED: use --listen)
- Strports description of port to start the server on
- """
- msg = deprecate.getDeprecationWarningString(
- self.opt_port, incremental.Version("Twisted", 18, 4, 0)
- )
- warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
- self["port"] = port
- opt_p = opt_port
- def opt_https(self, port):
- """
- (DEPRECATED: use --listen)
- Port to listen on for Secure HTTP.
- """
- msg = deprecate.getDeprecationWarningString(
- self.opt_https, incremental.Version("Twisted", 18, 4, 0)
- )
- warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
- self["https"] = port
- def opt_listen(self, port):
- """
- Add an strports description of port to start the server on.
- [default: tcp:8080]
- """
- self["ports"].append(port)
- def opt_index(self, indexName):
- """
- Add the name of a file used to check for directory indexes.
- [default: index, index.html]
- """
- self["indexes"].append(indexName)
- opt_i = opt_index
- def opt_user(self):
- """
- Makes a server with ~/public_html and ~/.twistd-web-pb support for
- users.
- """
- self["root"] = distrib.UserDirectory()
- opt_u = opt_user
- def opt_path(self, path):
- """
- <path> is either a specific file or a directory to be set as the root
- of the web server. Use this if you have a directory full of HTML, cgi,
- epy, or rpy files or any other files that you want to be served up raw.
- """
- self["root"] = static.File(os.path.abspath(path))
- self["root"].processors = {
- ".epy": script.PythonScript,
- ".rpy": script.ResourceScript,
- }
- self["root"].processors[".cgi"] = twcgi.CGIScript
- def opt_processor(self, proc):
- """
- `ext=class' where `class' is added as a Processor for files ending
- with `ext'.
- """
- if not isinstance(self["root"], static.File):
- raise usage.UsageError("You can only use --processor after --path.")
- ext, klass = proc.split("=", 1)
- self["root"].processors[ext] = reflect.namedClass(klass)
- def opt_class(self, className):
- """
- Create a Resource subclass with a zero-argument constructor.
- """
- classObj = reflect.namedClass(className)
- self["root"] = classObj()
- def opt_resource_script(self, name):
- """
- An .rpy file to be used as the root resource of the webserver.
- """
- self["root"] = script.ResourceScriptWrapper(name)
- def opt_wsgi(self, name):
- """
- The FQPN of a WSGI application object to serve as the root resource of
- the webserver.
- """
- try:
- application = reflect.namedAny(name)
- except (AttributeError, ValueError):
- raise usage.UsageError(f"No such WSGI application: {name!r}")
- pool = threadpool.ThreadPool()
- reactor.callWhenRunning(pool.start)
- reactor.addSystemEventTrigger("after", "shutdown", pool.stop)
- self["root"] = wsgi.WSGIResource(reactor, pool, application)
- def opt_mime_type(self, defaultType):
- """
- Specify the default mime-type for static files.
- """
- if not isinstance(self["root"], static.File):
- raise usage.UsageError("You can only use --mime_type after --path.")
- self["root"].defaultType = defaultType
- opt_m = opt_mime_type
- def opt_allow_ignore_ext(self):
- """
- Specify whether or not a request for 'foo' should return 'foo.ext'
- """
- if not isinstance(self["root"], static.File):
- raise usage.UsageError(
- "You can only use --allow_ignore_ext " "after --path."
- )
- self["root"].ignoreExt("*")
- def opt_ignore_ext(self, ext):
- """
- Specify an extension to ignore. These will be processed in order.
- """
- if not isinstance(self["root"], static.File):
- raise usage.UsageError("You can only use --ignore_ext " "after --path.")
- self["root"].ignoreExt(ext)
- def opt_add_header(self, header):
- """
- Specify an additional header to be included in all responses. Specified
- as "HeaderName: HeaderValue".
- """
- name, value = header.split(":", 1)
- self["extraHeaders"].append((name.strip(), value.strip()))
- def postOptions(self):
- """
- Set up conditional defaults and check for dependencies.
- If SSL is not available but an HTTPS server was configured, raise a
- L{UsageError} indicating that this is not possible.
- If no server port was supplied, select a default appropriate for the
- other options supplied.
- """
- if self["port"] is not None:
- self["ports"].append(self["port"])
- if self["https"] is not None:
- try:
- reflect.namedModule("OpenSSL.SSL")
- except ImportError:
- raise usage.UsageError("SSL support not installed")
- sslStrport = "ssl:port={}:privateKey={}:certKey={}".format(
- self["https"],
- self["privkey"],
- self["certificate"],
- )
- self["ports"].append(sslStrport)
- if len(self["ports"]) == 0:
- if self["personal"]:
- path = os.path.expanduser(
- os.path.join("~", distrib.UserDirectory.userSocketName)
- )
- self["ports"].append("unix:" + path)
- else:
- self["ports"].append("tcp:8080")
- def makePersonalServerFactory(site):
- """
- Create and return a factory which will respond to I{distrib} requests
- against the given site.
- @type site: L{twisted.web.server.Site}
- @rtype: L{twisted.internet.protocol.Factory}
- """
- return pb.PBServerFactory(distrib.ResourcePublisher(site))
- class _AddHeadersResource(resource.Resource):
- def __init__(self, originalResource, headers):
- self._originalResource = originalResource
- self._headers = headers
- def getChildWithDefault(self, name, request):
- for k, v in self._headers:
- request.responseHeaders.addRawHeader(k, v)
- return self._originalResource.getChildWithDefault(name, request)
- def makeService(config):
- s = service.MultiService()
- if config["root"]:
- root = config["root"]
- if config["indexes"]:
- config["root"].indexNames = config["indexes"]
- else:
- # This really ought to be web.Admin or something
- root = demo.Test()
- if isinstance(root, static.File):
- root.registry.setComponent(interfaces.IServiceCollection, s)
- if config["extraHeaders"]:
- root = _AddHeadersResource(root, config["extraHeaders"])
- if config["logfile"]:
- site = server.Site(root, logPath=config["logfile"])
- else:
- site = server.Site(root)
- if config["display-tracebacks"]:
- site.displayTracebacks = True
- # Deprecate --notracebacks/-n
- if config["notracebacks"]:
- msg = deprecate._getDeprecationWarningString(
- "--notracebacks", incremental.Version("Twisted", 19, 7, 0)
- )
- warnings.warn(msg, category=DeprecationWarning, stacklevel=2)
- if config["personal"]:
- site = makePersonalServerFactory(site)
- for port in config["ports"]:
- svc = strports.service(port, site)
- svc.setServiceParent(s)
- return s
|