1# Copyright (c) Twisted Matrix Laboratories. 2# See LICENSE for details. 3 4""" 5Support for creating a service which runs a web server. 6""" 7 8import os 9 10# Twisted Imports 11from twisted.web import server, static, twcgi, script, demo, distrib, wsgi 12from twisted.internet import interfaces, reactor 13from twisted.python import usage, reflect, threadpool 14from twisted.spread import pb 15from twisted.application import internet, service, strports 16 17 18class 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", "Do not display tracebacks in broken web pages. " + 38 "Displaying tracebacks to users may be security risk!"], 39 ] 40 41 compData = usage.Completions( 42 optActions={"logfile" : usage.CompleteFiles("*.log"), 43 "certificate" : usage.CompleteFiles("*.pem"), 44 "privkey" : usage.CompleteFiles("*.pem")} 45 ) 46 47 longdesc = """\ 48This starts a webserver. If you specify no arguments, it will be a 49demo webserver that has the Test class from twisted.web.demo in it.""" 50 51 def __init__(self): 52 usage.Options.__init__(self) 53 self['indexes'] = [] 54 self['root'] = None 55 56 57 def opt_index(self, indexName): 58 """ 59 Add the name of a file used to check for directory indexes. 60 [default: index, index.html] 61 """ 62 self['indexes'].append(indexName) 63 64 opt_i = opt_index 65 66 67 def opt_user(self): 68 """ 69 Makes a server with ~/public_html and ~/.twistd-web-pb support for 70 users. 71 """ 72 self['root'] = distrib.UserDirectory() 73 74 opt_u = opt_user 75 76 77 def opt_path(self, path): 78 """ 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. 82 """ 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, 88 } 89 90 91 def opt_processor(self, proc): 92 """ 93 `ext=class' where `class' is added as a Processor for files ending 94 with `ext'. 95 """ 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) 100 101 102 def opt_class(self, className): 103 """ 104 Create a Resource subclass with a zero-argument constructor. 105 """ 106 classObj = reflect.namedClass(className) 107 self['root'] = classObj() 108 109 110 def opt_resource_script(self, name): 111 """ 112 An .rpy file to be used as the root resource of the webserver. 113 """ 114 self['root'] = script.ResourceScriptWrapper(name) 115 116 117 def opt_wsgi(self, name): 118 """ 119 The FQPN of a WSGI application object to serve as the root resource of 120 the webserver. 121 """ 122 try: 123 application = reflect.namedAny(name) 124 except (AttributeError, ValueError): 125 raise usage.UsageError("No such WSGI application: %r" % (name,)) 126 pool = threadpool.ThreadPool() 127 reactor.callWhenRunning(pool.start) 128 reactor.addSystemEventTrigger('after', 'shutdown', pool.stop) 129 self['root'] = wsgi.WSGIResource(reactor, pool, application) 130 131 132 def opt_mime_type(self, defaultType): 133 """ 134 Specify the default mime-type for static files. 135 """ 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 140 141 142 def opt_allow_ignore_ext(self): 143 """ 144 Specify whether or not a request for 'foo' should return 'foo.ext' 145 """ 146 if not isinstance(self['root'], static.File): 147 raise usage.UsageError("You can only use --allow_ignore_ext " 148 "after --path.") 149 self['root'].ignoreExt('*') 150 151 152 def opt_ignore_ext(self, ext): 153 """ 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 162 def postOptions(self): 163 """ 164 Set up conditional defaults and check for dependencies. 165 166 If SSL is not available but an HTTPS server was configured, raise a 167 L{UsageError} indicating that this is not possible. 168 169 If no server port was supplied, select a default appropriate for the 170 other options supplied. 171 """ 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']: 179 path = os.path.expanduser( 180 os.path.join('~', distrib.UserDirectory.userSocketName)) 181 self['port'] = 'unix:' + path 182 else: 183 self['port'] = 'tcp:8080' 184 185 186 187def makePersonalServerFactory(site): 188 """ 189 Create and return a factory which will respond to I{distrib} requests 190 against the given site. 191 192 @type site: L{twisted.web.server.Site} 193 @rtype: L{twisted.internet.protocol.Factory} 194 """ 195 return pb.PBServerFactory(distrib.ResourcePublisher(site)) 196 197 198 199def makeService(config): 200 s = service.MultiService() 201 if config['root']: 202 root = config['root'] 203 if config['indexes']: 204 config['root'].indexNames = config['indexes'] 205 else: 206 # This really ought to be web.Admin or something 207 root = demo.Test() 208 209 if isinstance(root, static.File): 210 root.registry.setComponent(interfaces.IServiceCollection, s) 211 212 if config['logfile']: 213 site = server.Site(root, logPath=config['logfile']) 214 else: 215 site = server.Site(root) 216 217 site.displayTracebacks = not config["notracebacks"] 218 219 if config['personal']: 220 personal = strports.service( 221 config['port'], makePersonalServerFactory(site)) 222 personal.setServiceParent(s) 223 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) 231 232 return s 233