PageRenderTime 44ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/anyserver.py

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