1 # Copyright (c) Twisted Matrix Laboratories.
2 # See LICENSE for details.
5 Support for creating a service which runs a web server.
11 from twisted.web import server, static, twcgi, script, demo, distrib, wsgi
12 from twisted.internet import interfaces, reactor
13 from twisted.python import usage, reflect, threadpool
14 from twisted.spread import pb
15 from twisted.application import internet, service, strports
18 class Options(usage.Options):
20 Define the options accepted by the I{twistd web} plugin.
22 synopsis = "[web options]"
24 optParameters = [["port", "p", None, "strports description of the port to "
25 "start the server on."],
26 ["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."],
32 optFlags = [["personal", "",
33 "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."],
37 ["notracebacks", "n", "Do not display tracebacks in broken web pages. " +
38 "Displaying tracebacks to users may be security risk!"],
41 compData = usage.Completions(
42 optActions={"logfile" : usage.CompleteFiles("*.log"),
43 "certificate" : usage.CompleteFiles("*.pem"),
44 "privkey" : usage.CompleteFiles("*.pem")}
48 This starts a webserver. If you specify no arguments, it will be a
49 demo webserver that has the Test class from twisted.web.demo in it."""
52 usage.Options.__init__(self)
57 def opt_index(self, indexName):
59 Add the name of a file used to check for directory indexes.
60 [default: index, index.html]
62 self['indexes'].append(indexName)
69 Makes a server with ~/public_html and ~/.twistd-web-pb support for
72 self['root'] = distrib.UserDirectory()
77 def opt_path(self, path):
79 <path> is either a specific file or a directory to be set as the root
80 of the web server. Use this if you have a directory full of HTML, cgi,
81 epy, or rpy files or any other files that you want to be served up raw.
83 self['root'] = static.File(os.path.abspath(path))
84 self['root'].processors = {
85 '.cgi': twcgi.CGIScript,
86 '.epy': script.PythonScript,
87 '.rpy': script.ResourceScript,
91 def opt_processor(self, proc):
93 `ext=class' where `class' is added as a Processor for files ending
96 if not isinstance(self['root'], static.File):
97 raise usage.UsageError("You can only use --processor after --path.")
98 ext, klass = proc.split('=', 1)
99 self['root'].processors[ext] = reflect.namedClass(klass)
102 def opt_class(self, className):
104 Create a Resource subclass with a zero-argument constructor.
106 classObj = reflect.namedClass(className)
107 self['root'] = classObj()
110 def opt_resource_script(self, name):
112 An .rpy file to be used as the root resource of the webserver.
114 self['root'] = script.ResourceScriptWrapper(name)
117 def opt_wsgi(self, name):
119 The FQPN of a WSGI application object to serve as the root resource of
122 pool = threadpool.ThreadPool()
123 reactor.callWhenRunning(pool.start)
124 reactor.addSystemEventTrigger('after', 'shutdown', pool.stop)
126 application = reflect.namedAny(name)
127 except (AttributeError, ValueError):
128 raise usage.UsageError("No such WSGI application: %r" % (name,))
129 self['root'] = wsgi.WSGIResource(reactor, pool, application)
132 def opt_mime_type(self, defaultType):
134 Specify the default mime-type for static files.
136 if not isinstance(self['root'], static.File):
137 raise usage.UsageError("You can only use --mime_type after --path.")
138 self['root'].defaultType = defaultType
139 opt_m = opt_mime_type
142 def opt_allow_ignore_ext(self):
144 Specify whether or not a request for 'foo' should return 'foo.ext'
146 if not isinstance(self['root'], static.File):
147 raise usage.UsageError("You can only use --allow_ignore_ext "
149 self['root'].ignoreExt('*')
152 def opt_ignore_ext(self, ext):
154 Specify an extension to ignore. These will be processed in order.
156 if not isinstance(self['root'], static.File):
157 raise usage.UsageError("You can only use --ignore_ext "
159 self['root'].ignoreExt(ext)
162 def postOptions(self):
164 Set up conditional defaults and check for dependencies.
166 If SSL is not available but an HTTPS server was configured, raise a
167 L{UsageError} indicating that this is not possible.
169 If no server port was supplied, select a default appropriate for the
170 other options supplied.
174 from twisted.internet.ssl import DefaultOpenSSLContextFactory
176 raise usage.UsageError("SSL support not installed")
177 if self['port'] is None:
179 path = os.path.expanduser(
180 os.path.join('~', distrib.UserDirectory.userSocketName))
181 self['port'] = 'unix:' + path
183 self['port'] = 'tcp:8080'
187 def makePersonalServerFactory(site):
189 Create and return a factory which will respond to I{distrib} requests
190 against the given site.
192 @type site: L{twisted.web.server.Site}
193 @rtype: L{twisted.internet.protocol.Factory}
195 return pb.PBServerFactory(distrib.ResourcePublisher(site))
199 def makeService(config):
200 s = service.MultiService()
202 root = config['root']
203 if config['indexes']:
204 config['root'].indexNames = config['indexes']
206 # This really ought to be web.Admin or something
209 if isinstance(root, static.File):
210 root.registry.setComponent(interfaces.IServiceCollection, s)
212 if config['logfile']:
213 site = server.Site(root, logPath=config['logfile'])
215 site = server.Site(root)
217 site.displayTracebacks = not config["notracebacks"]
219 if config['personal']:
220 personal = strports.service(
221 config['port'], makePersonalServerFactory(site))
222 personal.setServiceParent(s)
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)