From fe8898bf3df4cf1dc57d7a5c507070dadd45d831 Mon Sep 17 00:00:00 2001 From: jobe Date: Sun, 29 Sep 2019 21:58:23 +0200 Subject: [PATCH] Enabled command invocation via HTTP GET request --- README.md | 14 +++++++ raspend.pyproj | 2 +- raspend/http.py | 68 +++++++++++++++++++++++++++++---- raspend/utils/commandmapping.py | 4 +- setup.py | 2 +- 5 files changed, 79 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 7483be2..43cd3e3 100644 --- a/README.md +++ b/README.md @@ -197,6 +197,20 @@ and invokes the method. The response of this HTTP POST request will be your JSON ``` Since remain untouched, you can attach any other information with that command such as an element-id of your frontend invoking it. +Starting with version 1.1.0, you can also use HTTP GET requests to invoke commands. The request has to following structure: + +``` + /cmd?name=command&arg1=val1&arg2=val2...&argN=valN +``` + +So for the above mentioned example **theDoorBell.switchDoorBell**, the correct request would be: + +``` + /cmd?name=theDoorBell.switchDoorBell&onoff=off +``` + +The **RaspendHTTPServerThread** invokes the command and responds with the result of the invocation as a JSON string. + ## How to install? Make sure you have installed Python 3.5 or higher. I've tested the package on my Raspberry Pi 3 Model B+ running **Raspbian GNU/Linux 9 (stretch)** with Python 3.5.3 installed. diff --git a/raspend.pyproj b/raspend.pyproj index e81e7d1..ec7e824 100644 --- a/raspend.pyproj +++ b/raspend.pyproj @@ -5,7 +5,7 @@ 2.0 {d0fd4413-717c-4da2-b189-4cc8bdecbc68} - example2.py + example1.py . . diff --git a/raspend/http.py b/raspend/http.py index 4c84fe5..c3132b7 100644 --- a/raspend/http.py +++ b/raspend/http.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # # The HTTP request handling interface for raspend. -# 'raspend' stands for RaspberryPi EndPoint. +# 'raspend' stands for RaspberryPi Backend. # # License: MIT # @@ -11,6 +11,7 @@ from functools import partial from http.server import BaseHTTPRequestHandler import json +import urllib from .utils import stoppablehttpserver @@ -181,6 +182,49 @@ def onGetCmds(self): self.dataLock.release() return strJsonResponse + def onGetCmd(self, queryParams): + """ Call a command via HTTP GET. The response will be the command's return value as a JSON string. + """ + strJsonResponse = "" + + cmdName = queryParams["name"][0] + cmdArgs = dict() + + cmd = self.commandMap.get(cmdName) + + if cmd == None: + self.send_error(404, ("Command '{0}' not found!".format(cmdName))) + return + + del (queryParams["name"]) + + for k,v in queryParams.items(): + cmdArgs[k] = v[0] + + result = "" + + try: + result = cmd.execute(cmdArgs) + except Exception as e: + self.send_error(500, "An unexpected error occured during execution of '{0}'! Exception: {1}".format(cmdName, e)) + return + + strJsonResponse = json.dumps(result, ensure_ascii=False) + + try: + self.send_response(200) + self.send_header('Content-type', 'application/json; charset=utf-8') + self.send_header('Access-Control-Allow-Origin', '*') + self.end_headers() + self.wfile.write(bytes(strJsonResponse, 'utf-8')) + except OSError: + self.send_error(500) + except BlockingIOError: + self.send_error(500) + except Exception as e: + self.send_error(500, "An unexpected error occured during execution of '{0}'! Exception: {1}".format(cmdName, e)) + return + def do_GET(self): """ Handle HTTP GET request @@ -188,21 +232,31 @@ def do_GET(self): '/data/key' : returns sub-element of 'dataDict' as JSON string '/cmds' : returns the list of available commands """ + urlComponents = urllib.parse.urlparse(self.path) + queryParams = urllib.parse.parse_qs(urlComponents.query) + strJsonResponse = "" - - if self.path.lower() == "/cmds": + + if urlComponents.path.lower() == "/cmds": if self.commandMap == None or len(self.commandMap) == 0: - self.send_error(501, "No commands available.") + self.send_error(501, "No commands available") + return else: strJsonResponse = self.onGetCmds() - elif self.path == "/data" and self.dataDict != None: + elif urlComponents.path.lower() == "/cmd": + if self.commandMap == None or len(self.commandMap) == 0: + self.send_error(501, "No commands available") + return + else: + return self.onGetCmd(queryParams) + elif urlComponents.path.lower() == "/data" and self.dataDict != None: strJsonResponse = self.onGetRootDataPath() - elif self.path.startswith("/data/") and self.dataDict != None: + elif urlComponents.path.startswith("/data/") and self.dataDict != None: strJsonResponse = self.onGetDetailedDataPath() else: self.send_error(404) return - + try: self.send_response(200) self.send_header('Content-type', 'application/json; charset=utf-8') diff --git a/raspend/utils/commandmapping.py b/raspend/utils/commandmapping.py index f6162cd..51b1b75 100644 --- a/raspend/utils/commandmapping.py +++ b/raspend/utils/commandmapping.py @@ -79,10 +79,10 @@ def invoke(self, args): Check args and invoke callback method. """ if not type(args) is dict: - return False + raise TypeError("Arguments need to be passed in a dictionary!") for key in args.keys(): if not self.hasParameter(key): - return False + raise KeyError("No argument '{0}' found!".format(key)) return self.__function(**args) class Command(): diff --git a/setup.py b/setup.py index f08e169..d4b6b32 100644 --- a/setup.py +++ b/setup.py @@ -12,7 +12,7 @@ setuptools.setup( name="raspend", - version="1.0.2", + version="1.1.0", author="Joerg Beckers", author_email="pypi@jobe-software.de", description="A small and easy to use HTTP backend framework for the Raspberry Pi which is ideal for small to medium-sized home automation projects.",