om@3
|
1 |
__all__ = ["runsimple"]
|
om@3
|
2 |
|
om@3
|
3 |
import sys, os
|
om@3
|
4 |
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
om@3
|
5 |
import urllib
|
om@3
|
6 |
import posixpath
|
om@3
|
7 |
|
om@3
|
8 |
import webapi as web
|
om@3
|
9 |
import net
|
om@3
|
10 |
import utils
|
om@3
|
11 |
|
om@3
|
12 |
def runbasic(func, server_address=("0.0.0.0", 8080)):
|
om@3
|
13 |
"""
|
om@3
|
14 |
Runs a simple HTTP server hosting WSGI app `func`. The directory `static/`
|
om@3
|
15 |
is hosted statically.
|
om@3
|
16 |
|
om@3
|
17 |
Based on [WsgiServer][ws] from [Colin Stewart][cs].
|
om@3
|
18 |
|
om@3
|
19 |
[ws]: http://www.owlfish.com/software/wsgiutils/documentation/wsgi-server-api.html
|
om@3
|
20 |
[cs]: http://www.owlfish.com/
|
om@3
|
21 |
"""
|
om@3
|
22 |
# Copyright (c) 2004 Colin Stewart (http://www.owlfish.com/)
|
om@3
|
23 |
# Modified somewhat for simplicity
|
om@3
|
24 |
# Used under the modified BSD license:
|
om@3
|
25 |
# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
|
om@3
|
26 |
|
om@3
|
27 |
import SimpleHTTPServer, SocketServer, BaseHTTPServer, urlparse
|
om@3
|
28 |
import socket, errno
|
om@3
|
29 |
import traceback
|
om@3
|
30 |
|
om@3
|
31 |
class WSGIHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
om@3
|
32 |
def run_wsgi_app(self):
|
om@3
|
33 |
protocol, host, path, parameters, query, fragment = \
|
om@3
|
34 |
urlparse.urlparse('http://dummyhost%s' % self.path)
|
om@3
|
35 |
|
om@3
|
36 |
# we only use path, query
|
om@3
|
37 |
env = {'wsgi.version': (1, 0)
|
om@3
|
38 |
,'wsgi.url_scheme': 'http'
|
om@3
|
39 |
,'wsgi.input': self.rfile
|
om@3
|
40 |
,'wsgi.errors': sys.stderr
|
om@3
|
41 |
,'wsgi.multithread': 1
|
om@3
|
42 |
,'wsgi.multiprocess': 0
|
om@3
|
43 |
,'wsgi.run_once': 0
|
om@3
|
44 |
,'REQUEST_METHOD': self.command
|
om@3
|
45 |
,'REQUEST_URI': self.path
|
om@3
|
46 |
,'PATH_INFO': path
|
om@3
|
47 |
,'QUERY_STRING': query
|
om@3
|
48 |
,'CONTENT_TYPE': self.headers.get('Content-Type', '')
|
om@3
|
49 |
,'CONTENT_LENGTH': self.headers.get('Content-Length', '')
|
om@3
|
50 |
,'REMOTE_ADDR': self.client_address[0]
|
om@3
|
51 |
,'SERVER_NAME': self.server.server_address[0]
|
om@3
|
52 |
,'SERVER_PORT': str(self.server.server_address[1])
|
om@3
|
53 |
,'SERVER_PROTOCOL': self.request_version
|
om@3
|
54 |
}
|
om@3
|
55 |
|
om@3
|
56 |
for http_header, http_value in self.headers.items():
|
om@3
|
57 |
env ['HTTP_%s' % http_header.replace('-', '_').upper()] = \
|
om@3
|
58 |
http_value
|
om@3
|
59 |
|
om@3
|
60 |
# Setup the state
|
om@3
|
61 |
self.wsgi_sent_headers = 0
|
om@3
|
62 |
self.wsgi_headers = []
|
om@3
|
63 |
|
om@3
|
64 |
try:
|
om@3
|
65 |
# We have there environment, now invoke the application
|
om@3
|
66 |
result = self.server.app(env, self.wsgi_start_response)
|
om@3
|
67 |
try:
|
om@3
|
68 |
try:
|
om@3
|
69 |
for data in result:
|
om@3
|
70 |
if data:
|
om@3
|
71 |
self.wsgi_write_data(data)
|
om@3
|
72 |
finally:
|
om@3
|
73 |
if hasattr(result, 'close'):
|
om@3
|
74 |
result.close()
|
om@3
|
75 |
except socket.error, socket_err:
|
om@3
|
76 |
# Catch common network errors and suppress them
|
om@3
|
77 |
if (socket_err.args[0] in \
|
om@3
|
78 |
(errno.ECONNABORTED, errno.EPIPE)):
|
om@3
|
79 |
return
|
om@3
|
80 |
except socket.timeout, socket_timeout:
|
om@3
|
81 |
return
|
om@3
|
82 |
except:
|
om@3
|
83 |
print >> web.debug, traceback.format_exc(),
|
om@3
|
84 |
|
om@3
|
85 |
if (not self.wsgi_sent_headers):
|
om@3
|
86 |
# We must write out something!
|
om@3
|
87 |
self.wsgi_write_data(" ")
|
om@3
|
88 |
return
|
om@3
|
89 |
|
om@3
|
90 |
do_POST = run_wsgi_app
|
om@3
|
91 |
do_PUT = run_wsgi_app
|
om@3
|
92 |
do_DELETE = run_wsgi_app
|
om@3
|
93 |
|
om@3
|
94 |
def do_GET(self):
|
om@3
|
95 |
if self.path.startswith('/static/'):
|
om@3
|
96 |
SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
|
om@3
|
97 |
else:
|
om@3
|
98 |
self.run_wsgi_app()
|
om@3
|
99 |
|
om@3
|
100 |
def wsgi_start_response(self, response_status, response_headers,
|
om@3
|
101 |
exc_info=None):
|
om@3
|
102 |
if (self.wsgi_sent_headers):
|
om@3
|
103 |
raise Exception \
|
om@3
|
104 |
("Headers already sent and start_response called again!")
|
om@3
|
105 |
# Should really take a copy to avoid changes in the application....
|
om@3
|
106 |
self.wsgi_headers = (response_status, response_headers)
|
om@3
|
107 |
return self.wsgi_write_data
|
om@3
|
108 |
|
om@3
|
109 |
def wsgi_write_data(self, data):
|
om@3
|
110 |
if (not self.wsgi_sent_headers):
|
om@3
|
111 |
status, headers = self.wsgi_headers
|
om@3
|
112 |
# Need to send header prior to data
|
om@3
|
113 |
status_code = status[:status.find(' ')]
|
om@3
|
114 |
status_msg = status[status.find(' ') + 1:]
|
om@3
|
115 |
self.send_response(int(status_code), status_msg)
|
om@3
|
116 |
for header, value in headers:
|
om@3
|
117 |
self.send_header(header, value)
|
om@3
|
118 |
self.end_headers()
|
om@3
|
119 |
self.wsgi_sent_headers = 1
|
om@3
|
120 |
# Send the data
|
om@3
|
121 |
self.wfile.write(data)
|
om@3
|
122 |
|
om@3
|
123 |
class WSGIServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
|
om@3
|
124 |
def __init__(self, func, server_address):
|
om@3
|
125 |
BaseHTTPServer.HTTPServer.__init__(self,
|
om@3
|
126 |
server_address,
|
om@3
|
127 |
WSGIHandler)
|
om@3
|
128 |
self.app = func
|
om@3
|
129 |
self.serverShuttingDown = 0
|
om@3
|
130 |
|
om@3
|
131 |
print "http://%s:%d/" % server_address
|
om@3
|
132 |
WSGIServer(func, server_address).serve_forever()
|
om@3
|
133 |
|
om@3
|
134 |
# The WSGIServer instance.
|
om@3
|
135 |
# Made global so that it can be stopped in embedded mode.
|
om@3
|
136 |
server = None
|
om@3
|
137 |
|
om@3
|
138 |
def runsimple(func, server_address=("0.0.0.0", 8080)):
|
om@3
|
139 |
"""
|
om@3
|
140 |
Runs [CherryPy][cp] WSGI server hosting WSGI app `func`.
|
om@3
|
141 |
The directory `static/` is hosted statically.
|
om@3
|
142 |
|
om@3
|
143 |
[cp]: http://www.cherrypy.org
|
om@3
|
144 |
"""
|
om@3
|
145 |
global server
|
om@3
|
146 |
func = StaticMiddleware(func)
|
om@3
|
147 |
func = LogMiddleware(func)
|
om@3
|
148 |
|
om@3
|
149 |
server = WSGIServer(server_address, func)
|
om@3
|
150 |
|
om@3
|
151 |
if server.ssl_adapter:
|
om@3
|
152 |
print "https://%s:%d/" % server_address
|
om@3
|
153 |
else:
|
om@3
|
154 |
print "http://%s:%d/" % server_address
|
om@3
|
155 |
|
om@3
|
156 |
try:
|
om@3
|
157 |
server.start()
|
om@3
|
158 |
except (KeyboardInterrupt, SystemExit):
|
om@3
|
159 |
server.stop()
|
om@3
|
160 |
server = None
|
om@3
|
161 |
|
om@3
|
162 |
def WSGIServer(server_address, wsgi_app):
|
om@3
|
163 |
"""Creates CherryPy WSGI server listening at `server_address` to serve `wsgi_app`.
|
om@3
|
164 |
This function can be overwritten to customize the webserver or use a different webserver.
|
om@3
|
165 |
"""
|
om@3
|
166 |
import wsgiserver
|
om@3
|
167 |
|
om@3
|
168 |
# Default values of wsgiserver.ssl_adapters uses cherrypy.wsgiserver
|
om@3
|
169 |
# prefix. Overwriting it make it work with web.wsgiserver.
|
om@3
|
170 |
wsgiserver.ssl_adapters = {
|
om@3
|
171 |
'builtin': 'web.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
|
om@3
|
172 |
'pyopenssl': 'web.wsgiserver.ssl_pyopenssl.pyOpenSSLAdapter',
|
om@3
|
173 |
}
|
om@3
|
174 |
|
om@3
|
175 |
server = wsgiserver.CherryPyWSGIServer(server_address, wsgi_app, server_name="localhost")
|
om@3
|
176 |
|
om@3
|
177 |
def create_ssl_adapter(cert, key):
|
om@3
|
178 |
# wsgiserver tries to import submodules as cherrypy.wsgiserver.foo.
|
om@3
|
179 |
# That doesn't work as not it is web.wsgiserver.
|
om@3
|
180 |
# Patching sys.modules temporarily to make it work.
|
om@3
|
181 |
import types
|
om@3
|
182 |
cherrypy = types.ModuleType('cherrypy')
|
om@3
|
183 |
cherrypy.wsgiserver = wsgiserver
|
om@3
|
184 |
sys.modules['cherrypy'] = cherrypy
|
om@3
|
185 |
sys.modules['cherrypy.wsgiserver'] = wsgiserver
|
om@3
|
186 |
|
om@3
|
187 |
from wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter
|
om@3
|
188 |
adapter = pyOpenSSLAdapter(cert, key)
|
om@3
|
189 |
|
om@3
|
190 |
# We are done with our work. Cleanup the patches.
|
om@3
|
191 |
del sys.modules['cherrypy']
|
om@3
|
192 |
del sys.modules['cherrypy.wsgiserver']
|
om@3
|
193 |
|
om@3
|
194 |
return adapter
|
om@3
|
195 |
|
om@3
|
196 |
# SSL backward compatibility
|
om@3
|
197 |
if (server.ssl_adapter is None and
|
om@3
|
198 |
getattr(server, 'ssl_certificate', None) and
|
om@3
|
199 |
getattr(server, 'ssl_private_key', None)):
|
om@3
|
200 |
server.ssl_adapter = create_ssl_adapter(server.ssl_certificate, server.ssl_private_key)
|
om@3
|
201 |
|
om@3
|
202 |
server.nodelay = not sys.platform.startswith('java') # TCP_NODELAY isn't supported on the JVM
|
om@3
|
203 |
return server
|
om@3
|
204 |
|
om@3
|
205 |
class StaticApp(SimpleHTTPRequestHandler):
|
om@3
|
206 |
"""WSGI application for serving static files."""
|
om@3
|
207 |
def __init__(self, environ, start_response):
|
om@3
|
208 |
self.headers = []
|
om@3
|
209 |
self.environ = environ
|
om@3
|
210 |
self.start_response = start_response
|
om@3
|
211 |
|
om@3
|
212 |
def send_response(self, status, msg=""):
|
om@3
|
213 |
self.status = str(status) + " " + msg
|
om@3
|
214 |
|
om@3
|
215 |
def send_header(self, name, value):
|
om@3
|
216 |
self.headers.append((name, value))
|
om@3
|
217 |
|
om@3
|
218 |
def end_headers(self):
|
om@3
|
219 |
pass
|
om@3
|
220 |
|
om@3
|
221 |
def log_message(*a): pass
|
om@3
|
222 |
|
om@3
|
223 |
def __iter__(self):
|
om@3
|
224 |
environ = self.environ
|
om@3
|
225 |
|
om@3
|
226 |
self.path = environ.get('PATH_INFO', '')
|
om@3
|
227 |
self.client_address = environ.get('REMOTE_ADDR','-'), \
|
om@3
|
228 |
environ.get('REMOTE_PORT','-')
|
om@3
|
229 |
self.command = environ.get('REQUEST_METHOD', '-')
|
om@3
|
230 |
|
om@3
|
231 |
from cStringIO import StringIO
|
om@3
|
232 |
self.wfile = StringIO() # for capturing error
|
om@3
|
233 |
|
om@3
|
234 |
try:
|
om@3
|
235 |
path = self.translate_path(self.path)
|
om@3
|
236 |
etag = '"%s"' % os.path.getmtime(path)
|
om@3
|
237 |
client_etag = environ.get('HTTP_IF_NONE_MATCH')
|
om@3
|
238 |
self.send_header('ETag', etag)
|
om@3
|
239 |
if etag == client_etag:
|
om@3
|
240 |
self.send_response(304, "Not Modified")
|
om@3
|
241 |
self.start_response(self.status, self.headers)
|
om@3
|
242 |
raise StopIteration
|
om@3
|
243 |
except OSError:
|
om@3
|
244 |
pass # Probably a 404
|
om@3
|
245 |
|
om@3
|
246 |
f = self.send_head()
|
om@3
|
247 |
self.start_response(self.status, self.headers)
|
om@3
|
248 |
|
om@3
|
249 |
if f:
|
om@3
|
250 |
block_size = 16 * 1024
|
om@3
|
251 |
while True:
|
om@3
|
252 |
buf = f.read(block_size)
|
om@3
|
253 |
if not buf:
|
om@3
|
254 |
break
|
om@3
|
255 |
yield buf
|
om@3
|
256 |
f.close()
|
om@3
|
257 |
else:
|
om@3
|
258 |
value = self.wfile.getvalue()
|
om@3
|
259 |
yield value
|
om@3
|
260 |
|
om@3
|
261 |
class StaticMiddleware:
|
om@3
|
262 |
"""WSGI middleware for serving static files."""
|
om@3
|
263 |
def __init__(self, app, prefix='/static/'):
|
om@3
|
264 |
self.app = app
|
om@3
|
265 |
self.prefix = prefix
|
om@3
|
266 |
|
om@3
|
267 |
def __call__(self, environ, start_response):
|
om@3
|
268 |
path = environ.get('PATH_INFO', '')
|
om@3
|
269 |
path = self.normpath(path)
|
om@3
|
270 |
|
om@3
|
271 |
if path.startswith(self.prefix):
|
om@3
|
272 |
return StaticApp(environ, start_response)
|
om@3
|
273 |
else:
|
om@3
|
274 |
return self.app(environ, start_response)
|
om@3
|
275 |
|
om@3
|
276 |
def normpath(self, path):
|
om@3
|
277 |
path2 = posixpath.normpath(urllib.unquote(path))
|
om@3
|
278 |
if path.endswith("/"):
|
om@3
|
279 |
path2 += "/"
|
om@3
|
280 |
return path2
|
om@3
|
281 |
|
om@3
|
282 |
|
om@3
|
283 |
class LogMiddleware:
|
om@3
|
284 |
"""WSGI middleware for logging the status."""
|
om@3
|
285 |
def __init__(self, app):
|
om@3
|
286 |
self.app = app
|
om@3
|
287 |
self.format = '%s - - [%s] "%s %s %s" - %s'
|
om@3
|
288 |
|
om@3
|
289 |
from BaseHTTPServer import BaseHTTPRequestHandler
|
om@3
|
290 |
import StringIO
|
om@3
|
291 |
f = StringIO.StringIO()
|
om@3
|
292 |
|
om@3
|
293 |
class FakeSocket:
|
om@3
|
294 |
def makefile(self, *a):
|
om@3
|
295 |
return f
|
om@3
|
296 |
|
om@3
|
297 |
# take log_date_time_string method from BaseHTTPRequestHandler
|
om@3
|
298 |
self.log_date_time_string = BaseHTTPRequestHandler(FakeSocket(), None, None).log_date_time_string
|
om@3
|
299 |
|
om@3
|
300 |
def __call__(self, environ, start_response):
|
om@3
|
301 |
def xstart_response(status, response_headers, *args):
|
om@3
|
302 |
out = start_response(status, response_headers, *args)
|
om@3
|
303 |
self.log(status, environ)
|
om@3
|
304 |
return out
|
om@3
|
305 |
|
om@3
|
306 |
return self.app(environ, xstart_response)
|
om@3
|
307 |
|
om@3
|
308 |
def log(self, status, environ):
|
om@3
|
309 |
outfile = environ.get('wsgi.errors', web.debug)
|
om@3
|
310 |
req = environ.get('PATH_INFO', '_')
|
om@3
|
311 |
protocol = environ.get('ACTUAL_SERVER_PROTOCOL', '-')
|
om@3
|
312 |
method = environ.get('REQUEST_METHOD', '-')
|
om@3
|
313 |
host = "%s:%s" % (environ.get('REMOTE_ADDR','-'),
|
om@3
|
314 |
environ.get('REMOTE_PORT','-'))
|
om@3
|
315 |
|
om@3
|
316 |
time = self.log_date_time_string()
|
om@3
|
317 |
|
om@3
|
318 |
msg = self.format % (host, time, protocol, method, req, status)
|
om@3
|
319 |
print >> outfile, utils.safestr(msg)
|