Return to Main Page


Creating Your Own Embedded Dynamic Web Site. Linux, Busybox, Httpd and Sqlite


Introduction

Embedded Linux usually offers limited capabilities for web application programming. Disk space, available memory, processing speed put certain restrictions that may not allow installing and running de-facto standard LAMP bundle (Linux, Apache, MySQL, PHP/Perl).

At the same time by default any embedded Linux distribution contains software suitable for web programming. The first thing that comes to mind is Busybox.

Busybox is a single executable combining many standard Unix tools, much like the larger (but more capable) GNU Core Utilities. Busybox incorporates minimalist replacements of a variety of Unix utilities into a small single binary file, which makes it ideal for special purpose distributions and embedded devices. It is sometimes called "The Swiss Army Knife of Embedded Linux".

Although Busybox is highly customizable and may contain more than 200 common Unix utilities, the most important for embedded web development among them are:

(a) ash      :   a Bourne compatible shell used in resource-constrained environments
(b) httpd    :   a simple, lightweight http server
(c) awk      :   an awk programming language interpreter
(d) sed      :   a standard Unix utility for text transformation

These four Busybox applets combined can provide enough flexibility for creation of dynamic web site powered with CGI ash scripts.


Starting and Configuring Httpd

The full syntax of httpd server commands is available at Busybox web site. Here we will show some of its options omitting others:

[busybox] httpd [-c conffile] [-p [ip:]port] [-u user[:grp]] [-r realm] [-h home]
[busybox] httpd [-m pass]
[busybox] httpd [-d/-e string]

-c conffile         : configuration file to read. By default it is /etc/httpd.conf, although the server can start without any configuration file at all.
-p [ip:]port       : allows binding to another port. By default the port is 80.
-u user[:grp]    : sets server's UID[/GID].
-r realm           : sets authentication realm (a string appearing at web browser login/password prompt).
-h home           : home directory.
-m pass           : encrypts the pass string with MD5 algorithm.
-d/-e string      : respectively decode URL string and encodes HTML string

For example the following command will start httpd server listening on 8080 port, with configured options and having nobody's privileges:

busybox httpd -c /etc/httpd.conf -p 8080 -u nobody -r "Protected Area" -h /home/www

A sample configuration file with detailed comments is available at Busybox website. It is relatively simple and easy to understand. To encrypt users' passwords one may use the following command that will append respective string to the configuration file:

echo /directory/path:username:`Busybox httpd -m userpassword` >> /etc/httpd.conf


Writing a Simple Calendar Script

Any URL starting by /cgi-bin/ is assumed to be a CGI script. Needless to say that in order to run properly scripts must have executable bit set. The server passes scripts to ash shell for execution after setting environment variables. All form's variables and their values are put into QUERY_STRING.

For demonstration purpose we will write a script that shows a month's calendar based on arguments submitted. A piece of html code for a form that posts the data to our script is shown below:

<form method="post" action="./cgi-bin/calendar.cgi">
Enter MM/YYYY<br>
<input type="text" name="m" value="" style="width: 24px">
<input type="text" name="y" value="" style="width: 40px"><br>
<input type="submit" value="Submit">
</form>

Our calendar script has four distinctive parts (see below): html header code; query string processing; printing of calendar information; and closing html tags. The html code is primarily generated by awk.

#!/bin/ash

echo -e "Content-type: text/html; charset=UTF-8\n"
echo "<html><head><title>Calendar script</title></head><body>"

read QUERY_STRING
eval $(echo $QUERY_STRING|sed 's/&/ /g')

for i in $(echo $QUERY_STRING|awk -F'&' '{for(;i++<NF;) print $i}'|cut -d= -f1)
do
eval $i='`busybox httpd -d $'$i'`' 2>/dev/null
done

echo "<table>"
busybox cal $m $y|
awk    '{
if(NR>1)
{
print "<tr align=right>"
for(i=1; i < 21; i+=3) print "<td>" substr($0, i, 2) "</td>"
print "</tr>"
}
else print "<tr align=center><td colspan=7>" $0 "</td></tr>"
}'
echo "</table>"

echo "</body></html>"

exit 0

Versions of Unix calendar utility (cal, gcal or ncal) have slightly different output format and may contain or omit leading and/or trailing empty strings or spaces. As a result it is recommended to check the difference and amend the code accordingly.

Obviously we can make query string processing code reusable by putting it into a separate resource file loadable at every execution of a script. For this purpose we create the following .httpd.rc file and place it into our CGI directory. One can also put there functions printing opening and closing html tags.

header ()
{
echo -e "Content-type: text/html; charset=$1\n"
echo "<html><head><title>$2</title></head><body>"
}

cgi ()
{
read QUERY_STRING
eval $(echo $QUERY_STRING|sed 's/&/ /g')
for i in $(echo $QUERY_STRING|awk -F'&' '{for(;i++ do
eval $i='`busybox httpd -d $'$i'`' 2>/dev/null
done
}

end () { echo "</body></html>"; }

As a result our calendar script looks much more concise now:

#!/bin/ash

. ./.httpd.rc 2>/dev/null || exit 1

header UTF-8 "Calendar script"
cgi

echo "<table>"
busybox cal $m $y|
awk    '{
if(NR>1)
{
print "<tr align=right>"
for(i=1; i < 21; i+=3) print "<td>" substr($0, i, 2) "</td>"
print "</tr>"
}
else print "<tr align=center><td colspan=7>" $0 "</td></tr>"
}'
echo "</table>"

end

exit 0


SQLite Web Interface

Due to its small footprint, extensive SQL syntax support and high speed, SQLite is a good choice for an embedded web site database.

SQLite can be compiled as a command-line executable utility to access and modify its databases. This utility reads lines of input from Linux shell and passes them on to the SQLite library for execution. To demonstrate how to access the data, we create a sample SQLite database ./emp.db with a single table containing the list of a company's employees and their respective salaries:

CREATE TABLE IF NOT EXISTS employees (name TEXT, salary INT);
INSERT INTO employees VALUES('JANE DOE', 8500);
INSERT INTO employees VALUES('JOHN DOE', 3500);
INSERT INTO employees VALUES('RICHARD ROE', 2500);
INSERT INTO employees VALUES('JOHN SMITH', 10500);
INSERT INTO employees VALUES('WALTER JOHNSON', 10500);
INSERT INTO employees VALUES('ANNE SMITH', 7500);

In our case we create a simple form containing two radio buttons allowing selection of (a) all employees or (b) just top 3 salaried ones (see below):

<form method="post" action="./cgi-bin/sqlite.cgi">
<input type="radio" name="limit" value="-1"> All<br>
<input type="radio" name="limit" value="3"> Top 3 only<br><br>
<input type="submit" value="Submit">
</form>

It is also possible to modify SQL query output format. A special dot-command .mode html passed to SQLite utility will make SQL query output html table coded data, a very useful feature especially for web applications.

The below script shows the table listing of employees (either all or top 3 salaried ones, depending on the form values submitted) and their average salary in the final row.

#!/bin/ash

. ./.httpd.rc 2>/dev/null || exit 1

header UTF-8 "Database script"
cgi

echo "<table>"
echo "<tr><td>Name</td><td>Salary</td></tr>"
echo   ".mode html
select * from employees order by salary desc limit $limit;
select \"Average salary:\", avg(salary) from employees;" | sqlite3 ./emp.db
echo "</table>"

end

exit 0


Conclusion

Despite the fact that Busybox applets do not provide functionality comparable to that of Perl or PHP scripting and Apache or MySQL engines, users may find them suitable for special purpose projects that do not require high level of sophistication or heavy workload. This article was an attempt to show how one can use capabilities provided by Busybox and SQLite to build a minimalist interactive web service equipped with a database engine.

January 20, 2010