How to write a web server in bash script

Building a Web Server from Scratch with Bash Script

In the world of web development, the terms "web server" and "programming language" often go hand in hand. However, what if we told you that you could create a simple yet functional web server using nothing but a humble scripting language? In this article, we will explore an example for crafting a basic web server using Bash script, demonstrating the power of simplicity and resourcefulness.


The Basics of a Web Server

Before diving into the intricacies of a Bash-based web server, let's quickly recap what a web server is. At its core, a web server is software that handles incoming HTTP requests from clients (typically web browsers) and responds by serving the requested resources, such as HTML files, images, or other content. This process involves binding to a port, listening for incoming connections, and sending appropriate responses back to clients.


Web server


Building Bash Web Servers

Installation

Firstly we will install the dependencies required for this example. We will be using apache2 as web server, with the CGI module enabled. The Apache Common Gateway Interface (CGI) module is a crucial component of the Apache HTTP Server that facilitates the dynamic generation of web content by connecting external scripts and programs to the web server. Operating as an intermediary between the web server and these scripts, the CGI module enables the execution of various programming languages, such as Perl, Python, or Bash, in response to HTTP requests. When a client requests a dynamic resource, the CGI module invokes the relevant script or program, passing along necessary data and environmental variables. The output generated by the script is then formatted as an HTTP response and delivered to the requesting client. 

We will use sqlite3 as database.


>sudo apt install sqlite3
>sudo apt install apache2
>sudo a2enmod cgi
>sudo service apache2 restart


Form

The first example will be based on a form to send values to the server. We could use either a GET or a POST method to do it. We will create a script called index.sh that allows the user to specify the user name and email on a form. It will expose a couple of forms.


#!/bin/bash

echo "Content-type: text/html"
echo ""

cat <<EOT
<!DOCTYPE html>
<html>
<head>
        <title>Welcome to our application</title>
</head>
<body>
        <p>GET Form</p>
	<br/>
        <form action="get.sh" method="get">
                <label>Name</label>
                <input type="text" name="name">
                <br>
                <label>E-mail</label>
                <input type="text" name="email">
                <br>
                <button type="submit">Submit</button>
        </form>
	<br/>
        <p>POST Form</p>
	<br/>
        <form action="post.sh" method="post">
                <label>Name</label>
                <input type="text" name="name">
                <br>
                <label>E-mail</label>
                <input type="text" name="email">
                <br>
                <button type="submit">Submit</button>
        </form>
	<br/>
	<p>Cookies: $HTTP_COOKIE</p>
</body>
</html>
EOT

The first one will use a GET request. The processing of this request will be handled by the script called get.sh. In this script we can also see how to use cookies.


#!/bin/bash

name=`echo "$QUERY_STRING" | awk '{split($0,array,"&")} END{print array[1]}' | awk '{split($0,array,"=")} END{print array[2]}'`
email=`echo "$QUERY_STRING" | awk '{split($0,array,"&")} END{print array[2]}' | awk '{split($0,array,"=")} END{print array[2]}' | sed -e 's/%40/@/g'`

user_id=`sqlite3 app.db "select id from user where name = '$name';"`
if [ ! -n "$user_id" ]
then
	sqlite3 app.db "insert into user (name, email) values ('$name', '$email');"
	user_id=`sqlite3 app.db "select id from user where name = '$name';"`
fi

env_var=`env`

echo "Set-Cookie: user_id='$user_id'"
echo "Content-type: text/html"
echo ""

cat <<EOT
<!DOCTYPE html>
<html>
<head>
        <title>Welcome to our application</title>
</head>
<body>
        <h1>Welcome $name </h1>
        <h2> Your e-mail address is $email <h2>
	<br/>
	<p>Cookies: $HTTP_COOKIE</p>
	<br/>
	<p>ENV: $env_var</p>
</body>
</html>
EOT

The second form will use a POST method. And its processing will be handled by the script named post.sh.


#!/bin/bash

read form

name=`echo "$form" | awk '{split($0,array,"&")} END{print array[1]}' | awk '{split($0,array,"=")} END{print array[2]}'`
email=`echo "$form" | awk '{split($0,array,"&")} END{print array[2]}' | awk '{split($0,array,"=")} END{print array[2]}' | sed -e 's/%40/@/g'`

user_id=`sqlite3 app.db "select id from user where name = '$name';"`
if [ ! -n "$user_id" ]
then
	sqlite3 app.db "insert into user (name, email) values ('$name', '$email');"
	user_id=`sqlite3 app.db "select id from user where name = '$name';"`
fi

env_var=`env`

echo "Set-Cookie: user_id='$user_id'"
echo "Content-type: text/html"
echo ""

cat <<EOT
<!DOCTYPE html>
<html>
<head>
        <title>Welcome to our application</title>
</head>
<body>
        <h1>Welcome $name </h1>
        <h2> Your e-mail address is $email <h2>
	<br/>
	<p>Cookies: $HTTP_COOKIE</p>
	<br/>
	<p>ENV: $env_var</p>
</body>
</html>
EOT


Upload file

The next example will consist on a form that allows the user to upload a file to the server. We will first create a script called upload.sh that exposes a multipart/form-data form.


#!/bin/bash

echo "Content-type: text/html"
echo ""

cat <<EOT
<!DOCTYPE html>
<html>
<head>
        <title>Welcome to our application</title>
</head>
<body>
	<form action="fileup.sh" method="post" enctype="multipart/form-data">                                          
		<input type="file" name="file" />                                    
                <br>
                <button type="submit">Submit</button>
	</form> 
</body>
</html>
EOT

Once the user submits the selected file, the POST request will be processed by a script named fileup.sh.


#!/bin/bash

echo "Set-Cookie: user_id='$user_id'"
echo "Content-type: text/plain"
echo ""

TMPOUT=/home/al/tmp/test
cat > $TMPOUT

# Get the line count
LINES=$(wc -l $TMPOUT | cut -d ' ' -f 1)

# Remove the first four lines
tail -$((LINES - 4)) $TMPOUT >$TMPOUT.1

# Remove the last line
head -$((LINES - 5)) $TMPOUT.1 >$TMPOUT


Terminal

The final example is based on executing a command line program and returning the output of the program in the response of the POST request.


#!/bin/bash

read form

cmd=`echo "$form" | awk '{split($0,array,"&")} END{print array[1]}' | awk '{split($0,array,"=")} END{print array[2]}' | sed -e 's/+/ /g' | sed -e 's/\%2F/\//g' `

echo "Content-type: text/html"
echo ""

cat <<EOT
<!DOCTYPE html>
<html>
<head>
        <title>Web terminal</title>
</head>
<body>
<textarea rows="50" cols="200">
$($cmd)
</textarea>
<br/>
<form action="terminal.sh" method="post">
	<label>></label>
	<input type="text" name="cmd">
	<button type="submit">Run</button>
</form>
<br/>
</body>
</html>
EOT


Deployment

In order to deploy these example, we need to first create a database file for sqlite, and then copy the scripts to the cgi-bin folder.


#!/bin/bash

echo "Creating database"
sqlite3 app/app.db "create table user (id INTEGER PRIMARY KEY, name TEXT, email TEXT);"
echo "Database created"

echo "Deploying app"
sudo cp -r scripts /usr/lib/cgi-bin
sudo chmod 777 -R /usr/lib/cgi-bin/scripts
echo "Done"


Testing the examples

We can test the examples just by navigating to the URL of the corresponding script. For example:


http://localhost/cgi-bin/scripts/index.sh

And we can check any error message by looking at the apache error log.


>tailf /var/log/apache2/error.log


Conclusion

Crafting a web server from scratch using Bash scripting is just an exercise of curiosity to understand how the old CGI applications worked. While this approach might not be suitable for production-grade applications, it offers an excellent learning opportunity for understanding the fundamental principles of web servers, networking, and HTTP. By experimenting with Bash scripting, you can gain a deeper appreciation for the intricate processes that power the web.

Popular posts from this blog

How to setup NeoVim configuration file

WebAssembly (Wasm): Fixing the Flaws of Applets

How to query Outlook from PowerShell