Websocket Server

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


Considering that in HTTP, the value can change only after a client request,
if I want to refresh a value on a web page, I can :

  • download all the page ( heavy php way )
  • Make the client ask to refresh a value ( javascript / ajax way )
  • change the http protocole in a websocket protocole to get a bidirectionnal communication tube between the client and the server.

Websocket Server ( server sends random datas )

code_ws_rand.zip

ws_1.svg

ws_2.svg

ws_3.svg

ws_4.svg

ws_5.svg

ws_6.svg

ws_7.svg

ws_8.svg

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

#define BUFFER_SIZE 0x1000
//======================================================================
void * dialogThread(void *arg)
{
	char header[0x100];
	char handshake[29];
	  
	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))
				{
					printf("CONTENTLENGTH\n");
						// nothing more to be done
				}
				else if(!strcmp(buffer,"\n")||!strcmp(buffer,"\r\n"))
				{
						break; // end of header
				}
				else if(sscanf(buffer, "Upgrade : %31s", req->upgrade)==1)
				{
				  // nothing more to be done
				}
				else if(sscanf(buffer, "Sec-WebSocket-Key : %31s", req->wsKey)==1)
				{
				  // nothing more to be done
				}								
	  }
	//------------------------------------------------------------------------------------------
	//---- 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);
	  }
	//------------------------------------------------------------------------------------------  
	//---- HANDLE UPGRADE TO WEBSOCKET REQUEST ----	  
	if(!strcmp(req->requestMethod, "GET")&& !strcmp(req->requestUri, "/")&& !strcmp(req->upgrade, "websocket"))
	{
		printf("upgrading to websocket\n");
		//---- prepare and send reply header ----
		wsHandshake(handshake, req->wsKey);
		
		 const int hLen=snprintf(header, sizeof(header),
                            "HTTP/1.1 101 Switching Protocols\r\n"
                            "Connection: Upgrade\r\n"
                            "Upgrade: websocket\r\n"
                            "Sec-WebSocket-Accept: %s\r\n"
                            "\r\n",
                            handshake);
		  sendAll(req->sock, header, hLen);
		  
		      //---- receive some parameters from client ----
			uint32_t params[3];
			WsResult r=wsRecv(req->sock, params, sizeof(params));
			if((r.opcode==WS_BIN)&&(r.length!=sizeof(params)))
			{
			  fprintf(stderr, "cannot receive parameters\n");
			  goto threadEnd;
			}
			const int delayMs=(int)ntohl(params[0]),
			modulo=(int)ntohl(params[1]),
			repeatCount=(int)ntohl(params[2]);
			
			 //---- produce and send values to client ----
			for(int repeat=0; repeat<repeatCount; ++repeat)
			{
			  //---- produce new values ----
			  //sleepMs(delayMs); // pretend it takes some time to obtain
			  sleep(1);
			  uint32_t value=(uint32_t)(rand()%modulo);
			  uint32_t values[4]= {value, value+10, value*2, value*value}; // any value is suitable in this example       

			  //---- send this value to client ----
			  for(int i=0; i<4; ++i)
			  {
				values[i]=htonl(values[i]);
			  }
			  wsSend(req->sock, values, sizeof(values), WS_BIN);	//>>>>>>>>>>>>>>>>>>>> WS SEND
			}

			//---- signal end of websocket dialog ----
			wsSend(req->sock, NULL, 0, WS_CLOSE);

		  //---- websocket dialog stops here ----
    goto threadEnd;
	}	
		  
	//------------------------------------------------------------------------------------------	 
	// 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))
	  {
	  
			  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:		
										{
											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
											
											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
			  {
			  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);
				}
	  }
	  
threadEnd:
	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_httpDynServerWs 5555
new HTTP connection from 192.168.0.1:44086
GET / 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

new HTTP connection from 192.168.0.1:44092
GET /favicon.ico 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: */*
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
DNT: 1
Connection: keep-alive

# click on 'websocket appli' link :

new HTTP connection from 192.168.0.1:44096
GET /wsAppli.html 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
Referer: http://192.168.0.2:5555/
DNT: 1
Connection: keep-alive
Upgrade-Insecure-Requests: 1

new HTTP connection from 192.168.0.1:44098
GET / 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
Sec-WebSocket-Version: 13
Origin: http://192.168.0.2:5555
Sec-WebSocket-Extensions: permessage-deflate
Sec-WebSocket-Key: H3eb/3onA9ZjQavD3M4MXA==
DNT: 1
Connection: keep-alive, Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket

upgrading to websocket