/webserver.py
Python | 619 lines | 534 code | 13 blank | 72 comment | 32 complexity | 20d3019b2b19cc082b86fa46ee583089 MD5 | raw file
- #!/usr/bin/env python
- ## ==================================
- ## === packages, modules, pragmas ===
- ## ==================================
- ## === built-ins ===
- import cgi
- import sys
- import os # used to obtain environment host name & process web file paths
- import argparse # used when running as script; should be moved to DAO
- import logging
- import logging.config # pythons logging feature
- from mimetypes import types_map # for processing additional files
- from http.server import BaseHTTPRequestHandler, HTTPServer
- # ## === 3rd party ===
- from sqlalchemy import create_engine
- from sqlalchemy.orm import sessionmaker
- from database_setup import Restaurant, MenuItem, Employee, Address
- # from database_setup import Base, Restaurant, MenuItem, Employee, Address
- # import pymongo
- # import json
- # from tornado.ioloop import IOLoop
- # #from tornado.log import enable_pretty_logging
- # #import tornado.options
- # from tornado.web import RequestHandler, Application, url, RedirectHandler, StaticFileHandler
- # from tornado.httpclient import AsyncHTTPClient
- # from tornado.escape import json_decode
- # from tornado import gen
- # #import tornado.logging
- # #import tornado.web.authenticated (decorator for ensuring user is logged in)
- #
- # ## === custom ===
- # import restaurant as r
- # import util
- # import rplace
- #
- ## ===============================
- ## === command-line processing ===
- ## ===============================
- parser = argparse.ArgumentParser()
- parser.add_argument('--port', default=8080, help="sets port number for web service")
- parser.add_argument('--log_file', default='log/web.log', help="path/file for logging")
- parser.add_argument('--start', dest='start', action='store_true', help="start the server")
- parser.add_argument('--app', default='restaurant_app', help="name of application")
- parser.add_argument('--debug', dest='debug', action='store_true', help="sets server debug, and level of logging")
- #parser.add_argument('--debug_off', dest='debug', action='store_false', help="sets server debug")
- #parser.add_argument('--mongo_server', default=2, help="server where mongodb resides")
- #parser.add_argument('--i_mode', dest='start', action='store_false', help="skip starting the server")
- args = parser.parse_args()
- def pargs():
- '''prints items in args object for ease of reading'''
- print('\n')
- for item in args._get_kwargs():
- k,v = item
- print('\t' + k + ': ' + str(v))
- ## ===================================
- ## === logging to file and console ===
- ## ===================================
- ERROR_FORMAT = "%(asctime)s %(name)s %(levelname)-8s %(message)s"
- INFO_FORMAT = "%(asctime)s %(name)s %(levelname)-8s %(message)s"
- CONSOLE_FORMAT = "\n\t%(message)s"
- if args.debug == True:
- DEBUG_FORMAT = "%(asctime)s %(name)s %(levelname)-8s %(filename)s->%(funcName)s line %(lineno)d: %(message)s"
- LOG_LEVEL = "DEBUG"
- else:
- DEBUG_FORMAT = INFO_FORMAT
- LOG_LEVEL = "INFO"
- LOG_CONFIG = {'version':1,
- 'formatters':{'error':{'format':ERROR_FORMAT},
- 'info':{'format':INFO_FORMAT},
- 'console':{'format':CONSOLE_FORMAT},
- 'debug':{'format':DEBUG_FORMAT}},
- 'handlers':{'console':{'class':'logging.StreamHandler',
- 'formatter':'console',
- 'level':logging.DEBUG},
- 'file':{'class':'logging.FileHandler',
- 'filename':args.log_file,
- 'formatter':'debug',
- 'level':logging.INFO}},
- 'root':{'handlers':['console', 'file'], 'level':LOG_LEVEL}}
- logging.config.dictConfig(LOG_CONFIG)
- logger = logging.getLogger(args.app)
- # ## =============
- # ## == classes ==
- # ## =============
- class DB(object):
- '''custom class for loading menu items from json'''
- def __init__(self, db = 'sqlite:///restaurant.db'):
- self.engine = create_engine(db)
- Base.metadata.bind = self.engine # is this really used?
- self.DBSession = sessionmaker(bind=self.engine)
- self.session = self.DBSession()
- # self.session = sessionmaker(bind=self.engine)
- def loadJSON(self, file='menu_items.json'):
- '''bulk upload menu items using JSON file'''
- self.items = util.readFile(file)
- for item in self.items:
- self.loadItem(item)
- def loadItem(self, menu):
- '''create/add and commit items to db
- TODO: make this a generic loader for any underlying db table'''
- restaurant = Restaurant(name = menu['restaurant']) # TODO: read attributes from JSON entry to determine type
- self.session.add(restaurant)
- self.session.commit()
- for item in menu['menu_items']:
- menu_item = MenuItem(name = item['name'], desc = item['desc'], price = item['price'], course = item['course'], restaurant = restaurant)
- self.session.add(menu_item)
- self.session.commit()
- def getMenuItems(self, name = None, id = None):
- '''method for fetching items from db'''
- if name != None:
- items = self.session.query(MenuItem).filter_by(name = name)
- for item in items:
- print(item.id, item.price, item.restaurant.name, item.name)
- print('thats all folks...')
- if id != None:
- item = self.session.query(MenuItem).filter_by(id = id).one()
- self.result = item
- class webserverHandler(BaseHTTPRequestHandler):
- def do_GET(self):
- self.cwd = os.environ['PWD']
- try:
- if self.path == "/":
- self.path = "/index.html"
- fname,ext = os.path.splitext(self.path)
- if ext in (".ico", ".jpg",".png"): # binary files (images)
- with open('.'+self.path, 'rb') as f:
- self.send_response(200)
- self.send_header('Content-type', types_map[ext])
- self.end_headers()
- self.wfile.write(f.read())
- if ext in (".html", ".css",".js"): # text files only, not binary (images)
- with open('.'+self.path) as f:
- self.send_response(200)
- self.send_header('Content-type', types_map[ext])
- self.end_headers()
- self.wfile.write(bytes(f.read(), 'utf-8'))
- if self.path.endswith("/hello"):
- self.send_response(200)
- self.send_header('Content-type', 'text/html')
- self.end_headers()
- ho = htmlizer()
- ho.title = 'Hello form'
- ho.head = 'Hello'
- ho.body = 'some text here'
- ho.foot = 'little test footnote'
- ho.prompt = 'what should I say?'
- ho.action = '/hello'
- ho.renderForm()
- output = bytes(ho.__repr__(), 'utf-8')
- self.wfile.write(output)
- # logger.debug(output)
- return
- if self.path.endswith("/hola"):
- self.send_response(200)
- self.send_header('Content-type', 'text/html')
- self.end_headers()
- ho = htmlizer()
- ho.title = 'Hola form'
- ho.head = '¡Hola <a href = "/hello" ">Back to Hello</a>'
- ho.body = 'some text here'
- ho.foot = 'little test footnote'
- ho.prompt = 'what should I say?'
- ho.action = '/hola'
- ho.renderForm()
- output = bytes(ho.__repr__(), 'utf-8')
- self.wfile.write(output)
- # logger.debug(output)
- return
- if self.path.endswith("/restaurants"):
- self.send_response(200)
- self.send_header('Content-type', 'text/html')
- self.end_headers()
- ho = htmlizer()
- ho.renderRest(rest)
- output = bytes(ho.__repr__(), 'utf-8')
- self.wfile.write(output)
- # logger.debug(output)
- return
- if self.path.endswith("/restaurants/new"):
- self.send_response(200)
- self.send_header('Content-type', 'text/html')
- self.end_headers()
- ho = htmlizer()
- ho.title = 'New Restaurant'
- ho.head = 'Make a New Restaurant'
- ho.body = 'please enter restaurant name'
- ho.foot = 'new restaurant form'
- ho.prompt = "enter name"
- ho.action = '/restaurants/new'
- ho.placeholder = 'new restaurant name'
- ho.name = 'NewRestaurantName'
- ho.value = 'Create'
- ho.renderForm()
- # logger.debug('do_GET' + ho.__repr__())
- output = bytes(ho.__repr__(), 'utf-8')
- self.wfile.write(output)
- return
- # if self.path.endswith(r"/restaurants/([0-9]+)/edit"):
- if self.path.endswith("/edit"):
- rest_ID = self.path.split('/')[-2]
- curRest = rest.filter_by(id=rest_ID).one()
- self.send_response(200)
- self.send_header('Content-type', 'text/html')
- self.end_headers()
- ho = htmlizer()
- ho.title = curRest.name
- ho.head = 'Restaurant: {0}'.format(curRest.name)
- ho.body = 'update restaurant name'
- ho.foot = 'update restaurant form'
- ho.prompt = "enter name"
- ho.action = '/restaurants/{0}/edit'.format(rest_ID)
- ho.placeholder = 'updated restaurant name'
- ho.name = 'NewRestaurantName'
- ho.value = 'Update'
- ho.renderForm()
- output = bytes(ho.__repr__(), 'utf-8')
- self.wfile.write(output)
- logger.debug(output)
- return
- if self.path.endswith("/delete"):
- rest_ID = self.path.split('/')[-2]
- curRest = rest.filter_by(id=rest_ID).one()
- self.send_response(200)
- self.send_header('Content-type', 'text/html')
- self.end_headers()
- ho = htmlizer()
- ho.title = curRest.name
- ho.head = 'Delete Restaurant: {0}'.format(curRest.name)
- ho.body = 'Delete Restaurant'
- ho.foot = 'delete restaurant form'
- ho.prompt = "delete restaurant?"
- ho.action = '/restaurants/{0}/delete'.format(rest_ID)
- # ho.placeholder = 'updated restaurant name'
- # ho.name = 'NewRestaurantName'
- ho.value = 'Delete'
- ho.renderForm()
- output = bytes(ho.__repr__(), 'utf-8')
- self.wfile.write(output)
- logger.debug(output)
- return
- if self.path.endswith("/restaurant_list"):
- self.send_response(200)
- self.send_header('Content-type', 'text/html')
- self.end_headers()
- rest_list = sorted([item.name for item in rest])
- output = bytes(htmlizer(list = rest_list, title='Restaurant List', head='List of Restaurants', body='additional body text...', foot='end of list').__repr__(), 'utf-8')
- self.wfile.write(output)
- # logger.debug(output)
- return
- except IOError:
- self.send_error(404, "File Not Found %s" % self.path)
- def do_POST(self):
- try:
- if self.path.endswith("/restaurants/new"):
- ctype, pdict = cgi.parse_header(self.headers.get('content-type'))
- logger.debug('do_POST (ctype): ' + str(ctype))
- pdict['boundary'] = pdict['boundary'].encode('utf-8')
- if ctype == 'multipart/form-data':
- fields=cgi.parse_multipart(self.rfile, pdict)
- messagecontent = fields.get('NewRestaurantName')
- restName = messagecontent[0].decode('ascii')
- logger.debug('do_POST (restName): ' + restName)
- newRestaurant = Restaurant(name=restName)
- session.add(newRestaurant)
- session.commit()
- self.send_response(200)
- # self.send_response(301)
- # self.send_header('Content-type', 'text/html')
- # self.send_header('Location', '/restaurants')
- self.end_headers()
- ho = htmlizer()
- ho.title = 'New Restaurant'
- ho.head = 'Restaurant Added'
- ho.body = restName
- ho.foot = '<a href = "/restaurants">Back to Restaurants</a>'
- ho.renderHTML()
- output = bytes(ho.__repr__(), 'utf-8')
- self.wfile.write(output)
- return
- if self.path.endswith("/edit"):
- rest_ID = self.path.split('/')[-2]
- curRest = rest.filter_by(id=rest_ID).one()
- ctype, pdict = cgi.parse_header(self.headers.get('content-type'))
- logger.debug('do_POST (ctype): ' + str(ctype))
- pdict['boundary'] = pdict['boundary'].encode('utf-8')
- if ctype == 'multipart/form-data':
- fields=cgi.parse_multipart(self.rfile, pdict)
- messagecontent = fields.get('NewRestaurantName')
- newRestName = messagecontent[0].decode('ascii')
- logger.debug('do_POST (restName): ' + newRestName)
- curRest.name = newRestName
- session.add(curRest)
- session.commit()
- self.send_response(200)
- # self.send_response(301)
- # self.send_header('Content-type', 'text/html')
- # self.send_header('Location', '/restaurants')
- self.end_headers()
- ho = htmlizer()
- ho.title = 'Edit Restaurant'
- ho.head = 'Restaurant Name Updated'
- ho.body = newRestName
- ho.foot = '<a href = "/restaurants">Back to Restaurants</a>'
- ho.renderHTML()
- output = bytes(ho.__repr__(), 'utf-8')
- self.wfile.write(output)
- return
- if self.path.endswith("/delete"):
- rest_ID = self.path.split('/')[-2]
- curRest = rest.filter_by(id=rest_ID).one()
- # ctype, pdict = cgi.parse_header(self.headers.get('content-type'))
- # logger.debug('do_POST (ctype): ' + str(ctype))
- # pdict['boundary'] = pdict['boundary'].encode('utf-8')
- # if ctype == 'multipart/form-data':
- # fields=cgi.parse_multipart(self.rfile, pdict)
- # messagecontent = fields.get('NewRestaurantName')
- # newRestName = messagecontent[0].decode('ascii')
- # logger.debug('do_POST (restName): ' + newRestName)
- # curRest.name = newRestName
- # session.add(curRest)
- session.delete(curRest)
- session.commit()
- self.send_response(200)
- # self.send_response(301)
- # self.send_header('Content-type', 'text/html')
- # self.send_header('Location', '/restaurants')
- self.end_headers()
- ho = htmlizer()
- ho.title = 'Restaurant Deleted'
- ho.head = 'Restaurant Deleted'
- ho.body = curRest.name
- ho.foot = '<a href = "/restaurants">Back to Restaurants</a>'
- ho.renderHTML()
- output = bytes(ho.__repr__(), 'utf-8')
- self.wfile.write(output)
- return
- if self.path.endswith("/hello"):
- # if self.path.endswith("/hello") or self.path.endswith("/hola"):
- self.send_response(301)
- self.end_headers()
- ctype, pdict = cgi.parse_header(self.headers.get('content-type'))
- logger.debug(type(ctype), ctype)
- logger.debug(type(pdict), pdict)
- pdict['boundary'] = pdict['boundary'].encode('utf-8')
- if ctype == 'multipart/form-data':
- fields=cgi.parse_multipart(self.rfile, pdict)
- messagecontent = fields.get('message')
- ho = htmlizer()
- ho.title = 'Echo'
- ho.head = 'How about this...?'
- ho.body = messagecontent[0].decode('ascii')
- ho.foot = 'echo messages'
- ho.prompt = 'what else should I say?'
- ho.action = '/hello'
- ho.placeholder = 'message'
- ho.name = 'message'
- ho.value = 'Submit'
- ho.renderForm()
- output = bytes(ho.__repr__(), 'utf-8')
- self.wfile.write(output)
- return
- except IOError:
- logger.exception("IOError", exc_info=1)
- pass
- class htmlizer(object):
- '''to structure html page'''
- title = 'test_title'
- head = 'header_text'
- body = 'body_text'
- foot = 'footer_stuff'
- list = None
- prompt = None
- action = None
- placeholder = 'message'
- name = None
- value = 'Submit'
- form = ''
- doc = ''
- def __init__(self):
- pass
- def __repr__(self):
- '''method for generating page'''
- return self.doc
- def renderList(self, list):
- '''simple list replaced body'''
- self.body = str('<br>'.join(list))
- self.renderHTML()
- def renderRest(self, rest):
- '''use restaurant_id as key for edit and delete replaces body'''
- self.title='Restaurants'
- self.head='List of Restaurants'
- self.foot='<a href = "/restaurants/new">Add new restaurant</a>'
- self.rest_list = []
- for item in rest:
- self.rest_list.append('{0} <a href="/restaurants/{1}/edit">edit</a> <a href="/restaurants/{1}/delete">delete</a>'.format(item.name, str(item.id)))
- self.body = '<br>'.join(sorted(self.rest_list))
- # self.body = ""
- # for item in rest:
- # self.body += '<br>{0} <a href="/restaurants/{1}/edit">edit</a> <a href="/restaurants/{1}/delete">delete</a>'.format(item.name, str(item.id))
- self.renderHTML()
- def renderForm(self):
- '''form appended to body'''
- self.body += '<form method="POST" enctype="multipart/form-data" action="{0}">'.format(self.action)
- if self.name != None:
- self.body += '<br><input name="{0}" type="text" placeholder="{1}">'.format(self.name, self.placeholder)
- self.body += '<input type="submit" value="{0}"></form>'.format(self.value)
- # self.body += str(
- # '<form method="POST" enctype="multipart/form-data" ' +
- # 'action="%s"><br><input name="%s" type="text" placeholder="%s">' % (self.action, self.name, self.placeholder) +
- # '<input type="submit" value="%s"></form>' % self.value)
- self.renderHTML()
- def renderHTML(self):
- self.doc = str(
- '<!DOCTYPE html>' +
- '<meta charset="utf-8">' +
- '<html>' +
- '<head>' +
- ' <title>' + self.title + '</title>' +
- ' <link rel="icon" href="/image/favicon.ico">' +
- ' <link rel="stylesheet" type="text/css" href="/css/bootstrap.min.css" />' +
- ' <link rel="stylesheet" type="text/css" href="/css/restaurant.css" />' +
- '</head>' +
- '<body>' +
- ' <div class="container-fluid">' +
- self.head +
- ' <p>' +
- self.body +
- ' </p>' +
- ' <script src="/js/jquery.min.js"></script>' +
- ' <script src="/js/bootstrap.min.js"></script>' +
- ' </div>' +
- '</body>' +
- ' <div class="container-fluid">' +
- self.foot +
- ' </div>' +
- '</html>')
- def renderTable(self, dict):
- pass
- # <table class="table table-condensed">
- # <thead>
- # <tr>
- # <th>'Restaurant'</th>
- # <th>QF Name</th>
- # <th>Versions</th>
- # </tr>
- # </thead>
- # {%for model in models%}
- # <tbody>
- # <tr>
- # <td>{{model['class']}}</td>
- # <td>{{model['qf_name']}}</td>
- # <td>{{model['versions']}}</td>
- # </tr>
- # </tbody>
- # {%end%}
- # </table>
- # class Server(object):
- # '''server object for assembling model objects'''
- # freshness = 3600
- # def __init__(self, db=args.mongo_server):
- # args.mongo_server = db or args.mongo_server
- # # self.db = args.mongo_server
- # self.r = rplace.RPLACE(args.mongo_server)
- # self.h = html_obj()
- # self.refreshed = 0
- # self.refresh()
- # self.r.setChildNodes()
- # self.r.setMiniNodes()
- # self.setRouteTable()
- # if args.start == False: pargs()
- # def load(self):
- # self.r.setObjViv()
- # def refresh(self, force=False):
- # '''refresh models from DAO, store in sorted order based on sort keys'''
- # print(util.logger('IO read') + 'refreshing server objects...')
- # force = bool(force)
- # if force == True or self.refreshed < int(util.strftime('%s',util.localtime())) - self.freshness:
- # self.load()
- # self.refreshed = int(util.strftime('%s',util.localtime()))
- # print(util.logger('Status') + 'server objects last refreshed: ' + str(util.strftime('%Y-%m-%d %H:%M:%S',util.localtime(self.refreshed))))
- # logger.error('server objects last refreshed: ' + str(util.strftime('%Y-%m-%d %H:%M:%S',util.localtime(self.refreshed))))
- # return
- # def getRefreshedTime(self): return util.displayTime(option=1, value=self.refreshed)
- # def setRouteTable(self):
- # self.route_table = Application([
- # # -- pages --#
- # url(r"/", MainHandler),
- # url(r"/(robots.txt|sitemap.xml)", StaticFileHandler, {"path": "admin/"}),
- # url(r"/objectives", ObjectiveHandler),
- # # -- data --#
- # url(r"/data/(.*)", StaticFileHandler, {"path": "data/"}),
- # url(r"/objective_json", ObjectiveJSONHandler),
- # url(r"/mini_nodes", MiniNodeHandler),
- # url(r"/obj_nodes", ObjectiveNodeHandler),
- # # -- files --#
- # url(r"/html/(.*)", StaticFileHandler, {"path": "html/"}),
- # url(r"/fonts/(.*)", StaticFileHandler, {"path": "fonts/"}),
- # url(r"/image/(.*)", StaticFileHandler, {"path": "image/"}),
- # url(r"/css/(.*)", StaticFileHandler, {"path": "css/"}),
- # url(r"/js/(.*)", StaticFileHandler, {"path": "js/"}),
- # # -- test --#
- # url(r"/app", RedirectHandler, dict(url="http://itunes.apple.com/my-app-id")),
- # url(r"/co", CoHandler),
- # url(r"/api/", ApiHandler),
- # url(r"/hello", HelloHandler),
- # url(r"/hello_body", HelloBodyHandler),
- # url(r"/myform", FormHandler),
- # url(r"/static/(.*)", StaticFileHandler, {"path": "html/"}),
- # url(r"/example/([0-9]+)", ExampleHandler, dict(db=None), name="example"),
- # url(r"/story/([0-9]+)", StoryHandler, dict(db=None), name="story"),
- # url(r"/template", TemplateHandler),
- # url(r"/template_html", TemplateHandler, {"path": "html/"}),
- # # url(r"/photos/(.*)", MyPhotoHandler),
- # # url(r"/pictures/(.*)", tornado.web.RedirectHandler, dict(url=r"/photos/\1")),
- # ],debug=args.debug)
- #
- # ## =====================
- # ## == handler classes ==
- # ## =====================
- #
- # class MainHandler(RequestHandler):
- # '''main route'''
- # def get(self):
- # self.render('html/index.html')
- # # self.write('<a href="%s">link to story 1</a>' % self.reverse_url("story", "1"))
- #
- # class ObjectiveHandler(RequestHandler):
- # '''starburst route'''
- # def get(self):
- # self.render('html/sunburst.html')
- #
- # class ObjectiveJSONHandler(RequestHandler):
- # '''autoviv derived json built from objectives'''
- # def get(self):
- # self.write(s.r.objective_viv)
- #
- # class MiniNodeHandler(RequestHandler):
- # '''children node derived json built from objectives'''
- # def get(self):
- # self.write(s.r.obj_mini)
- #
- # class ObjectiveNodeHandler(RequestHandler):
- # '''children node derived json built from objectives'''
- # def get(self):
- # self.write(s.r.obj_children)
- ## ====================
- ## == db connection ==
- ## ===================
- try:
- session = sessionmaker()(bind=create_engine('sqlite:///restaurant.db'))
- # engine = create_engine('sqlite:///restaurant.db') # multi-line approach
- # Base.metadata.bind = engine
- # DBSession = sessionmaker(bind=engine) # can bind when creating sessionmaker instance
- # session = DBSession()
- # DBSession = sessionmaker() # or after
- # session = DBSession(bind=engine)
- emp = session.query(Employee) # load global objects for convenience
- rest = session.query(Restaurant)
- addr = session.query(Address)
- mi = session.query(MenuItem)
- # db = DB() # prefer to put in a class
- # emp = db.session.query(Employee)
- # rest = db.session.query(Restaurant)
- # addr = db.session.query(Address)
- # mi = db.session.query(MenuItem)
- except:
- logger.exception("issue loading restaurant database items", exc_info=1)
- ## ====================================================
- ## == create server object and run bottle -> tornado ==
- ## ====================================================
- def main():
- try:
- server = HTTPServer(('', args.port), webserverHandler)
- log_info = "HTTPServer started " + os.environ['HOSTNAME'] + ':' + str(args.port) + ' debug=' + str(args.debug) + ' log_file=' + str(args.log_file)
- logger.info(log_info)
- server.serve_forever()
- except KeyboardInterrupt:
- logger.info("HTTPServer stopped via control-C")
- server.socket.close() # server not in scope...
- except OSError:
- logger.exception("HTTPServer server already running?", exc_info=1)
- except:
- logger.exception("hmmm, we have a problem", exc_info=1)
- # server.socket.close() # server not in scope...
- if args.start == True:
- #if __name__== '__main__':
- main()
-
- # def main
- # if args.start == True:
- # global s
- # s = Server(db=args.mongo_server)
- # logger.info('Tornado server running on: ' + os.environ['HOSTNAME'] + ':' + str(args.port) + ' debug = ' + str(args.debug))
- # s.route_table.listen(args.port)
- # IOLoop.current().start()
- # enable_pretty_logging()