Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

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