Module npsgd_web
[hide private]
[frames] | no frames]

Source Code for Module npsgd_web

  1  #!/usr/bin/python 
  2  # Author: Thomas Dimson [tdimson@gmail.com] 
  3  # Date:   January 2011 
  4  # For distribution details, see LICENSE 
  5  """Server for processing client (web-site) requests to NPSGD. 
  6   
  7  This module is meant to be run as a daemon process in conjunction with npsgd_queue 
  8  and npsgd_web. It acts as an HTTP server that serves up html for models  
  9  (e.g. http://localhost:8000/model/example) and can be proxied behind a server 
 10  or run as a web service. 
 11  """ 
 12  import os 
 13  import sys 
 14  import time 
 15  import logging 
 16  import functools 
 17  import threading 
 18  import tornado.web 
 19  import tornado.ioloop 
 20  import tornado.escape 
 21  import tornado.httpclient 
 22  import tornado.httpserver 
 23  import urllib 
 24  from optparse import OptionParser 
 25  from datetime import datetime 
 26   
 27  from npsgd import model_manager 
 28  from npsgd import model_parameters 
 29  from npsgd import ui_modules 
 30   
 31  from npsgd.model_manager import modelManager 
 32  from npsgd.model_task import ModelTask 
 33  from npsgd.model_parameters import ValidationError 
 34  from npsgd.config import config 
 35   
 36  #This is a simple cache so we don't overload the queue with unnecessary requests 
 37  #just to see it has workers 
 38  lastWorkerCheckSuccess = datetime(1,1,1) 
39 40 -class ClientModelRequest(tornado.web.RequestHandler):
41 """HTTP handler for all model related requests.""" 42 43 @tornado.web.asynchronous
44 - def get(self, modelName):
45 """Get handler to serve up the html for a specific model. 46 47 This method will either serve up an error if the queue is down or 48 the queue has no workers or return a nice view of the given model 49 using the model template. Requests to the queue are done 50 asynchronously to avoid blocking. 51 """ 52 global lastWorkerCheckSuccess 53 model = modelManager.getLatestModel(modelName) 54 55 td = datetime.now() - lastWorkerCheckSuccess 56 checkWorkers = (td.seconds + td.days * 24 * 3600) > config.keepAliveTimeout 57 58 if checkWorkers: 59 #check with queue to see if we have workers 60 http = tornado.httpclient.AsyncHTTPClient() 61 request = tornado.httpclient.HTTPRequest( 62 "http://%s:%s/client_queue_has_workers?secret=%s" % (config.queueServerAddress, config.queueServerPort, config.requestSecret), 63 method="GET") 64 callback = functools.partial(self.queueCallback, model=model) 65 http.fetch(request, callback) 66 else: 67 self.renderModel(model)
68
69 - def renderModel(self, model):
70 """Render the html associated with a given model object.""" 71 self.render(config.modelTemplatePath, model=model, errorText=None)
72
73 - def queueErrorRender(self, errorText):
74 self.render(config.modelErrorTemplatePath, errorText=errorText)
75
76 - def queueCallback(self, response, model=None):
77 """Asynchronous callback from the queue (for checking if it is up, or has workers). 78 79 This method will actually perform the rendering of the model if things 80 look good. 81 """ 82 global lastWorkerCheckSuccess 83 if response.error: 84 self.queueErrorRender("We are sorry. Our queuing server appears to be down at the moment, please try again later") 85 return 86 87 try: 88 json = tornado.escape.json_decode(response.body) 89 hasWorkers = json["response"]["has_workers"] 90 if hasWorkers: 91 lastWorkerCheckSuccess = datetime.now() 92 self.renderModel(model) 93 else: 94 self.queueErrorRender("We are sorry, our model worker machines appear to be down at the moment. Please try again later") 95 return 96 97 except KeyError: 98 logging.info("Bad response from queue server") 99 self.queueErrorRender("We are sorry. Our queuing server appears to be having issues communicating at the moment, please try again later") 100 return
101 102 @tornado.web.asynchronous
103 - def post(self, modelName):
104 """Post handler to actually create a model task with given parameters.""" 105 modelVersion = self.get_argument("modelVersion") 106 model = modelManager.getModel(modelName, modelVersion) 107 try: 108 task = self.setupModelTask(model) 109 except ValidationError, e: 110 logging.exception(e) 111 self.render(config.modelTemplatePath, model=model, errorText=str(e)) 112 return 113 except tornado.web.HTTPError, e: 114 logging.exception(e) 115 self.render(config.modelTemplatePath, model=model, errorText=None) 116 return 117 118 logging.info("Making async request to get confirmation number for task") 119 120 http = tornado.httpclient.AsyncHTTPClient() 121 request = tornado.httpclient.HTTPRequest( 122 "http://%s:%s/client_model_create" % (config.queueServerAddress, config.queueServerPort), 123 method="POST", 124 body=urllib.urlencode({ 125 "secret": config.requestSecret, 126 "task_json": tornado.escape.json_encode(task.asDict())})) 127 128 http.fetch(request, self.confirmationNumberCallback)
129
130 - def confirmationNumberCallback(self, response):
131 """Asynchronous callback when model run is populated to confirmation queue.""" 132 133 if response.error: raise tornado.web.HTTPError(500) 134 135 try: 136 json = tornado.escape.json_decode(response.body) 137 logging.info(json) 138 res = json["response"] 139 model = modelManager.getModel(res["task"]["modelName"], res["task"]["modelVersion"]) 140 task = model.fromDict(res["task"]) 141 code = res["code"] 142 except KeyError: 143 logging.info("Bad response from queue server") 144 raise tornado.web.HTTPError(500) 145 146 self.render(config.confirmTemplatePath, email=task.emailAddress, code=code)
147
148 - def setupModelTask(self, model):
149 email = self.get_argument("email") 150 151 paramDict = {} 152 for param in model.parameters: 153 try: 154 argVal = self.get_argument(param.name) 155 except tornado.web.HTTPError: 156 argVal = param.nonExistValue() 157 158 value = param.withValue(argVal) 159 paramDict[param.name] = value.asDict() 160 161 task = model(email, 0, paramDict) 162 return task
163
164 165 -class ClientBaseRequest(tornado.web.RequestHandler):
166 """Index response handler (e.g. http://localhost:8000): do nothing for now.""" 167
168 - def get(self):
169 self.write("Find a model!")
170
171 -class ClientConfirmRequest(tornado.web.RequestHandler):
172 """HTTP confirmation code handler. 173 174 Requests to this server proxy to the queue for actual confirmation code handling. 175 The queue request is called asynchronously. 176 """ 177 178 @tornado.web.asynchronous
179 - def get(self, confirmationCode):
180 http = tornado.httpclient.AsyncHTTPClient() 181 request = tornado.httpclient.HTTPRequest( 182 "http://%s:%s/client_confirm/%s?secret=%s" % (config.queueServerAddress, config.queueServerPort, \ 183 confirmationCode, config.requestSecret)) 184 185 logging.info("Asyncing a confirmation: '%s'", confirmationCode) 186 http.fetch(request, self.confirmationCallback)
187
188 - def confirmationCallback(self, response):
189 if response.error: raise tornado.web.HTTPError(404) 190 191 json = tornado.escape.json_decode(response.body) 192 res = json["response"] 193 194 if res == "okay": 195 self.render(config.confirmedTemplatePath) 196 elif res == "already_confirmed": 197 self.render(config.alreadyConfirmedTemplatePath) 198 else: 199 logging.info("Bad response from queue server: %s", res) 200 raise tornado.web.HTTPError(500)
201
202 203 -def setupClientApplication():
204 appList = [ 205 (r"/", ClientBaseRequest), 206 (r"/confirm_submission/(\w+)", ClientConfirmRequest), 207 (r"/models/(.*)", ClientModelRequest) 208 ] 209 210 settings = { 211 "static_path": os.path.join(os.path.dirname(__file__), "static"), 212 "ui_modules": ui_modules, 213 "template_path": config.htmlTemplateDirectory 214 } 215 216 return tornado.web.Application(appList, **settings)
217
218 219 -def main():
220 parser = OptionParser() 221 parser.add_option('-c', '--config', dest='config', 222 help="Config file", default="config.cfg") 223 parser.add_option('-p', '--client-port', dest='port', 224 help="Http port (for serving html)", default=8000, type="int") 225 parser.add_option('-l', '--log-filename', dest='log', 226 help="Log filename (use '-' for stderr)", default="-") 227 228 (options, args) = parser.parse_args() 229 230 config.loadConfig(options.config) 231 config.setupLogging(options.log) 232 model_manager.setupModels() 233 model_manager.startScannerThread() 234 235 clientHTTP = tornado.httpserver.HTTPServer(setupClientApplication()) 236 clientHTTP.listen(options.port) 237 238 logging.info("NPSGD Web Booted up, serving on port %d", options.port) 239 print >>sys.stderr, "NPSGD web server running on port %d" % options.port 240 241 tornado.ioloop.IOLoop.instance().start()
242 243 if __name__ == "__main__": 244 main() 245