3 '''mudpuppy httpd API interface
5 This provides a very, very lightweight WSGI based http server
6 to expose a JSON based API.
8 This is by no means the only way to interact with make magic
9 from the outside world, but it is a convenient way to make a
10 solid point of demarcation, and give something like mudpuppy
13 There is NO user security implemented here. If you want some
14 (and you really, really do), use your favourite web server's
15 WSGI interface rather than cherrypy's own one
24 from contextlib import contextmanager
26 def expose_json(func):
27 '''decorate to set Content-Type on return to application/json and to expose to cherrypy'''
28 def wrapper(*argc,**argd):
29 cherrypy.response.headers['Content-Type'] = 'application/json'
30 return func(*argc,**argd)
31 wrapper.exposed = True
36 '''propogate KeyErrors and ValueErrors as HTTP errors'''
39 except KeyError as err:
40 raise cherrypy.HTTPError(404, str(err)) # Resource not found
41 except ValueError as err:
42 raise cherrypy.HTTPError(400, str(err)) # Bad request
44 def simple_error_page(status, message, traceback, version):
45 return '{"error": "%s", "message": "%s"}' % (status, message)
46 def error_page(status, message, traceback, version):
47 '''simple error page for HTTP service rather than default overblown HTML one'''
48 return '{"error": "%s", "message": "%s",\n"traceback": "%s"}' % (status, message,traceback)
50 cherrypy.config.update({'error_page.400': simple_error_page,
51 'error_page.404': simple_error_page,
52 'error_page.405': simple_error_page,
53 'error_page.default': error_page
60 if cherrypy.request.method == 'GET':
62 return json.dumps(self.magic.get_tasks(),indent=1)
63 raise cherrypy.HTTPError(405)
67 if cherrypy.request.method == 'POST':
68 # TODO: ANY sort of taint checking
70 taskdata = json.load(cherrypy.request.body)
71 task = self.magic.create_task(taskdata)
72 return json.dumps(task,indent=1)
73 raise cherrypy.HTTPError(405)
76 def default(self, uuid, *args):
77 # TODO: replace this horrible, horrible spagetti
80 if cherrypy.request.method == 'GET':
81 with http_resource(): # Show the task
82 return json.dumps(self.magic.get_task(uuid), indent=1)
83 elif cherrypy.request.method == 'DELETE':
84 with http_resource(): # wipe task
85 self.magic.delete_task(uuid)
87 else: raise cherrypy.HTTPError(405) # Invalid method
89 if args[0] == 'available':
90 # return items that we can do now
91 if cherrypy.request.method == 'GET':
93 return json.dumps(self.magic.ready_to_run(uuid), indent=1)
94 else: raise cherrypy.HTTPError(405) # Invalid method
95 elif args[0] == 'metadata':
96 if cherrypy.request.method == 'GET':
98 return json.dumps(self.magic.get_metadata(uuid), indent=1)
99 elif cherrypy.request.method == 'POST':
100 with http_resource():
101 updatedata = json.load(cherrypy.request.body)
102 return json.dumps(self.magic.update_task_metadata(uuid,updatedata), indent=1)
104 # Nothing so simple as a single task.
105 args = dict(zip(['itemname','attrib'],args))
108 # Handle attribute on the item (only state so far)
109 if args['attrib'] == 'state':
110 if cherrypy.request.method == 'GET':
111 with http_resource():
112 return self.magic.get_item(uuid,args['itemname'])['state']
113 raise cherrypy.HTTPError(405)
115 if cherrypy.request.method == 'GET':
116 with http_resource():
117 return json.dumps(self.magic.get_item(uuid,args['itemname']), indent=1)
118 elif cherrypy.request.method == 'POST':
119 # Update stuff in the item
120 with http_resource():
121 updatedata = json.load(cherrypy.request.body)
122 return json.dumps(self.magic.update_item(uuid,args['itemname'],updatedata), indent=1)
123 raise cherrypy.HTTPError(405)
128 cherrypy.response.headers['Content-Type'] = 'text/plain'
129 return '''make magic httpd API is running and happy
130 /task/ GET: list tasks
131 /task/ POST: create new task (takes { 'requirements': [] } at minimum)
132 /task/uuid/ GET: show task
135 def get_cherrypy_root(magiclib):
136 '''return the root object to be given to cherrypy
138 also defines which URLs work. Fun times :)
143 # Allow the objects to access magic
144 # this may not be the optimal way to do it; might want to start more
145 # instances so everything isn't using the same Store connection
146 root.magic = magiclib
147 root.task.magic = magiclib
152 magiclib = lib.magic.Magic()
153 cpconfig = {'global': {'server.socket_host': config.httpd_listen_address, 'server.socket_port': config.httpd_listen_port}}
154 cherrypy.quickstart(get_cherrypy_root(magiclib), config=cpconfig)
156 if __name__ == '__main__':