0001import threading
0002import urllib
0003from itertools import count
0004import time
0005import md5
0006from paste.request import path_info_pop, construct_url, get_cookies, parse_formvars
0007from paste import httpexceptions
0008from paste import httpheaders
0009from paste.util.template import Template
0010import simplejson
0011import re
0012
0013counter = count()
0014
0015def make_id():
0016 value = str(time.time()) + str(counter.next())
0017 h = md5.new(value).hexdigest()
0018 return h
0019
0020class WaitForIt(object):
0021
0022 def __init__(self, app, time_limit=10, poll_time=10,
0023 template=None):
0024 self.app = app
0025 self.time_limit = time_limit
0026 self.poll_time = poll_time
0027 self.pending = {}
0028 if template is None:
0029 template = TEMPLATE
0030 if isinstance(template, basestring):
0031 template = Template(template)
0032 self.template = template
0033
0034 def __call__(self, environ, start_response):
0035 assert not environ['wsgi.multiprocess'], (
0036 "WaitForIt does not work in a multiprocess environment")
0037 path_info = environ.get('PATH_INFO', '')
0038 if path_info.startswith('/.waitforit/'):
0039 path_info_pop(environ)
0040 return self.check_status(environ, start_response)
0041 try:
0042 id = self.get_id(environ)
0043 if id:
0044 if id in self.pending:
0045 return self.send_wait_page(environ, start_response, id=id)
0046 else:
0047
0048 qs = environ['QUERY_STRING']
0049 qs = re.sub(r'&?waitforit_id=[a-f0-9]*', '', qs)
0050 qs = re.sub(r'&send$', '', qs)
0051 environ['QUERY_STRING'] = qs
0052
0053 exc = httpexceptions.HTTPMovedPermanently(
0054 headers=[('Location', construct_url(environ))])
0055 return exc(environ, start_response)
0056 except KeyError:
0057
0058 pass
0059 if not self.accept_html(environ):
0060 return self.app(environ, start_response)
0061 data = []
0062 progress = {}
0063 environ['waitforit.progress'] = progress
0064 event = threading.Event()
0065 self.launch_application(environ, data, event, progress)
0066 event.wait(self.time_limit)
0067 if not data and progress.get('synchronous'):
0068
0069
0070 event.wait()
0071 if not data:
0072
0073 id = make_id()
0074 self.pending[id] = [data, event, progress]
0075 return self.start_wait_page(environ, start_response, id)
0076 else:
0077
0078 return self.send_page(start_response, data)
0079
0080 def accept_html(self, environ):
0081 accept = httpheaders.ACCEPT.parse(environ)
0082 if not accept:
0083 return True
0084 for arg in accept:
0085 if ';' in arg:
0086 arg = arg.split(';', 1)[0]
0087 if arg in ('*/*', 'text/*', 'text/html', 'application/xhtml+xml',
0088 'application/xml', 'text/xml'):
0089 return True
0090 return False
0091
0092 def send_wait_page(self, environ, start_response, id=None):
0093 if id is None:
0094 id = self.get_id(environ)
0095 self.get_id(environ)
0096 if self.pending[id][0]:
0097
0098
0099 data, event, progress = self.pending.pop(id)
0100 return self.send_page(start_response, data)
0101 request_url = construct_url(environ)
0102 waitforit_url = construct_url(environ, path_info='/.waitforit/')
0103 page = self.template.substitute(
0104 request_url=request_url,
0105 waitforit_url=waitforit_url,
0106 poll_time=self.poll_time,
0107 time_limit=self.time_limit,
0108 environ=environ,
0109 id=id)
0110 if isinstance(page, unicode):
0111 page = page.encode('utf8')
0112 start_response('200 OK',
0113 [('Content-Type', 'text/html; charset=utf8'),
0114 ('Content-Length', str(len(page))),
0115 ('Set-Cookie', 'waitforit_id=%s' % id),
0116 ])
0117 return [page]
0118
0119 def start_wait_page(self, environ, start_response, id):
0120 url = construct_url(environ)
0121 if '?' in url:
0122 url += '&'
0123 else:
0124 url += '?'
0125 url += 'waitforit_id=%s' % urllib.quote(id)
0126 exc = httpexceptions.HTTPTemporaryRedirect(
0127 headers=[('Location', url)])
0128 return exc(environ, start_response)
0129
0130 def send_page(self, start_response, data):
0131 status, headers, exc_info, app_iter = data
0132 start_response(status, headers, exc_info)
0133 return app_iter
0134
0135 def get_id(self, environ):
0136 qs = parse_formvars(environ)
0137 return qs['waitforit_id']
0138
0139 def check_status(self, environ, start_response, id=None):
0140 assert environ['PATH_INFO'] == '/status.json', (
0141 "Bad PATH_INFO=%r for %r" % (environ['PATH_INFO'], construct_url(environ)))
0142 if id is None:
0143 try:
0144 id = self.get_id(environ)
0145 except KeyError:
0146 body = "There is no pending request with the id %s" % id
0147 start_response('400 Bad Request', [
0148 ('Content-type', 'text/plain'),
0149 ('Content-length', str(len(body)))])
0150 return [body]
0151 try:
0152 data, event, progress = self.pending[id]
0153 except KeyError:
0154 data, event, progress = [True, None, None]
0155 if not data:
0156 result = {'done': False, 'progress': progress}
0157 else:
0158 result = {'done': True}
0159 start_response('200 OK',
0160 [('Content-Type', 'application/json'),
0161 ('Content-Length', str(len(result))),
0162 ])
0163 return [simplejson.dumps(result)]
0164
0165 def launch_application(self, environ, data, event, progress):
0166 t = threading.Thread(target=self.run_application,
0167 args=(environ, data, event, progress))
0168 t.setDaemon(True)
0169 t.start()
0170
0171 def run_application(self, environ, data, event, progress):
0172 start_response_data = []
0173 output = []
0174 def start_response(status, headers, exc_info=None):
0175 start_response_data[:] = [status, headers, exc_info]
0176 return output.append
0177 app_iter = self.app(environ, start_response)
0178 if output:
0179
0180 output.extend(app_iter)
0181 app_iter = output
0182 elif not start_response_data:
0183
0184 app_iter = list(app_iter)
0185 assert start_response_data
0186 start_response_data.append(app_iter)
0187 data[:] = start_response_data
0188 event.set()
0189
0190
0191
0192TEMPLATE = '''\
0193<html>
0194 <head>
0195 <title>Please wait</title>
0196 <script type="text/javascript">
0197 waitforit_url = "{{waitforit_url}}";
0198 poll_time = {{poll_time}};
0199 <<JAVASCRIPT>>
0200 </script>
0201 <style type="text/css">
0202 <<CSS>>
0203 </style>
0204 </head>
0205 <body onload="checkStatus()">
0206
0207 <h1>Please wait...</h1>
0208
0209 <p>
0210 The page you have requested is taking a while to generate...
0211 </p>
0212
0213 <p id="progress-box">
0214 </p>
0215
0216 <p id="percent-box">
0217
0218 <p id="error-box">
0219 </p>
0220
0221 </body>
0222</html>
0223'''
0224
0225JAVASCRIPT = '''\
0226function getXMLHttpRequest() {
0227 var tryThese = [
0228 function () { return new XMLHttpRequest(); },
0229 function () { return new ActiveXObject('Msxml2.XMLHTTP'); },
0230 function () { return new ActiveXObject('Microsoft.XMLHTTP'); },
0231 function () { return new ActiveXObject('Msxml2.XMLHTTP.4.0'); }
0232 ];
0233 for (var i = 0; i < tryThese.length; i++) {
0234 var func = tryThese[i];
0235 try {
0236 return func();
0237 } catch (e) {
0238 // pass
0239 }
0240 }
0241}
0242
0243function checkStatus() {
0244 var xhr = getXMLHttpRequest();
0245 xhr.onreadystatechange = function () {
0246 if (xhr.readyState == 4) {
0247 statusReceived(xhr);
0248 }
0249 };
0250 if (waitforit_url.indexOf("?") != -1) {
0251 var parts = waitforit_url.split("?");
0252 var base = parts[0];
0253 var qs = "?" + parts[1];
0254 } else {
0255 var base = waitforit_url;
0256 var qs = '';
0257 }
0258 var status_url = base + "status.json" + qs;
0259 xhr.open("GET", status_url);
0260 xhr.send(null);
0261}
0262
0263var percent_inner = null;
0264
0265function statusReceived(req) {
0266 if (req.status != 200) {
0267 var el = document.getElementById("error-box");
0268 el.innerHTML = req.responseText;
0269 return;
0270 }
0271 var status = eval("dummy="+req.responseText);
0272 if (typeof status.done == "undefined") {
0273 // Something went wrong
0274 var el = document.getElementById("error-box");
0275 el.innerHTML = req.responseText;
0276 return;
0277 }
0278 if (status.done) {
0279 window.location.href = window.location.href + "&send";
0280 return;
0281 }
0282 if (status.progress.message) {
0283 var el = document.getElementById("progress-box");
0284 el.innerHTML = status.progress.message;
0285 }
0286 if (status.progress.percent) {
0287 if (! percent_inner) {
0288 var outer = document.createElement("div");
0289 outer.setAttribute("id", "percent-container");
0290 percent_inner = document.createElement("div");
0291 percent_inner.setAttribute("id", "percent-inner");
0292 //percent_inner.innerHTML = " ";
0293 outer.appendChild(percent_inner);
0294 var parent = document.getElementById("percent-box");
0295 parent.appendChild(outer);
0296 }
0297 percent_inner.style.width = ""+Math.round(status.progress.percent) + "%";
0298 }
0299 setTimeout("checkStatus()", poll_time*1000);
0300}
0301'''
0302
0303CSS = '''\
0304body {
0305 font-family: sans-serif;
0306}
0307div#percent-container {
0308 border: 1px solid #000;
0309 width: 100%;
0310 height: 20px;
0311}
0312div#percent-inner {
0313 background-color: #999;
0314 height: 100%;
0315}
0316'''
0317
0318TEMPLATE = TEMPLATE.replace('<<JAVASCRIPT>>', JAVASCRIPT);
0319TEMPLATE = TEMPLATE.replace('<<CSS>>', CSS);