[42bd667] | 1 | #!/usr/bin/env python |
---|
| 2 | # -*- coding: utf-8 -*- |
---|
| 3 | |
---|
| 4 | """ |
---|
| 5 | This file is part of the web2py Web Framework |
---|
| 6 | Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu> |
---|
| 7 | License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html) |
---|
| 8 | |
---|
| 9 | This file is based, although a rewrite, on MIT-licensed code from the Bottle web framework. |
---|
| 10 | """ |
---|
| 11 | |
---|
| 12 | import os |
---|
| 13 | import sys |
---|
| 14 | import optparse |
---|
| 15 | import urllib |
---|
| 16 | |
---|
| 17 | path = os.path.dirname(os.path.abspath(__file__)) |
---|
| 18 | os.chdir(path) |
---|
| 19 | sys.path = [path] + [p for p in sys.path if not p == path] |
---|
| 20 | |
---|
| 21 | |
---|
| 22 | class Servers: |
---|
| 23 | @staticmethod |
---|
| 24 | def cgi(app, address=None, **options): |
---|
| 25 | from wsgiref.handlers import CGIHandler |
---|
| 26 | CGIHandler().run(app) # Just ignore host and port here |
---|
| 27 | |
---|
| 28 | @staticmethod |
---|
| 29 | def flup(app, address, **options): |
---|
| 30 | import flup.server.fcgi |
---|
| 31 | flup.server.fcgi.WSGIServer(app, bindAddress=address).run() |
---|
| 32 | |
---|
| 33 | @staticmethod |
---|
| 34 | def wsgiref(app, address, **options): # pragma: no cover |
---|
| 35 | from wsgiref.simple_server import make_server, WSGIRequestHandler |
---|
| 36 | options = {} |
---|
| 37 | class QuietHandler(WSGIRequestHandler): |
---|
| 38 | def log_request(*args, **kw): |
---|
| 39 | pass |
---|
| 40 | options['handler_class'] = QuietHandler |
---|
| 41 | srv = make_server(address[0], address[1], app, **options) |
---|
| 42 | srv.serve_forever() |
---|
| 43 | |
---|
| 44 | @staticmethod |
---|
| 45 | def cherrypy(app, address, **options): |
---|
| 46 | from cheroot.wsgi import Server as WSGIServer |
---|
| 47 | server = WSGIServer(address, app) |
---|
| 48 | server.start() |
---|
| 49 | |
---|
| 50 | @staticmethod |
---|
| 51 | def rocket(app, address, **options): |
---|
| 52 | from gluon.rocket import CherryPyWSGIServer |
---|
| 53 | server = CherryPyWSGIServer(address, app) |
---|
| 54 | server.start() |
---|
| 55 | |
---|
| 56 | @staticmethod |
---|
| 57 | def rocket_with_repoze_profiler(app, address, **options): |
---|
| 58 | from gluon.rocket import CherryPyWSGIServer |
---|
| 59 | from repoze.profile.profiler import AccumulatingProfileMiddleware |
---|
| 60 | from gluon.settings import global_settings |
---|
| 61 | global_settings.web2py_crontype = 'none' |
---|
| 62 | wrapped = AccumulatingProfileMiddleware( |
---|
| 63 | app, |
---|
| 64 | log_filename='wsgi.prof', |
---|
| 65 | discard_first_request=True, |
---|
| 66 | flush_at_shutdown=True, |
---|
| 67 | path='/__profile__' |
---|
| 68 | ) |
---|
| 69 | server = CherryPyWSGIServer(address, wrapped) |
---|
| 70 | server.start() |
---|
| 71 | |
---|
| 72 | @staticmethod |
---|
| 73 | def paste(app, address, **options): |
---|
| 74 | options = {} |
---|
| 75 | from paste import httpserver |
---|
| 76 | from paste.translogger import TransLogger |
---|
| 77 | httpserver.serve(app, host=address[0], port=address[1], **options) |
---|
| 78 | |
---|
| 79 | @staticmethod |
---|
| 80 | def fapws(app, address, **options): |
---|
| 81 | import fapws._evwsgi as evwsgi |
---|
| 82 | from fapws import base |
---|
| 83 | evwsgi.start(address[0], str(address[1])) |
---|
| 84 | evwsgi.set_base_module(base) |
---|
| 85 | |
---|
| 86 | def app(environ, start_response): |
---|
| 87 | environ['wsgi.multiprocess'] = False |
---|
| 88 | return app(environ, start_response) |
---|
| 89 | evwsgi.wsgi_cb(('', app)) |
---|
| 90 | evwsgi.run() |
---|
| 91 | |
---|
| 92 | @staticmethod |
---|
| 93 | def gevent(app, address, **options): |
---|
| 94 | options = options['options'] |
---|
| 95 | workers = options.workers |
---|
| 96 | from gevent import pywsgi |
---|
| 97 | from gevent.pool import Pool |
---|
| 98 | pywsgi.WSGIServer(address, app, spawn=workers and Pool( |
---|
| 99 | int(options.workers)) or 'default', log=None).serve_forever() |
---|
| 100 | |
---|
| 101 | @staticmethod |
---|
| 102 | def bjoern(app, address, **options): |
---|
| 103 | import bjoern |
---|
| 104 | bjoern.run(app, *address) |
---|
| 105 | |
---|
| 106 | @staticmethod |
---|
| 107 | def tornado(app, address, **options): |
---|
| 108 | import tornado.wsgi |
---|
| 109 | import tornado.httpserver |
---|
| 110 | import tornado.ioloop |
---|
| 111 | container = tornado.wsgi.WSGIContainer(app) |
---|
| 112 | server = tornado.httpserver.HTTPServer(container) |
---|
| 113 | server.listen(address=address[0], port=address[1]) |
---|
| 114 | tornado.ioloop.IOLoop.instance().start() |
---|
| 115 | |
---|
| 116 | @staticmethod |
---|
| 117 | def twisted(app, address, **options): |
---|
| 118 | from twisted.web import server, wsgi |
---|
| 119 | from twisted.python.threadpool import ThreadPool |
---|
| 120 | from twisted.internet import reactor |
---|
| 121 | thread_pool = ThreadPool() |
---|
| 122 | thread_pool.start() |
---|
| 123 | reactor.addSystemEventTrigger('after', 'shutdown', thread_pool.stop) |
---|
| 124 | factory = server.Site(wsgi.WSGIResource(reactor, thread_pool, app)) |
---|
| 125 | reactor.listenTCP(address[1], factory, interface=address[0]) |
---|
| 126 | reactor.run() |
---|
| 127 | |
---|
| 128 | @staticmethod |
---|
| 129 | def diesel(app, address, **options): |
---|
| 130 | from diesel.protocols.wsgi import WSGIApplication |
---|
| 131 | app = WSGIApplication(app, port=address[1]) |
---|
| 132 | app.run() |
---|
| 133 | |
---|
| 134 | @staticmethod |
---|
| 135 | def gunicorn(app, address, **options): |
---|
| 136 | options = {} |
---|
| 137 | from gunicorn.app.base import Application |
---|
| 138 | config = {'bind': "%s:%d" % address} |
---|
| 139 | config.update(options) |
---|
| 140 | sys.argv = ['anyserver.py'] |
---|
| 141 | |
---|
| 142 | class GunicornApplication(Application): |
---|
| 143 | def init(self, parser, opts, args): |
---|
| 144 | return config |
---|
| 145 | |
---|
| 146 | def load(self): |
---|
| 147 | return app |
---|
| 148 | g = GunicornApplication() |
---|
| 149 | g.run() |
---|
| 150 | |
---|
| 151 | @staticmethod |
---|
| 152 | def eventlet(app, address, **options): |
---|
| 153 | from eventlet import wsgi, listen |
---|
| 154 | wsgi.server(listen(address), app) |
---|
| 155 | |
---|
| 156 | @staticmethod |
---|
| 157 | def mongrel2(app, address, **options): |
---|
| 158 | import uuid |
---|
| 159 | sys.path.append(os.path.abspath(os.path.dirname(__file__))) |
---|
| 160 | from mongrel2 import handler |
---|
| 161 | conn = handler.Connection(str(uuid.uuid4()), |
---|
| 162 | "tcp://127.0.0.1:9997", |
---|
| 163 | "tcp://127.0.0.1:9996") |
---|
| 164 | mongrel2_handler(app, conn, debug=False) |
---|
| 165 | |
---|
| 166 | @staticmethod |
---|
| 167 | def motor(app, address, **options): |
---|
| 168 | #https://github.com/rpedroso/motor |
---|
| 169 | import motor |
---|
| 170 | app = motor.WSGIContainer(app) |
---|
| 171 | http_server = motor.HTTPServer(app) |
---|
| 172 | http_server.listen(address=address[0], port=address[1]) |
---|
| 173 | #http_server.start(2) |
---|
| 174 | motor.IOLoop.instance().start() |
---|
| 175 | |
---|
| 176 | @staticmethod |
---|
| 177 | def pulsar(app, address, **options): |
---|
| 178 | from pulsar.apps import wsgi |
---|
| 179 | sys.argv = ['anyserver.py'] |
---|
| 180 | s = wsgi.WSGIServer(callable=app, bind="%s:%d" % address) |
---|
| 181 | s.start() |
---|
| 182 | |
---|
| 183 | @staticmethod |
---|
| 184 | def waitress(app, address, **options): |
---|
| 185 | from waitress import serve |
---|
| 186 | serve(app, host=address[0], port=address[1], _quiet=True) |
---|
| 187 | |
---|
| 188 | |
---|
| 189 | def mongrel2_handler(application, conn, debug=False): |
---|
| 190 | """ |
---|
| 191 | Based on : |
---|
| 192 | https://github.com/berry/Mongrel2-WSGI-Handler/blob/master/wsgi-handler.py |
---|
| 193 | |
---|
| 194 | WSGI handler based on the Python wsgiref SimpleHandler. |
---|
| 195 | A WSGI application should return a iterable op StringTypes. |
---|
| 196 | Any encoding must be handled by the WSGI application itself. |
---|
| 197 | """ |
---|
| 198 | from wsgiref.handlers import SimpleHandler |
---|
| 199 | try: |
---|
| 200 | import cStringIO as StringIO |
---|
| 201 | except: |
---|
| 202 | import StringIO |
---|
| 203 | |
---|
| 204 | # TODO - this wsgi handler executes the application and renders a page |
---|
| 205 | # in memory completely before returning it as a response to the client. |
---|
| 206 | # Thus, it does not "stream" the result back to the client. It should be |
---|
| 207 | # possible though. The SimpleHandler accepts file-like stream objects. So, |
---|
| 208 | # it should be just a matter of connecting 0MQ requests/response streams to |
---|
| 209 | # the SimpleHandler requests and response streams. However, the Python API |
---|
| 210 | # for Mongrel2 doesn't seem to support file-like stream objects for requests |
---|
| 211 | # and responses. Unless I have missed something. |
---|
| 212 | |
---|
| 213 | while True: |
---|
| 214 | if debug: |
---|
| 215 | print("WAITING FOR REQUEST") |
---|
| 216 | |
---|
| 217 | # receive a request |
---|
| 218 | req = conn.recv() |
---|
| 219 | if debug: |
---|
| 220 | print("REQUEST BODY: %r\n" % req.body) |
---|
| 221 | |
---|
| 222 | if req.is_disconnect(): |
---|
| 223 | if debug: |
---|
| 224 | print("DISCONNECT") |
---|
| 225 | continue # effectively ignore the disconnect from the client |
---|
| 226 | |
---|
| 227 | # Set a couple of environment attributes a.k.a. header attributes |
---|
| 228 | # that are a must according to PEP 333 |
---|
| 229 | environ = req.headers |
---|
| 230 | environ['SERVER_PROTOCOL'] = 'HTTP/1.1' # SimpleHandler expects a server_protocol, lets assume it is HTTP 1.1 |
---|
| 231 | environ['REQUEST_METHOD'] = environ['METHOD'] |
---|
| 232 | if ':' in environ['Host']: |
---|
| 233 | environ['SERVER_NAME'] = environ['Host'].split(':')[0] |
---|
| 234 | environ['SERVER_PORT'] = environ['Host'].split(':')[1] |
---|
| 235 | else: |
---|
| 236 | environ['SERVER_NAME'] = environ['Host'] |
---|
| 237 | environ['SERVER_PORT'] = '' |
---|
| 238 | environ['SCRIPT_NAME'] = '' # empty for now |
---|
| 239 | environ['PATH_INFO'] = urllib.unquote(environ['PATH']) |
---|
| 240 | if '?' in environ['URI']: |
---|
| 241 | environ['QUERY_STRING'] = environ['URI'].split('?')[1] |
---|
| 242 | else: |
---|
| 243 | environ['QUERY_STRING'] = '' |
---|
| 244 | if 'Content-Length' in environ: |
---|
| 245 | environ['CONTENT_LENGTH'] = environ[ |
---|
| 246 | 'Content-Length'] # necessary for POST to work with Django |
---|
| 247 | environ['wsgi.input'] = req.body |
---|
| 248 | |
---|
| 249 | if debug: |
---|
| 250 | print("ENVIRON: %r\n" % environ) |
---|
| 251 | |
---|
| 252 | # SimpleHandler needs file-like stream objects for |
---|
| 253 | # requests, errors and responses |
---|
| 254 | reqIO = StringIO.StringIO(req.body) |
---|
| 255 | errIO = StringIO.StringIO() |
---|
| 256 | respIO = StringIO.StringIO() |
---|
| 257 | |
---|
| 258 | # execute the application |
---|
| 259 | handler = SimpleHandler(reqIO, respIO, errIO, environ, |
---|
| 260 | multithread=False, multiprocess=False) |
---|
| 261 | handler.run(application) |
---|
| 262 | |
---|
| 263 | # Get the response and filter out the response (=data) itself, |
---|
| 264 | # the response headers, |
---|
| 265 | # the response status code and the response status description |
---|
| 266 | response = respIO.getvalue() |
---|
| 267 | response = response.split("\r\n") |
---|
| 268 | data = response[-1] |
---|
| 269 | headers = dict([r.split(": ") for r in response[1:-2]]) |
---|
| 270 | code = response[0][9:12] |
---|
| 271 | status = response[0][13:] |
---|
| 272 | |
---|
| 273 | # strip BOM's from response data |
---|
| 274 | # Especially the WSGI handler from Django seems to generate them (2 actually, huh?) |
---|
| 275 | # a BOM isn't really necessary and cause HTML parsing errors in Chrome and Safari |
---|
| 276 | # See also: http://www.xs4all.nl/~mechiel/projects/bomstrip/ |
---|
| 277 | # Although I still find this a ugly hack, it does work. |
---|
| 278 | data = data.replace('\xef\xbb\xbf', '') |
---|
| 279 | |
---|
| 280 | # Get the generated errors |
---|
| 281 | errors = errIO.getvalue() |
---|
| 282 | |
---|
| 283 | # return the response |
---|
| 284 | if debug: |
---|
| 285 | print("RESPONSE: %r\n" % response) |
---|
| 286 | if errors: |
---|
| 287 | if debug: |
---|
| 288 | print("ERRORS: %r" % errors) |
---|
| 289 | data = "%s\r\n\r\n%s" % (data, errors) |
---|
| 290 | conn.reply_http( |
---|
| 291 | req, data, code=code, status=status, headers=headers) |
---|
| 292 | |
---|
| 293 | |
---|
| 294 | def run(servername, ip, port, softcron=True, logging=False, profiler=None, |
---|
| 295 | options=None): |
---|
| 296 | if servername == 'gevent': |
---|
| 297 | from gevent import monkey |
---|
| 298 | monkey.patch_all() |
---|
| 299 | elif servername == 'eventlet': |
---|
| 300 | import eventlet |
---|
| 301 | eventlet.monkey_patch() |
---|
| 302 | |
---|
| 303 | import gluon.main |
---|
| 304 | |
---|
| 305 | if logging: |
---|
| 306 | application = gluon.main.appfactory(wsgiapp=gluon.main.wsgibase, |
---|
| 307 | logfilename='httpserver.log', |
---|
| 308 | profiler_dir=profiler) |
---|
| 309 | else: |
---|
| 310 | application = gluon.main.wsgibase |
---|
| 311 | if softcron: |
---|
| 312 | from gluon.settings import global_settings |
---|
| 313 | global_settings.web2py_crontype = 'soft' |
---|
| 314 | getattr(Servers, servername)(application, (ip, int(port)), options=options) |
---|
| 315 | |
---|
| 316 | |
---|
| 317 | |
---|
| 318 | def main(): |
---|
| 319 | usage = "python anyserver.py -s tornado -i 127.0.0.1 -p 8000 -l -P" |
---|
| 320 | try: |
---|
| 321 | version = open('VERSION','r') |
---|
| 322 | except IOError: |
---|
| 323 | version = '' |
---|
| 324 | parser = optparse.OptionParser(usage, None, optparse.Option, version) |
---|
| 325 | parser.add_option('-l', |
---|
| 326 | '--logging', |
---|
| 327 | action='store_true', |
---|
| 328 | default=False, |
---|
| 329 | dest='logging', |
---|
| 330 | help='log into httpserver.log') |
---|
| 331 | parser.add_option('-P', |
---|
| 332 | '--profiler', |
---|
| 333 | default=False, |
---|
| 334 | dest='profiler_dir', |
---|
| 335 | help='profiler dir') |
---|
| 336 | servers = ', '.join(x for x in dir(Servers) if not x[0] == '_') |
---|
| 337 | parser.add_option('-s', |
---|
| 338 | '--server', |
---|
| 339 | default='rocket', |
---|
| 340 | dest='server', |
---|
| 341 | help='server name (%s)' % servers) |
---|
| 342 | parser.add_option('-i', |
---|
| 343 | '--ip', |
---|
| 344 | default='127.0.0.1', |
---|
| 345 | dest='ip', |
---|
| 346 | help='ip address') |
---|
| 347 | parser.add_option('-p', |
---|
| 348 | '--port', |
---|
| 349 | default='8000', |
---|
| 350 | dest='port', |
---|
| 351 | help='port number') |
---|
| 352 | parser.add_option('-w', |
---|
| 353 | '--workers', |
---|
| 354 | default=None, |
---|
| 355 | dest='workers', |
---|
| 356 | help='number of workers number') |
---|
| 357 | (options, args) = parser.parse_args() |
---|
| 358 | print('starting %s on %s:%s...' % ( |
---|
| 359 | options.server, options.ip, options.port)) |
---|
| 360 | run(options.server, options.ip, options.port, |
---|
| 361 | logging=options.logging, profiler=options.profiler_dir, |
---|
| 362 | options=options) |
---|
| 363 | |
---|
| 364 | if __name__ == '__main__': |
---|
| 365 | main() |
---|