1 __all__ = ["runsimple"]
2
3 import sys, os
4 from SimpleHTTPServer import SimpleHTTPRequestHandler
5
6 import webapi as web
7 import net
8 import utils
9
10 -def runbasic(func, server_address=("0.0.0.0", 8080)):
11 """
12 Runs a simple HTTP server hosting WSGI app `func`. The directory `static/`
13 is hosted statically.
14
15 Based on [WsgiServer][ws] from [Colin Stewart][cs].
16
17 [ws]: http://www.owlfish.com/software/wsgiutils/documentation/wsgi-server-api.html
18 [cs]: http://www.owlfish.com/
19 """
20
21
22
23
24
25 import SimpleHTTPServer, SocketServer, BaseHTTPServer, urlparse
26 import socket, errno
27 import traceback
28
29 class WSGIHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
30 def run_wsgi_app(self):
31 protocol, host, path, parameters, query, fragment = \
32 urlparse.urlparse('http://dummyhost%s' % self.path)
33
34
35 env = {'wsgi.version': (1, 0)
36 ,'wsgi.url_scheme': 'http'
37 ,'wsgi.input': self.rfile
38 ,'wsgi.errors': sys.stderr
39 ,'wsgi.multithread': 1
40 ,'wsgi.multiprocess': 0
41 ,'wsgi.run_once': 0
42 ,'REQUEST_METHOD': self.command
43 ,'REQUEST_URI': self.path
44 ,'PATH_INFO': path
45 ,'QUERY_STRING': query
46 ,'CONTENT_TYPE': self.headers.get('Content-Type', '')
47 ,'CONTENT_LENGTH': self.headers.get('Content-Length', '')
48 ,'REMOTE_ADDR': self.client_address[0]
49 ,'SERVER_NAME': self.server.server_address[0]
50 ,'SERVER_PORT': str(self.server.server_address[1])
51 ,'SERVER_PROTOCOL': self.request_version
52 }
53
54 for http_header, http_value in self.headers.items():
55 env ['HTTP_%s' % http_header.replace('-', '_').upper()] = \
56 http_value
57
58
59 self.wsgi_sent_headers = 0
60 self.wsgi_headers = []
61
62 try:
63
64 result = self.server.app(env, self.wsgi_start_response)
65 try:
66 try:
67 for data in result:
68 if data:
69 self.wsgi_write_data(data)
70 finally:
71 if hasattr(result, 'close'):
72 result.close()
73 except socket.error, socket_err:
74
75 if (socket_err.args[0] in \
76 (errno.ECONNABORTED, errno.EPIPE)):
77 return
78 except socket.timeout, socket_timeout:
79 return
80 except:
81 print >> web.debug, traceback.format_exc(),
82
83 if (not self.wsgi_sent_headers):
84
85 self.wsgi_write_data(" ")
86 return
87
88 do_POST = run_wsgi_app
89 do_PUT = run_wsgi_app
90 do_DELETE = run_wsgi_app
91
92 def do_GET(self):
93 if self.path.startswith('/static/'):
94 SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
95 else:
96 self.run_wsgi_app()
97
98 def wsgi_start_response(self, response_status, response_headers,
99 exc_info=None):
100 if (self.wsgi_sent_headers):
101 raise Exception \
102 ("Headers already sent and start_response called again!")
103
104 self.wsgi_headers = (response_status, response_headers)
105 return self.wsgi_write_data
106
107 def wsgi_write_data(self, data):
108 if (not self.wsgi_sent_headers):
109 status, headers = self.wsgi_headers
110
111 status_code = status[:status.find(' ')]
112 status_msg = status[status.find(' ') + 1:]
113 self.send_response(int(status_code), status_msg)
114 for header, value in headers:
115 self.send_header(header, value)
116 self.end_headers()
117 self.wsgi_sent_headers = 1
118
119 self.wfile.write(data)
120
121 class WSGIServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
122 def __init__(self, func, server_address):
123 BaseHTTPServer.HTTPServer.__init__(self,
124 server_address,
125 WSGIHandler)
126 self.app = func
127 self.serverShuttingDown = 0
128
129 print "http://%s:%d/" % server_address
130 WSGIServer(func, server_address).serve_forever()
131
132 -def runsimple(func, server_address=("0.0.0.0", 8080)):
133 """
134 Runs [CherryPy][cp] WSGI server hosting WSGI app `func`.
135 The directory `static/` is hosted statically.
136
137 [cp]: http://www.cherrypy.org
138 """
139 func = StaticMiddleware(func)
140 func = LogMiddleware(func)
141
142 server = WSGIServer(server_address, func)
143
144 print "http://%s:%d/" % server_address
145 try:
146 server.start()
147 except KeyboardInterrupt:
148 server.stop()
149
151 """Creates CherryPy WSGI server listening at `server_address` to serve `wsgi_app`.
152 This function can be overwritten to customize the webserver or use a different webserver.
153 """
154 from wsgiserver import CherryPyWSGIServer
155 return CherryPyWSGIServer(server_address, wsgi_app, server_name="localhost")
156
158 """WSGI application for serving static files."""
159 - def __init__(self, environ, start_response):
163
166
168 self.headers.append((name, value))
169
172
174
176 environ = self.environ
177
178 self.path = environ.get('PATH_INFO', '')
179 self.client_address = environ.get('REMOTE_ADDR','-'), \
180 environ.get('REMOTE_PORT','-')
181 self.command = environ.get('REQUEST_METHOD', '-')
182
183 from cStringIO import StringIO
184 self.wfile = StringIO()
185
186 try:
187 path = self.translate_path(self.path)
188 etag = '"%s"' % os.path.getmtime(path)
189 client_etag = environ.get('HTTP_IF_NONE_MATCH')
190 self.send_header('ETag', etag)
191 if etag == client_etag:
192 self.send_response(304, "Not Modified")
193 self.start_response(self.status, self.headers)
194 raise StopIteration
195 except OSError:
196 pass
197
198 f = self.send_head()
199 self.start_response(self.status, self.headers)
200
201 if f:
202 block_size = 16 * 1024
203 while True:
204 buf = f.read(block_size)
205 if not buf:
206 break
207 yield buf
208 f.close()
209 else:
210 value = self.wfile.getvalue()
211 yield value
212
214 """WSGI middleware for serving static files."""
215 - def __init__(self, app, prefix='/static/'):
216 self.app = app
217 self.prefix = prefix
218
219 - def __call__(self, environ, start_response):
225
227 """WSGI middleware for logging the status."""
229 self.app = app
230 self.format = '%s - - [%s] "%s %s %s" - %s'
231
232 from BaseHTTPServer import BaseHTTPRequestHandler
233 import StringIO
234 f = StringIO.StringIO()
235
236 class FakeSocket:
237 def makefile(self, *a):
238 return f
239
240
241 self.log_date_time_string = BaseHTTPRequestHandler(FakeSocket(), None, None).log_date_time_string
242
243 - def __call__(self, environ, start_response):
244 def xstart_response(status, response_headers, *args):
245 out = start_response(status, response_headers, *args)
246 self.log(status, environ)
247 return out
248
249 return self.app(environ, xstart_response)
250
251 - def log(self, status, environ):
252 outfile = environ.get('wsgi.errors', web.debug)
253 req = environ.get('PATH_INFO', '_')
254 protocol = environ.get('ACTUAL_SERVER_PROTOCOL', '-')
255 method = environ.get('REQUEST_METHOD', '-')
256 host = "%s:%s" % (environ.get('REMOTE_ADDR','-'),
257 environ.get('REMOTE_PORT','-'))
258
259 time = self.log_date_time_string()
260
261 msg = self.format % (host, time, protocol, method, req, status)
262 print >> outfile, utils.safestr(msg)
263