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() |
---|