/anyserver.py
http://github.com/web2py/web2py · Python · 365 lines · 274 code · 48 blank · 43 comment · 30 complexity · 979761012c79025eee7cc638219e8a19 MD5 · raw file
- #!/usr/bin/env python
- # -*- coding: utf-8 -*-
- """
- This file is part of the web2py Web Framework
- Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
- License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
- This file is based, although a rewrite, on MIT-licensed code from the Bottle web framework.
- """
- import os
- import sys
- import optparse
- import urllib
- path = os.path.dirname(os.path.abspath(__file__))
- os.chdir(path)
- sys.path = [path] + [p for p in sys.path if not p == path]
- class Servers:
- @staticmethod
- def cgi(app, address=None, **options):
- from wsgiref.handlers import CGIHandler
- CGIHandler().run(app) # Just ignore host and port here
- @staticmethod
- def flup(app, address, **options):
- import flup.server.fcgi
- flup.server.fcgi.WSGIServer(app, bindAddress=address).run()
- @staticmethod
- def wsgiref(app, address, **options): # pragma: no cover
- from wsgiref.simple_server import make_server, WSGIRequestHandler
- options = {}
- class QuietHandler(WSGIRequestHandler):
- def log_request(*args, **kw):
- pass
- options['handler_class'] = QuietHandler
- srv = make_server(address[0], address[1], app, **options)
- srv.serve_forever()
- @staticmethod
- def cherrypy(app, address, **options):
- from cheroot.wsgi import Server as WSGIServer
- server = WSGIServer(address, app)
- server.start()
- @staticmethod
- def rocket(app, address, **options):
- from gluon.rocket import CherryPyWSGIServer
- server = CherryPyWSGIServer(address, app)
- server.start()
- @staticmethod
- def rocket_with_repoze_profiler(app, address, **options):
- from gluon.rocket import CherryPyWSGIServer
- from repoze.profile.profiler import AccumulatingProfileMiddleware
- from gluon.settings import global_settings
- global_settings.web2py_crontype = 'none'
- wrapped = AccumulatingProfileMiddleware(
- app,
- log_filename='wsgi.prof',
- discard_first_request=True,
- flush_at_shutdown=True,
- path='/__profile__'
- )
- server = CherryPyWSGIServer(address, wrapped)
- server.start()
- @staticmethod
- def paste(app, address, **options):
- options = {}
- from paste import httpserver
- from paste.translogger import TransLogger
- httpserver.serve(app, host=address[0], port=address[1], **options)
- @staticmethod
- def fapws(app, address, **options):
- import fapws._evwsgi as evwsgi
- from fapws import base
- evwsgi.start(address[0], str(address[1]))
- evwsgi.set_base_module(base)
- def app(environ, start_response):
- environ['wsgi.multiprocess'] = False
- return app(environ, start_response)
- evwsgi.wsgi_cb(('', app))
- evwsgi.run()
- @staticmethod
- def gevent(app, address, **options):
- options = options['options']
- workers = options.workers
- from gevent import pywsgi
- from gevent.pool import Pool
- pywsgi.WSGIServer(address, app, spawn=workers and Pool(
- int(options.workers)) or 'default', log=None).serve_forever()
- @staticmethod
- def bjoern(app, address, **options):
- import bjoern
- bjoern.run(app, *address)
- @staticmethod
- def tornado(app, address, **options):
- import tornado.wsgi
- import tornado.httpserver
- import tornado.ioloop
- container = tornado.wsgi.WSGIContainer(app)
- server = tornado.httpserver.HTTPServer(container)
- server.listen(address=address[0], port=address[1])
- tornado.ioloop.IOLoop.instance().start()
- @staticmethod
- def twisted(app, address, **options):
- from twisted.web import server, wsgi
- from twisted.python.threadpool import ThreadPool
- from twisted.internet import reactor
- thread_pool = ThreadPool()
- thread_pool.start()
- reactor.addSystemEventTrigger('after', 'shutdown', thread_pool.stop)
- factory = server.Site(wsgi.WSGIResource(reactor, thread_pool, app))
- reactor.listenTCP(address[1], factory, interface=address[0])
- reactor.run()
- @staticmethod
- def diesel(app, address, **options):
- from diesel.protocols.wsgi import WSGIApplication
- app = WSGIApplication(app, port=address[1])
- app.run()
- @staticmethod
- def gunicorn(app, address, **options):
- options = {}
- from gunicorn.app.base import Application
- config = {'bind': "%s:%d" % address}
- config.update(options)
- sys.argv = ['anyserver.py']
- class GunicornApplication(Application):
- def init(self, parser, opts, args):
- return config
- def load(self):
- return app
- g = GunicornApplication()
- g.run()
- @staticmethod
- def eventlet(app, address, **options):
- from eventlet import wsgi, listen
- wsgi.server(listen(address), app)
- @staticmethod
- def mongrel2(app, address, **options):
- import uuid
- sys.path.append(os.path.abspath(os.path.dirname(__file__)))
- from mongrel2 import handler
- conn = handler.Connection(str(uuid.uuid4()),
- "tcp://127.0.0.1:9997",
- "tcp://127.0.0.1:9996")
- mongrel2_handler(app, conn, debug=False)
- @staticmethod
- def motor(app, address, **options):
- #https://github.com/rpedroso/motor
- import motor
- app = motor.WSGIContainer(app)
- http_server = motor.HTTPServer(app)
- http_server.listen(address=address[0], port=address[1])
- #http_server.start(2)
- motor.IOLoop.instance().start()
- @staticmethod
- def pulsar(app, address, **options):
- from pulsar.apps import wsgi
- sys.argv = ['anyserver.py']
- s = wsgi.WSGIServer(callable=app, bind="%s:%d" % address)
- s.start()
- @staticmethod
- def waitress(app, address, **options):
- from waitress import serve
- serve(app, host=address[0], port=address[1], _quiet=True)
- def mongrel2_handler(application, conn, debug=False):
- """
- Based on :
- https://github.com/berry/Mongrel2-WSGI-Handler/blob/master/wsgi-handler.py
- WSGI handler based on the Python wsgiref SimpleHandler.
- A WSGI application should return a iterable op StringTypes.
- Any encoding must be handled by the WSGI application itself.
- """
- from wsgiref.handlers import SimpleHandler
- try:
- import cStringIO as StringIO
- except:
- import StringIO
- # TODO - this wsgi handler executes the application and renders a page
- # in memory completely before returning it as a response to the client.
- # Thus, it does not "stream" the result back to the client. It should be
- # possible though. The SimpleHandler accepts file-like stream objects. So,
- # it should be just a matter of connecting 0MQ requests/response streams to
- # the SimpleHandler requests and response streams. However, the Python API
- # for Mongrel2 doesn't seem to support file-like stream objects for requests
- # and responses. Unless I have missed something.
- while True:
- if debug:
- print("WAITING FOR REQUEST")
- # receive a request
- req = conn.recv()
- if debug:
- print("REQUEST BODY: %r\n" % req.body)
- if req.is_disconnect():
- if debug:
- print("DISCONNECT")
- continue # effectively ignore the disconnect from the client
- # Set a couple of environment attributes a.k.a. header attributes
- # that are a must according to PEP 333
- environ = req.headers
- environ['SERVER_PROTOCOL'] = 'HTTP/1.1' # SimpleHandler expects a server_protocol, lets assume it is HTTP 1.1
- environ['REQUEST_METHOD'] = environ['METHOD']
- if ':' in environ['Host']:
- environ['SERVER_NAME'] = environ['Host'].split(':')[0]
- environ['SERVER_PORT'] = environ['Host'].split(':')[1]
- else:
- environ['SERVER_NAME'] = environ['Host']
- environ['SERVER_PORT'] = ''
- environ['SCRIPT_NAME'] = '' # empty for now
- environ['PATH_INFO'] = urllib.unquote(environ['PATH'])
- if '?' in environ['URI']:
- environ['QUERY_STRING'] = environ['URI'].split('?')[1]
- else:
- environ['QUERY_STRING'] = ''
- if 'Content-Length' in environ:
- environ['CONTENT_LENGTH'] = environ[
- 'Content-Length'] # necessary for POST to work with Django
- environ['wsgi.input'] = req.body
- if debug:
- print("ENVIRON: %r\n" % environ)
- # SimpleHandler needs file-like stream objects for
- # requests, errors and responses
- reqIO = StringIO.StringIO(req.body)
- errIO = StringIO.StringIO()
- respIO = StringIO.StringIO()
- # execute the application
- handler = SimpleHandler(reqIO, respIO, errIO, environ,
- multithread=False, multiprocess=False)
- handler.run(application)
- # Get the response and filter out the response (=data) itself,
- # the response headers,
- # the response status code and the response status description
- response = respIO.getvalue()
- response = response.split("\r\n")
- data = response[-1]
- headers = dict([r.split(": ") for r in response[1:-2]])
- code = response[0][9:12]
- status = response[0][13:]
- # strip BOM's from response data
- # Especially the WSGI handler from Django seems to generate them (2 actually, huh?)
- # a BOM isn't really necessary and cause HTML parsing errors in Chrome and Safari
- # See also: http://www.xs4all.nl/~mechiel/projects/bomstrip/
- # Although I still find this a ugly hack, it does work.
- data = data.replace('\xef\xbb\xbf', '')
- # Get the generated errors
- errors = errIO.getvalue()
- # return the response
- if debug:
- print("RESPONSE: %r\n" % response)
- if errors:
- if debug:
- print("ERRORS: %r" % errors)
- data = "%s\r\n\r\n%s" % (data, errors)
- conn.reply_http(
- req, data, code=code, status=status, headers=headers)
- def run(servername, ip, port, softcron=True, logging=False, profiler=None,
- options=None):
- if servername == 'gevent':
- from gevent import monkey
- monkey.patch_all()
- elif servername == 'eventlet':
- import eventlet
- eventlet.monkey_patch()
- import gluon.main
- if logging:
- application = gluon.main.appfactory(wsgiapp=gluon.main.wsgibase,
- logfilename='httpserver.log',
- profiler_dir=profiler)
- else:
- application = gluon.main.wsgibase
- if softcron:
- from gluon.settings import global_settings
- global_settings.web2py_crontype = 'soft'
- getattr(Servers, servername)(application, (ip, int(port)), options=options)
- def main():
- usage = "python anyserver.py -s tornado -i 127.0.0.1 -p 8000 -l -P"
- try:
- version = open('VERSION','r')
- except IOError:
- version = ''
- parser = optparse.OptionParser(usage, None, optparse.Option, version)
- parser.add_option('-l',
- '--logging',
- action='store_true',
- default=False,
- dest='logging',
- help='log into httpserver.log')
- parser.add_option('-P',
- '--profiler',
- default=False,
- dest='profiler_dir',
- help='profiler dir')
- servers = ', '.join(x for x in dir(Servers) if not x[0] == '_')
- parser.add_option('-s',
- '--server',
- default='rocket',
- dest='server',
- help='server name (%s)' % servers)
- parser.add_option('-i',
- '--ip',
- default='127.0.0.1',
- dest='ip',
- help='ip address')
- parser.add_option('-p',
- '--port',
- default='8000',
- dest='port',
- help='port number')
- parser.add_option('-w',
- '--workers',
- default=None,
- dest='workers',
- help='number of workers number')
- (options, args) = parser.parse_args()
- print('starting %s on %s:%s...' % (
- options.server, options.ip, options.port))
- run(options.server, options.ip, options.port,
- logging=options.logging, profiler=options.profiler_dir,
- options=options)
- if __name__ == '__main__':
- main()