366 lines
13 KiB
Python
366 lines
13 KiB
Python
#!/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()
|