RabbitWeb

To Web-Enable Embedded Applications

This document explains the ease with which you can now create a web interface to your Rabbit-based device. An add-on module available starting with Dynamic C 8.50 introduces an enhanced HTTP server that, in most cases, eliminates the need for complicated CGI programming while giving the developer complete freedom in the design of their web page.

The enhanced HTTP server is called RabbitWeb and uses:

Section 1.0 presents a simple example to show the power and ease of developing a RabbitWeb server that presents a web interface to your device. This example gives step-by-step descriptions of the HTML page and the Dynamic C code. New features will be briefly explained, then linked to their comprehensive descriptions in Section 2.0 and Section 3.0. These sections are followed by a more complex example in Section 4.0, which in turn is followed by quick reference guides for both the Dynamic C language extensions and the new scripting language, which is called ZHTML (Appendix A).

1.0 Getting Started: A Simple Example

In this example, we pretend that a humidity detector is connected to your Rabbit-based controller. Your controller runs a web server that displays a page showing the current reading from the humidity detector. From this monitoring page there is a link to another page that contains an HTML form that allows you to remotely change some configuration parameters. This example introduces web variables and user groups. It also illustrates some new security features and the use of error checking.

This example assumes you have already installed Dynamic C 8.50 (or later) and hooked up the RCM3700. Instructions for doing so are in the RabbitCore RCM3700 User's Manual. This user's manual describes network connections for your core module, as well as setting IP addresses for running sample programs.

1.1 Dynamic C Application Code for Humidity Detector

This section describes the application for our example. The program is shown in its entirety for convenience. It is broken down into manageable pieces on the following pages.

File Name: /Samples/tcpip/rabbitweb/humidity.c

#define TCPCONFIG 1
#define USE_RABBITWEB 1
#memmap xmem
#use "dcrtcp.lib"
#use "http.lib"

#ximport "samples/tcpip/rabbitweb/pages/humidity_monitor.zhtml" monitor_zhtml 

#ximport "samples/tcpip/rabbitweb/pages/humidity_admin.zhtml" admin_zhtml

SSPEC_MIMETABLE_START
SSPEC_MIME_FUNC(".zhtml", "text/html", zhtml_handler),
SSPEC_MIME(".html", "text/html")
SSPEC_MIMETABLE_END

SSPEC_RESOURCETABLE_START
SSPEC_RESOURCE_XMEMFILE("/index.zhtml", monitor_zhtml),
SSPEC_RESOURCE_XMEMFILE("/admin/index.zhtml", admin_zhtml)
SSPEC_RESOURCETABLE_END

#web_groups admin

int hum;
#web hum groups=all(ro)

int hum_alarm;

#web hum_alarm ((0 < $hum_alarm) && ($hum_alarm <= 100))\
groups=all(ro),admin

int alarm_interval;
char alarm_email[50];

#web alarm_interval ((0 < $alarm_interval) && ($alarm_interval < 30000)) \
groups=all(ro),admin

#web alarm_email groups=all(ro),admin
void main(void){
int userid;
hum = 50;
hum_alarm = 75;
alarm_interval = 60;
strcpy(alarm_email, "somebody@nowhere.org");

   sock_init();               // initialize TCP/IP stack
http_init();               // initialize web server

   http_set_path("/", "index.zhtml");
tcp_reserveport(80);

   sspec_addrule ("/admin", "Admin", admin, admin,
SERVER_ANY, SERVER_AUTH_BASIC, NULL);

   userid = sauth_adduser("harpo", "swordfish", SERVER_ANY);
sauth_setusermask(userid, admin, NULL);

   while(1) {
http_handler();
}
}

The source code walk-through consists of blocks of code followed by line-by-line descriptions. Particular attention is given to the RabbitWeb #web and #web_groups statements, which are new compiler directives.


#define TCPCONFIG 1
#define USE_RABBITWEB 1

#memmap xmem

#use "dcrtcp.lib"
#use "http.lib"

The macro TCPCONFIG is used to set network configuration parameters. Defining this macro to 1 sets 10.10.6.100, 255.255.255.0 and 10.10.6.1 for the board's IP address, netmask and gateway/nameserver respectively. If you need to change any of these values, read the comments at the top of \lib\tcpip\tcp_config.lib for instructions.

The USE_RABBITWEB macro must be defined to 1 to use the HTTP server enhancements. The #define of USE_RABBITWEB is followed by a request to map functions not flagged as root into xmem. The two #use statements allow the application the use of the main TCP/IP libraries (all brought in by dcrtcp.lib) and the HTTP server library (which also brings in the resource manager library, zserver.lib).


#ximport "samples/tcpip/rabbitweb/pages/humidity_monitor.zhtml" monitor_zhtml 

#ximport "samples/tcpip/rabbitweb/pages/humidity_admin.zhtml" admin_zhtml

SSPEC_MIMETABLE_START
SSPEC_MIME_FUNC(".zhtml", "text/html", zhtml_handler),
SSPEC_MIME(".html", "text/html")
SSPEC_MIMETABLE_END

SSPEC_RESOURCETABLE_START
SSPEC_RESOURCE_XMEMFILE("/index.zhtml", monitor_zhtml),
SSPEC_RESOURCE_XMEMFILE("/admin/index.zhtml", admin_zhtml)
SSPEC_RESOURCETABLE_END

The HTML pages are copied to Rabbit memory using #ximport. The first one is a status page and the second one is a configuration interface.

Next the MIME type mapping table is set up. This allows zhtml_handler() to be called when a file with the extension .zhtml is processed. Then the static resource table is set up, which gives the server access to the files that were just copied in using #ximport. The first parameter is the name of the resource and the second parameter is its address.


#web_groups admin

The RabbitWeb server has a concept of user groups, which are created using the compiler directive, #web_groups. Users can be added to and removed from these groups at runtime by calling the API functions sauth_adduser() and sauth_removeuser().

The purpose of the user groups is to protect directories and variables from unauthorized access. User groups are fully described in the section titled, Security Features.


int hum;
#web hum groups=all(ro)

This declares a variable named hum of type integer using normal Dynamic C syntax. It will be used to store the current humidity reported by the humidity detector. The #web expression registers this C variable with the web server. The read-only attribute is assigned by the "groups=all(ro)" part which gives read-only access to this variable to all user groups.

More information on registering variables is given in the section titled, Registering Variables, Arrays and Structures.


int hum_alarm;
#web hum_alarm \
((0 < $hum_alarm) && ($hum_alarm <= 100)) groups=all(ro),admin

This code creates a variable called hum_alarm to indicate the level at which the device should send an alarm. Unlike the #web statement for hum, there is a guard added when hum_alarm is registered. A guard is an error-checking expression used to evaluate the validity of submissions for its variable. The guard given for hum_alarm ensures only the range of values from 1 to 100 inclusive are accepted for this variable. More information on the syntax of the error-checking expression is in the section titled, Web Guards. The way error information is used in the HTML form is described in the section titled, Error Handling.

The dollar sign symbol in $hum_alarm specifies the latest submitted value of the variable, not necessarily the latest committed value of the variable. The difference between, and the importance of, the latest submitted value and the latest committed value of a variable will make more sense when you have read Section 2.2. Also, $-variables in web guards must be simple variables: for example, int, long, float, char, or string (char array). They cannot be structures or arrays.

The "admin" group is given full access to the variable (access is read and write by default), while all other users are limited to read-only access. If no "group=" parameter is given, then anyone can read or write hum_alarm. The order of group names is important. If "admin" came before "all(ro)" then the admin group would not have write access.


int alarm_interval;
char alarm_email[50];

#web alarm_interval \
((0 < $alarm_interval) && ($alarm_interval < 30000))\
groups=all(ro),admin
#web alarm_email groups=all(ro),admin

These lines declare and register an integer variable and a string. The variable alarm_interval gives the minimum amount of time in minutes between two alarms, thus preventing alarm flooding. The variable alarm_email gives the email address to which alarms should be sent.

This concludes the compile-time initialization part of the code.


void main(void)
{
int userid;
hum = 50;
hum_alarm = 75;
alarm_interval = 60;
strcpy(alarm_email, "somebody@nowhere.org");

   sock_init();                 // initialize TCP/IP stack
http_init();                 // initialize web server

   http_set_path("/", "index.zhtml");
tcp_reserveport(80);

   sspec_addrule ("/admin", "Admin", admin, admin, SERVER_ANY,
SERVER_AUTH_BASIC, NULL);
userid = sauth_adduser("harpo", "swordfish", SERVER_ANY);
sauth_setusermask(userid, admin, NULL);

   while(1) {
http_handler();            //
call the http server
}
}

In main(), after the local variable userid is declared, there is run-time initialization of the variables that will be visible on the HTML page. Then the stack and the web server are initialized with calls to sock_init() and http_init(), respectively.

The function http_init() sets the root directory to "/" and sets the default file name to index.html. The call to http_set_path() can be used to change these defaults. We only want to change the default filename, so in the function call we keep the default root directory by passing "/" as the first parameter and change the default filename by passing index.zhtml as the second parameter. The reason we want to do this is for when the browser specifies a directory (instead of a proper resource name) we want to default to using index.zhtml in that directory, if it exists. If we don't use the set path function, the default is index.html which won't work for this sample because the file index.html doesn't exist.

The call to sspec_addrule() configures the web server to give write and read access to the directory /admin to any members of the admin group and to require basic authentication for any access to this directory. The call to sauth_adduser() adds the user named harpo with a password of swordfish to the list of users kept by the server. The next function call, sauth_setusermask(), adds the user named harpo to the user group named admin. This sequence of calls allows you to restrict access to the file humidity_admin.zhtml. Only members of the user group admin,which in this case is the one user named harpo, can get the server to display a file resource that starts with /admin. Recall that the file humidity_admin.zhtml was copied to memory by the #ximport directive and given the label admin_zhtml. The file was then added to the static resource table and given the name /admin/index.zhtml. This is the name by which the server recognizes the file and the name by which access to it is restricted.

The web server is driven by the repeated call to http_handler().

The second part of our example requires additions to the HTML page that is served by our web server. The use of the new scripting language will be explained as it is encountered in the sample pages. Regular HTML code will not be explained, as it is assumed the reader has a working knowledge of it. If that is not the case, refer to one of the numerous sources that exist (on the web, etc.) for information on HTML.

1.2 HTML Pages for Humidity Detector

This sample requires two HTML files: one to display the current humidity to all users, and another page that contains the form that allows some parameters to be changed.

1.2.1 The Monitor Page

The first HTML file is humidity_monitor.zhtml. The ".zhtml" suffix indicates that it contains special server-parsed HTML tags. That is, the server must inspect the contents of the HTML file for special tags, rather than just sending the file verbatim.

File name: humidity_monitor.zhtml


<HTML>
<HEAD><TITLE>Current humidity reading</TITLE></HEAD>
<BODY>

      <H1>Current humidity reading</H1>
The current humidity reading is (in percent):
<?z print($hum) ?>
<P>
<A HREF="/admin/index.zhtml">Change the device settings</A>
</BODY>
</HTML>

The above code displays the current humidity reading. The new server-parsed tags begin with "<?z" and end with "?>". "print ($hum)" displays the given variable (that must have been registered with #web).

This code sets up a hyperlink that the user can click on to change the device settings. Note that since it is in the "/admin" directory, the user will need to enter a username and password ("harpo" and "swordfish") to access the file. The username and password requirement was determined by the call to sspec_addrule() in humidity.c. Also note that the reference to the second HTML file uses the name that was given to humidity_admin.zhtml when it was entered into the static resource table in humidity.c.

Figure 1. Web page served by RabbitWeb

This web page is very simple, as shown in Figure 1, but you are free to create more complex web pages (probably containing more variables to monitor). HTML editors such as Netscape's Composer, Hotdog Professional, and Macromedia Dreamweaver can be used to create these web pages.

1.2.2 The Configuration Page

The second HTML file is known to the server as "/admin/index.zhtml." Using error() and some conditional code allows multiple display options with the same HTML file. Again, the file is shown in its entirety for convenience. It is broken down on the following pages.

File name: Samples/tcpip/rabbitweb/pages/humidity_admin.zhtml


<HTML>
<HEAD><TITLE>Configure the humidity device</TITLE></HEAD>
<BODY>
<H1>Configure the humidity device</H1>
<?z if (error()) { ?>
ERROR! Your submission contained errors. Please correct
the entries marked in red below.
<?z } ?>
<FORM ACTION="/admin/index.zhtml" METHOD="POST">
<P><?z if (error($hum_alarm)) { ?>
<FONT COLOR="#ff0000">
<?z } ?>
Humidity alarm level (percent):
<?z if (error($hum_alarm)) { ?>
</FONT>
<?z } ?>
<INPUT TYPE="text" NAME="hum_alarm" SIZE=3
VALUE="<?z print($hum_alarm) ?>">
<P><?z if(error($alarm_email)) { ?>
<FONT COLOR="#ff0000">
<?z } ?>
Send email alarm to:
<? if (error($alarm_email)) { ?>
</FONT>
<?z } ?>
<INPUT TYPE="text" NAME="alarm_email" SIZE=50
VALUE="<?z print($alarm_email) ?>">

      <P><?z if (error($alarm_interval)) { ?>
<FONT COLOR="#ff0000">
<?z } ?>
Minimum time between alarms (minutes):
<?z if (error($alarm_interval)) { ?>
</FONT>
<?z } ?>
<INPUT TYPE="text" NAME="alarm_interval" SIZE=5
VALUE="<?z print($alarm_interval) ?>">
<P><INPUT TYPE="submit" VALUE="Submit">
</FORM>
<A HREF="/index.zhtml">Return to the humidity monitor page</A>
</BODY>
</HTML>

After the usual opening lines of an HTML page, is our first server-parsed tag.

<?z if (error()) { ?>
ERROR! Your submission contained errors. Please correct
the entries marked in red below.
<?z } ?>

Without any parameters, error() returns TRUE if there were any errors in the last form submission. When the submit button for the form is clicked, the POST request goes back to the zhtml page specified by the line:

<FORM ACTION="/admin/index.zhtml" METHOD="POST">

Since this refers back to itself, if there were any errors in the last form submission, the page is redisplayed and along with it the error message inside the if statement.

Figure 2. Web page with error message

There are five actions the user can take on this page. The Submit button was discussed above and the link to the monitor page is a common HREF link. The other three actions are the input fields of the form. These are text fields created by the INPUT tags.


<INPUT TYPE="text" NAME="hum_alarm" SIZE=3 
   VALUE="<?z print($hum_alarm) ?>">

Notice how, with the use of print(), the value of the text fields are filled in by the server before the page is given to the browser.

Before the INPUT tag there is some code that displays text to describe the input field, along with some error checking:


<?z if (error($hum_alarm)) { ?>
<FONT COLOR="#ff0000">
<?z } ?>
Humidity alarm level (percent):
<?z if (error($hum_alarm)) { ?>
</FONT>
<?z } ?>

Instead of calling error() with no parameters, the variable whose input field we are considering is passed to error(). Used with an if statement, this call to error() lets us change the font color to red if the value for that variable was invalid in the last form submission. Note that it is the text we have used to describe the web variable on the HTML page that is shown in red, not the value of the web variable itself. Also note that it is necessary to call error() twice, the second call is to close the FONT tag.

If in the last form submission the web variable had a valid value, the code above will still display the descriptive text but its font color will not be changed.

If there were no errors with any of the web variables in the last form submission, the page display reflects this status.

Figure 3. Web page with no error message

next