How to run external scripts from the HTTP server?

External scripts are a great way to enhance the HTTP server with specific features. This is supported in the Raspberry Pi Version V2.9.4.0 only, for now.

The external script can be actually any executable, but the most obvious is to run a bash script. Here is an example:

#!/bin/bash
echo "<!DOCTYPE html><html><head>"
echo "</head><body>"
echo "<h2>Hello Script</h2>"
echo "</body></html>"

Place this file into your wwwroot folder name it say “script01″ and make it an executable with the chmod command from the command line:

cd /usr/local/etc/dhcpsrv/wwwroot
chmod +x script01

It is assumed that the HTTP Server function is enabled and reachable by an url such as http://raspberry. To execute script01 and see the resulting page enter http://raspberry/script01 into the address line of your browser and you will see the  Hello Script page. If that is not the case then please check the HTTP server configuration.

In case you don’t want to return HTML code from your script but for example some JSON structure then this can be done as well. This a bash script that returns a JSON structure:

#!/bin/bash
DATA="{\"valid\":true}"
# write to stdout the HTTP response
DATESTRING=$(date "+%a, %d %b %Y %H:%M:%S %Z")
printf "HTTP/1.1 200 OK\r\n"
printf "Date: %s\r\n" "$DATESTRING"
printf "Last-Modified: %s\r\n" "$DATESTRING"
printf "Server: %s\r\n" "dhcpsrv"
printf "Content-Type: %s\r\n" "application/json"
printf "Content-Length: %s\r\n" "${#DATA}"
printf "\r\n"
printf "%s" "$DATA"

As you can see the big difference is that in case of something else than a <!DOCTYPE html>, the full header needs to be included into the output of the bash script. Let’s paste the above into a bash script02 file and make that executable as well with chmod, then your browser will display the JSON output when directed to the url http://raspberry/script02.

The examples so far were HTTP GET use cases. Now let’s take a look at POST requests. POST requests differ from GET requests in extra data supplied from the client app running in the browser. This extra data is usually a list of key/value pairs as they are coming from an HTML form. The DHCP server integrated HTTP function writes that extra data into a temporary file and that filename is supplied as a parameter to the external script. Here is an example on how to deal with that:

#!/bin/bash
saveIFS=$IFS
IFS='=&'

declare -A post

if [ -f "$1" ]; then
 # construct the associative array for POST_STRING
 POST_STRING="$(<"$1")"
 parm=($POST_STRING)
 for ((i=0; i<${#parm[@]}; i+=2))
 do
    post[${parm[i]}]=${parm[i+1]}
 done
fi

IFS=$saveIFS

# do something with the post data e.g. ${post["firstname"]}

printf "HTTP/1.1 301 Moved Permanently\r\n"
printf "Date: %s\r\n" "$DATESTRING"
printf "Last-Modified: %s\r\n" "$DATESTRING"
printf "Server: %s\r\n" "dhcpsrv"
printf "Location: %s\r\n" "post.html"
printf "\r\n"

We see two important things here:

  1. The post data is read from the file supplied as a argument $1 into a param array splitted by ‘=’ and ‘&’. The param array is only there to construct an associative array with the name “post”.   That in turn can be used by the script to perform the required action on that data.
  2. The returning result text from the script is now a redirect header. This is part of the post-redirect-get pattern commonly used together with POST requests. In this case the redirection target is post.html and obviously can be anything else.

We save the above script as “script03″ in the wwwroot folder and call that script from a post request such as in the following example HTML post.html:

<!DOCTYPE html>
<html>
 <head>
 </head>
 <body>
  <form method="post" id="form1">
   <label for="firstname">First name:</label>
   <input id="id_firstname" type="text" name="firstname" p/><br />

   <label for="lastname">Last name:</label>
   <input id="id_lastname" type="text" name="lastname" p/><br />

  </form>
  <button type="submit" form="form1" value="Submit" formaction="script03">Submit</button>
 </body>
</html>

This HTML code presents a form with two entry fields “First name” and “Last name”. Those will show up in the script03 as  ${post[“firstname”]} and ${post[“lastname”]} and can be used to perform whatever that script needs to do.

QUERY data is supported as well, as they are supplied as part of the URL such as “http://raspberry/script04?key1=value1&key2=value2″. An enhanced version of script03 (called script04) can make use of the query data as follows:

#!/bin/bash
saveIFS=$IFS
IFS='=&'

declare -A post
declare -A query

# construct the associative array for QUERY_STRING
parm=($QUERY_STRING)
for ((i=0; i<${#parm[@]}; i+=2))
do
 query[${parm[i]}]=${parm[i+1]}
done

if [ -f "$1" ]; then
 # construct the associative array for POST_STRING
 POST_STRING="$(<"$1")"
 parm=($POST_STRING)
 for ((i=0; i<${#parm[@]}; i+=2))
 do
    post[${parm[i]}]=${parm[i+1]}
 done
fi

IFS=$saveIFS

# do something with the post data e.g. ${post["firstname"]} or ${query["key1"]}

printf "HTTP/1.1 301 Moved Permanently\r\n"
printf "Date: %s\r\n" "$DATESTRING"
printf "Last-Modified: %s\r\n" "$DATESTRING"
printf "Server: %s\r\n" "dhcpsrv"
printf "Location: %s\r\n" "post.html"
printf "\r\n"

The scripts executed this way run with the privilege of the dhcpsrv executable. Usually root privileges in case of systemctl based installation as described here. Handle with care!