| 1 |
|
|---|
| 2 |
|
|---|
| 3 |
|
|---|
| 4 |
""" |
|---|
| 5 |
Support for creating a service which runs a web server. |
|---|
| 6 |
""" |
|---|
| 7 |
|
|---|
| 8 |
import os |
|---|
| 9 |
|
|---|
| 10 |
|
|---|
| 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 |
|---|
| 16 |
|
|---|
| 17 |
|
|---|
| 18 |
class Options(usage.Options): |
|---|
| 19 |
""" |
|---|
| 20 |
Define the options accepted by the I{twistd web} plugin. |
|---|
| 21 |
""" |
|---|
| 22 |
synopsis = "[web options]" |
|---|
| 23 |
|
|---|
| 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."], |
|---|
| 30 |
] |
|---|
| 31 |
|
|---|
| 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", "Display tracebacks in broken web pages. " + |
|---|
| 38 |
"Displaying tracebacks to users may be security risk!"], |
|---|
| 39 |
] |
|---|
| 40 |
|
|---|
| 41 |
zsh_actions = {"logfile" : "_files -g '*.log'", "certificate" : "_files -g '*.pem'", |
|---|
| 42 |
"privkey" : "_files -g '*.pem'"} |
|---|
| 43 |
|
|---|
| 44 |
|
|---|
| 45 |
longdesc = """\ |
|---|
| 46 |
This starts a webserver. If you specify no arguments, it will be a |
|---|
| 47 |
demo webserver that has the Test class from twisted.web.demo in it.""" |
|---|
| 48 |
|
|---|
| 49 |
def __init__(self): |
|---|
| 50 |
usage.Options.__init__(self) |
|---|
| 51 |
self['indexes'] = [] |
|---|
| 52 |
self['root'] = None |
|---|
| 53 |
|
|---|
| 54 |
def opt_index(self, indexName): |
|---|
| 55 |
"""Add the name of a file used to check for directory indexes. |
|---|
| 56 |
[default: index, index.html] |
|---|
| 57 |
""" |
|---|
| 58 |
self['indexes'].append(indexName) |
|---|
| 59 |
|
|---|
| 60 |
opt_i = opt_index |
|---|
| 61 |
|
|---|
| 62 |
def opt_user(self): |
|---|
| 63 |
"""Makes a server with ~/public_html and ~/.twistd-web-pb support for |
|---|
| 64 |
users. |
|---|
| 65 |
""" |
|---|
| 66 |
self['root'] = distrib.UserDirectory() |
|---|
| 67 |
|
|---|
| 68 |
opt_u = opt_user |
|---|
| 69 |
|
|---|
| 70 |
def opt_path(self, path): |
|---|
| 71 |
""" |
|---|
| 72 |
<path> is either a specific file or a directory to be set as the root |
|---|
| 73 |
of the web server. Use this if you have a directory full of HTML, cgi, |
|---|
| 74 |
php3, epy, or rpy files or any other files that you want to be served |
|---|
| 75 |
up raw. |
|---|
| 76 |
""" |
|---|
| 77 |
def trp(*args, **kwargs): |
|---|
| 78 |
|
|---|
| 79 |
|
|---|
| 80 |
|
|---|
| 81 |
from twisted.web import trp |
|---|
| 82 |
return trp.ResourceUnpickler(*args, **kwargs) |
|---|
| 83 |
|
|---|
| 84 |
self['root'] = static.File(os.path.abspath(path)) |
|---|
| 85 |
self['root'].processors = { |
|---|
| 86 |
'.cgi': twcgi.CGIScript, |
|---|
| 87 |
'.php3': twcgi.PHP3Script, |
|---|
| 88 |
'.php': twcgi.PHPScript, |
|---|
| 89 |
'.epy': script.PythonScript, |
|---|
| 90 |
'.rpy': script.ResourceScript, |
|---|
| 91 |
'.trp': trp, |
|---|
| 92 |
} |
|---|
| 93 |
|
|---|
| 94 |
def opt_processor(self, proc): |
|---|
| 95 |
"""`ext=class' where `class' is added as a Processor for files ending |
|---|
| 96 |
with `ext'. |
|---|
| 97 |
""" |
|---|
| 98 |
if not isinstance(self['root'], static.File): |
|---|
| 99 |
raise usage.UsageError("You can only use --processor after --path.") |
|---|
| 100 |
ext, klass = proc.split('=', 1) |
|---|
| 101 |
self['root'].processors[ext] = reflect.namedClass(klass) |
|---|
| 102 |
|
|---|
| 103 |
def opt_static(self, path): |
|---|
| 104 |
"""Same as --path, this is deprecated and will be removed in a |
|---|
| 105 |
future release.""" |
|---|
| 106 |
print ("WARNING: --static is deprecated and will be removed in" |
|---|
| 107 |
"a future release. Please use --path.") |
|---|
| 108 |
self.opt_path(path) |
|---|
| 109 |
opt_s = opt_static |
|---|
| 110 |
|
|---|
| 111 |
def opt_class(self, className): |
|---|
| 112 |
"""Create a Resource subclass with a zero-argument constructor. |
|---|
| 113 |
""" |
|---|
| 114 |
classObj = reflect.namedClass(className) |
|---|
| 115 |
self['root'] = classObj() |
|---|
| 116 |
|
|---|
| 117 |
|
|---|
| 118 |
def opt_resource_script(self, name): |
|---|
| 119 |
"""An .rpy file to be used as the root resource of the webserver.""" |
|---|
| 120 |
self['root'] = script.ResourceScriptWrapper(name) |
|---|
| 121 |
|
|---|
| 122 |
|
|---|
| 123 |
def opt_wsgi(self, name): |
|---|
| 124 |
""" |
|---|
| 125 |
The FQPN of a WSGI application object to serve as the root resource of |
|---|
| 126 |
the webserver. |
|---|
| 127 |
""" |
|---|
| 128 |
pool = threadpool.ThreadPool() |
|---|
| 129 |
reactor.callWhenRunning(pool.start) |
|---|
| 130 |
reactor.addSystemEventTrigger('after', 'shutdown', pool.stop) |
|---|
| 131 |
try: |
|---|
| 132 |
application = reflect.namedAny(name) |
|---|
| 133 |
except (AttributeError, ValueError): |
|---|
| 134 |
raise usage.UsageError("No such WSGI application: %r" % (name,)) |
|---|
| 135 |
self['root'] = wsgi.WSGIResource(reactor, pool, application) |
|---|
| 136 |
|
|---|
| 137 |
|
|---|
| 138 |
def opt_mime_type(self, defaultType): |
|---|
| 139 |
"""Specify the default mime-type for static files.""" |
|---|
| 140 |
if not isinstance(self['root'], static.File): |
|---|
| 141 |
raise usage.UsageError("You can only use --mime_type after --path.") |
|---|
| 142 |
self['root'].defaultType = defaultType |
|---|
| 143 |
opt_m = opt_mime_type |
|---|
| 144 |
|
|---|
| 145 |
|
|---|
| 146 |
def opt_allow_ignore_ext(self): |
|---|
| 147 |
"""Specify whether or not a request for 'foo' should return 'foo.ext'""" |
|---|
| 148 |
if not isinstance(self['root'], static.File): |
|---|
| 149 |
raise usage.UsageError("You can only use --allow_ignore_ext " |
|---|
| 150 |
"after --path.") |
|---|
| 151 |
self['root'].ignoreExt('*') |
|---|
| 152 |
|
|---|
| 153 |
def opt_ignore_ext(self, ext): |
|---|
| 154 |
"""Specify an extension to ignore. These will be processed in order. |
|---|
| 155 |
""" |
|---|
| 156 |
if not isinstance(self['root'], static.File): |
|---|
| 157 |
raise usage.UsageError("You can only use --ignore_ext " |
|---|
| 158 |
"after --path.") |
|---|
| 159 |
self['root'].ignoreExt(ext) |
|---|
| 160 |
|
|---|
| 161 |
def postOptions(self): |
|---|
| 162 |
""" |
|---|
| 163 |
Set up conditional defaults and check for dependencies. |
|---|
| 164 |
|
|---|
| 165 |
If SSL is not available but an HTTPS server was configured, raise a |
|---|
| 166 |
L{UsageError} indicating that this is not possible. |
|---|
| 167 |
|
|---|
| 168 |
If no server port was supplied, select a default appropriate for the |
|---|
| 169 |
other options supplied. |
|---|
| 170 |
""" |
|---|
| 171 |
if self['https']: |
|---|
| 172 |
try: |
|---|
| 173 |
from twisted.internet.ssl import DefaultOpenSSLContextFactory |
|---|
| 174 |
except ImportError: |
|---|
| 175 |
raise usage.UsageError("SSL support not installed") |
|---|
| 176 |
if self['port'] is None: |
|---|
| 177 |
if self['personal']: |
|---|
| 178 |
path = os.path.expanduser( |
|---|
| 179 |
os.path.join('~', distrib.UserDirectory.userSocketName)) |
|---|
| 180 |
self['port'] = 'unix:' + path |
|---|
| 181 |
else: |
|---|
| 182 |
self['port'] = 'tcp:8080' |
|---|
| 183 |
|
|---|
| 184 |
|
|---|
| 185 |
|
|---|
| 186 |
def makePersonalServerFactory(site): |
|---|
| 187 |
""" |
|---|
| 188 |
Create and return a factory which will respond to I{distrib} requests |
|---|
| 189 |
against the given site. |
|---|
| 190 |
|
|---|
| 191 |
@type site: L{twisted.web.server.Site} |
|---|
| 192 |
@rtype: L{twisted.internet.protocol.Factory} |
|---|
| 193 |
""" |
|---|
| 194 |
return pb.PBServerFactory(distrib.ResourcePublisher(site)) |
|---|
| 195 |
|
|---|
| 196 |
|
|---|
| 197 |
|
|---|
| 198 |
def makeService(config): |
|---|
| 199 |
s = service.MultiService() |
|---|
| 200 |
if config['root']: |
|---|
| 201 |
root = config['root'] |
|---|
| 202 |
if config['indexes']: |
|---|
| 203 |
config['root'].indexNames = config['indexes'] |
|---|
| 204 |
else: |
|---|
| 205 |
|
|---|
| 206 |
root = demo.Test() |
|---|
| 207 |
|
|---|
| 208 |
if isinstance(root, static.File): |
|---|
| 209 |
root.registry.setComponent(interfaces.IServiceCollection, s) |
|---|
| 210 |
|
|---|
| 211 |
if config['logfile']: |
|---|
| 212 |
site = server.Site(root, logPath=config['logfile']) |
|---|
| 213 |
else: |
|---|
| 214 |
site = server.Site(root) |
|---|
| 215 |
|
|---|
| 216 |
site.displayTracebacks = not config["notracebacks"] |
|---|
| 217 |
|
|---|
| 218 |
if config['personal']: |
|---|
| 219 |
personal = strports.service( |
|---|
| 220 |
config['port'], makePersonalServerFactory(site)) |
|---|
| 221 |
personal.setServiceParent(s) |
|---|
| 222 |
else: |
|---|
| 223 |
if config['https']: |
|---|
| 224 |
from twisted.internet.ssl import DefaultOpenSSLContextFactory |
|---|
| 225 |
i = internet.SSLServer(int(config['https']), site, |
|---|
| 226 |
DefaultOpenSSLContextFactory(config['privkey'], |
|---|
| 227 |
config['certificate'])) |
|---|
| 228 |
i.setServiceParent(s) |
|---|
| 229 |
strports.service(config['port'], site).setServiceParent(s) |
|---|
| 230 |
|
|---|
| 231 |
return s |
|---|