Dynamic HTTP Server

A few illustrated reminds about simple static http server.
For complete explanations, please have a look at : https://web.enib.fr/~harrouet


Getting server script results in a web browser client

REM : think about making the CGI file executable (chmod +x).

code_cgi.zip

dynamic_http_server_1.svg

dynamic_http_server_2.svg

dynamic_http_server_3.svg

httpServer_dyn_1.c
// CREDITS : F. Harrouet, www.enib.fr/~harrouet
//======================================================================
// REM : all static pages have to be readable only !!
//======================================================================
#include "main.h"
#include "httpServerLib.h"

#define BUFFER_SIZE 0x1000
//======================================================================
void * dialogThread(void *arg)
{
	pthread_detach(pthread_self());
	char buffer[BUFFER_SIZE];
	Request *req=(Request *)arg;
	//---- receive and analyse HTTP request line by line ----
	bool first=true;
	for(;;)
	  {
			  if(recvLine(req->sock,buffer,BUFFER_SIZE)<=0) { break; }
			  printf("%s",buffer);
			  if(first)
				{
						first=false;
						sscanf(buffer,"%s %s",req->requestMethod,req->requestUri);
				}
			  else if(sscanf(buffer,"Content-Length : %s",req->contentLength))
				{
						// nothing more to be done
				}
			  else if(!strcmp(buffer,"\n")||!strcmp(buffer,"\r\n"))
				{
						break;
				}
	  }
	//---- prepare and send HTTP reply ----
	sprintf(req->fileName,"./www%s",req->requestUri);
	char *params=strchr(req->fileName,'?');
	if(params) { *params='\0'; }
	struct stat st;
	int r=stat(req->fileName,&st);
	if((r!=-1)&&S_ISDIR(st.st_mode))
	  {
				
			  strcat(req->fileName,"/index.html");
			  r=stat(req->fileName,&st);
	  }
	//------------------------------------------------------------------------------------------	 
	// IF WE HAVE TO SERVE AN EXECUTABLE SCRIPT FILE ( CGI SCRIPT ) : 
	// rem : CGI scripts have to be placed in www/cgi/ folder
	if((access(req->fileName,R_OK|X_OK)==0) && !strncmp(req->requestUri, "/cgi",4))
	  {
		  					printf("EXECUTABLE SCRIPT FILE \n");
		  
			  if(strcmp(req->requestMethod,"GET")&&strcmp(req->requestMethod,"POST"))
				{
						r=sprintf(buffer,"HTTP/1.0 400 Bad Request\n"
										 "Connection: close\n"
										 "Content-Type: text/html\n"
										 "\n"
										 "<html><body>\n"
										 "<b>400 - Bad Request</b><br>\n"
										 "method: %s<br>\n"
										 "uri: %s<br>\n"
										 "fileName: %s<br>\n"
										 "</body></html>\n",
										 req->requestMethod,
										 req->requestUri,
										 req->fileName);
						sendAll(req->sock,buffer,r);
				}
			  else
				{
							// HTTP RESPONSE HEADER
							r=sprintf(buffer,"HTTP/1.0 200 OK\n"
											 "Connection: close\n");
							sendAll(req->sock,buffer,r);
							pid_t child=fork();	// execvp needs a new process, otherwhise the executed process will destroy all current threads.
							switch(child)
							  {
									  case -1:
										{
											perror("fork");
											break;
										}
									  //############ CHILD CODE ######################################
									  case 0:		
										{
											
											
											char* buff = 0;
											buff = (char*)malloc(sizeof(char*)*255);
											
											setenv("REQUEST_METHOD",req->requestMethod,1); 	// needed for queryProperties script
											setenv("REQUEST_URI",req->requestUri,1);	 	// needed for queryProperties script	
											setenv("CONTENT_LENGTH",req->contentLength,1); 	// needed for queryProperties script
											sscanf(buff,"?%s",req->requestUri);
											setenv("QUERY_STRING",buff,1);
											
											
											if(dup2(req->sock,0)==-1)
																					{ perror("dup2"); exit(1); }
											if(dup2(req->sock,1)==-1)
																					{ perror("dup2"); exit(1); }
											if(dup2(req->sock,2)==-1)
																					{ perror("dup2"); exit(1); }
											close(req->sock);
											char *args[2];
											args[0]=req->fileName;
											args[1]=(char *)0;
											execvp(args[0],args);
											perror("execvp");
											exit(1);
										}
									  //############ PARENT CODE #####################################
									  default:		
										{
											if(waitpid(child,(int *)0,0)==-1)
																					{ perror("waitpid"); }
										}
									  }
							}
			  }
			//------------------------------------------------------------------------------------------  
			else   // STATIC HTTP FILE SERVER
			  {
					printf("STATIC HTTP FILE SERVER \n");
			  int input=open(req->fileName,O_RDONLY);
			  if(input==-1)
				{
							r=sprintf(buffer,"HTTP/1.0 404 Not Found\n"
											 "Connection: close\n"
											 "Content-Type: text/html\n"
											 "\n"
											 "<html><body>\n"
											 "<b>404 - Not Found</b><br>\n"
											 "method: %s<br>\n"
											 "uri: %s<br>\n"
											 "fileName: %s<br>\n"
											 "</body></html>\n",
											 req->requestMethod,
											 req->requestUri,
											 req->fileName);
							sendAll(req->sock,buffer,r);
				}
			  else
				{
							const char *contentType="unknown/unknown";
							const char *ext=strrchr(req->fileName,'.');
							if(ext)
							  {
									  if(!strcmp(ext,".html"))     contentType="text/html";
									  else if(!strcmp(ext,".png")) contentType="image/png";
									  else if(!strcmp(ext,".ico")) contentType="image/vnd.microsoft.icon";
							  }
							r=sprintf(buffer,"HTTP/1.0 200 OK\n"
											 "Connection: close\n"
											 "Content-Type: %s\n"
											 "Content-Length: %ld\n\n",
											 contentType,
											 st.st_size);
							sendAll(req->sock,buffer,r);
							for(;;)
							  {
									  r=read(input,buffer,BUFFER_SIZE);
									  if(r<=0) break;
									  sendAll(req->sock,buffer,r);
							  }
							close(input);
				}
	  }

	destroyRequest(req);
	return (void *)0;
	}

//======================================================================
//						MAIN
//======================================================================

int main(int argc, char **argv)
{
	//---- avoid exiting on broken client connection (ignore SIGPIPE) ----
	struct sigaction sa;
	memset(&sa,0,sizeof(struct sigaction));
	sa.sa_handler=SIG_IGN;
	if(sigaction(SIGPIPE,&sa,(struct sigaction *)0)==-1)
											{ perror("sigaction"); exit(1); }

	//---- check command line arguments ----
	if(argc!=2)
											{ fprintf(stderr,"usage: %s http_port\n",argv[0]); exit(1); }

	//---- extract local port number ----
	int httpPortNumber;
	if(sscanf(argv[1],"%d",&httpPortNumber)!=1)
											{ fprintf(stderr,"invalid port %s\n",argv[1]); exit(1); }

	//---- multithreaded TCP server launching dialogThread() ----

	//---- create HTTP listen socket ----
	int httpSocket=socket(PF_INET,SOCK_STREAM,0);
	if(httpSocket==-1)
											{ perror("socket"); exit(1); }
	// ... avoiding timewait problems (optional)
	int on=1;
	if(setsockopt(httpSocket,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(int))==-1)
											{ perror("setsockopt"); exit(1); }
	// ... bound to any local address on the specified port
	struct sockaddr_in myAddr;
	myAddr.sin_family=AF_INET;
	myAddr.sin_port=htons(httpPortNumber);
	myAddr.sin_addr.s_addr=htonl(INADDR_ANY);
	if(bind(httpSocket,(struct sockaddr *)&myAddr,sizeof(myAddr))==-1)
											{ perror("bind"); exit(1); }
	// ... listening connections
	if(listen(httpSocket,10)==-1)
											{ perror("listen"); exit(1); }

	for(;;)
	  {
		  //---- accept new HTTP connection ----
		  struct sockaddr_in fromAddr;
		  socklen_t len=sizeof(fromAddr);
		  int dialogSocket=accept(httpSocket,(struct sockaddr *)&fromAddr,&len);
		  if(dialogSocket==-1)
												{ perror("accept"); exit(1); }
		  printf("new HTTP connection from %s:%d\n",
							inet_ntoa(fromAddr.sin_addr),ntohs(fromAddr.sin_port));

		  //---- start a new dialog thread ----
		  pthread_t th;
		  if(pthread_create(&th,(pthread_attr_t *)0, dialogThread,createRequest(dialogSocket)))
												{ fprintf(stderr,"cannot create thread\n"); exit(1); }
	  }

	close(httpSocket);
	return 0;
}

//^^^^^^^^^^^^^^^^^^^^^^^^^^ EOF ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# RPI Terminal

./exec_httpServer_dyn_1 5555
new HTTP connection from 192.168.0.1:40256
GET /script.cgi HTTP/1.1
Host: 192.168.0.2:5555
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:61.0) Gecko/20100101 Firefox/61.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1

Application : Controlling RPI Led from the web browser

code_led.zip

Configuring GPIO4 LED

image_rotation.jpg

Here we are in the RPI Terminal.
The following lines should be written in a bash script and recorded in /etc/profile.d to be executed on RPI startup.

$ echo 4 > /sys/class/gpio/export				# create the GPIO4 folder
$ echo out > /sys/class/gpio/gpio4/direction 	# make direction out for led

Once config done, try :

$ echo 1 > /sys/class/gpio/gpio4/value		# switch the LED on
$ echo 0 > /sys/class/gpio/gpio4/value		# switch the LED off

Control GPIO4 with CGI script

browser1_1.svg

browser1_2.svg

browser1_3.svg

The executed script is a bash one ; another example can be done with a lua script ( in www/cgi )

control_led.html - control_gpio4.sh
<html>
<head>
<title>
Control Led
</title>
</head>
<body>



<h1> Control GPIO4 Led</h1>

     <table>
		<tr>
		<td>
		<form  method=get action="cgi/control_gpio4.sh">
		<input type=radio name="gpio4" value="off" checked>off
		<input type=radio name="gpio4" value="on">on
		<p>
		<hr>
         	<input type=submit name="valide" value="valider">
		</form>
		</td>
		</tr>
	 </table>








</body>
</html>
# finish header
echo "Content-Type: text/html"
echo

# then produce content
echo "<html><head>"
echo "<title>Un script</title>"
echo "</head><body>"
echo "<h1>Dynamic Part : </h1>"

echo "<hr>"

echo "<p>QUERYSTRING = ["
echo $QUERY_STRING
echo "]"

echo "<br/>"

result=$(echo $QUERY_STRING | cut -d'=' -f2)
result=$(echo $result | cut -d'&' -f1)
#echo $result

if [ "$result" = "off" ]
then
	echo 0 > /sys/class/gpio/gpio4/value
	echo "gpio4 led turned off at"
	date
else
	echo 1 > /sys/class/gpio/gpio4/value
	echo "gpio4 led turned on at"
	date
fi

echo "<br/>"
echo "<a href="/">home</a>" 

echo "<hr>"
echo "</body></html>"

Control GPIO4 led with AJAX

Previously we were obliged to download the whole page.
The ajax-way permits to refresh only a part of this page after a client request.

browser2.svg

control_led_ajax.html - ajax_gpio4.sh
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>ENIB Dynamic Server</title>
  <link rel="stylesheet" type="text/css" href="../style.css" />
</head>
<body>
<div id="body">
<p>[<a href="/">home</a>]</p>		
	
<h3>Static Part :</h3>

Control GPIO4 led <input type="checkbox" id="tLed" onclick="do_onclick(this, document.getElementById('tLedLabel'))">

<hr>

<span id="tLedLabel"> GPIO4 Switched off</span>

<br/>


<!--===========================================================================-->
<!--                      JAVASCRIPT 					   
<!--===========================================================================-->
<script>
function do_onclick(led, label)
{
  var xmlhttp=new XMLHttpRequest();
  
  // register callback : function to be called when we receive the response
  xmlhttp.onreadystatechange=function() {
    if (xmlhttp.readyState==4 && xmlhttp.status==200) {
      label.innerHTML=xmlhttp.responseText;
    }
  }
  
  // send request and poll for asynchronous response
  xmlhttp.open("GET","cgi/ajax_gpio4.sh?gpio4="+led.checked,true);
  xmlhttp.send();
}

// initial checkbox state, reflect this state on the target
var led=document.getElementById("tLed");
var label=document.getElementById("tLedLabel");

led.checked=false;
do_onclick(led, label);
  
</script>
<!--===========================================================================-->

</body>
</html>
# finish header
echo "Content-Type: text/html"
echo

# then produce content
echo "<html><head>"
echo "<title>Un script</title>"
echo "</head><body>"
echo "<h3>Dynamic Part : </h3>"

echo "<p>QUERYSTRING = ["
echo $QUERY_STRING
echo "]"

echo "<br/>"

result=$(echo $QUERY_STRING | cut -d'=' -f2)

if [ "$result" = "false" ]
then
echo 0 > /sys/class/gpio/gpio4/value
echo "gpio4 led turned off at"
date
else
echo 1 > /sys/class/gpio/gpio4/value
echo "gpio4 led turned on at"
date
fi

echo "</body></html>"