tinyhttpd--Ultra-Lightweight HTTP server source code Analysis

Source: Internet
Author: User
Tags error handling http request int size sprintf stdin strcmp strlen server port

TINYHTTPD is an ultra-lightweight HTTP server, developed in C language, with all code less than 600 lines, with a simple client that can read this code to understand the nature of an HTTP server. SOURCE Download Link http://sourceforge.net/projects/tinyhttpd/

Before analyzing this source code, you need to have a certain understanding of network Protocols, UNIX programming, and HTTP, this assumes that you have a certain understanding of HTTP, if there is time, will introduce an additional HTTP.

This article first analysis the source code of the project, and finally give a test.

Server-side code: HTTPD.C


Suggested source reading order:main-> startup-> accept_request-> excute_cgi

TINYHTTPD Project Flowchart



First introduce several intermediate auxiliary functions: (full code see previous source link)

Reads a row of data from the client, with \ R or \ r \ n as the line terminator

/**********************************************************************//* Get a line from a socket, whether the line en  DS in a newline, * carriage return, or a CRLF combination.  Terminates the string read * with a null character.  If No newline indicator is found before the * end of the buffer, the string was terminated with a null.  If any of the above three line terminators are read, the last character of the * string would be a linefeed and the string
'll is terminated with a * null character. * Parameters:the Socket Descriptor * The buffer to save the data in * the size of the buffer * Re Turns:the number of bytes stored (excluding null) *//**************************************************************** //**********************************************************************//* Reads a row of data from the socket. with \ r or \ r \ n line terminator * parameters:the Socket Descriptor * The buffer to save the data in * the size of T He buffer * returns:the number of bytes stored (excluding null) *//**********************************************************************/int get_
	Line (int sock, char *buf, int size) {int i = 0;
	char c = ' + ';

	int n;

		Read size-1 characters at most, and the last character to "+" while ((I < size-1) && (c! = ' \ n ')) {n = recv (sock, &c, 1, 0);//single character receive if (n > 0) {if (c = = ' \ r ')//If it is a carriage return, continue reading {/* using the MSG_PEEK flag so that the next read can still get the contents of this read, you can think the receiving window does not slide */n = recv (

				Sock, &c, 1, Msg_peek); if ((n > 0) && (c = = ' \ n '))//If it is carriage return newline character recv (sock, &c, 1, 0);//Continue to receive a single character, actually and the above flag bit Msg_peek read the same character, after reading delete
		Enter the data for the queue, that is, the sliding window, c== ' \ n ' else c = ' \ n ';//Just read the carriage return, then set the newline character, and terminate the read} Buf[i] = c;//into the buffer i++;
	} else//did not read any data c = ' \ n ';

	} Buf[i] = ' + '; return (i);//Returns the number of characters read (including ' + ')}
Request Error Handling
/**********************************************************************//* Informs the client that the request has errors. * Parameters:client
	Socket *//**********************************************************************/void bad_request (int client) {
	Char buf[1024];
	/* Buffer the string and send it to the client via the Send function */sprintf (buf, "http/1.0" request\r\n ");
	Send (client, buf, sizeof (BUF), 0);
	sprintf (buf, "content-type:text/html\r\n");
	Send (client, buf, sizeof (BUF), 0);
	sprintf (buf, "\ r \ n");
	Send (client, buf, sizeof (BUF), 0);
	sprintf (buf, "<p>your browser sent a bad request,");
	Send (client, buf, sizeof (BUF), 0);
	sprintf (BUF, "such as a POST without a content-length.\r\n");
Send (client, buf, sizeof (BUF), 0); }/**********************************************************************//* Notifies the client that the CGI script cannot be executed on a $ * parameter:the Client socket descriptor. *//**********************************************************************/void Cannot_execute (int client) {Char
	BUF[1024]; /* Feedback Error message */sprintf (buf, "http/1.0Internal Server error\r\n ");
	Send (client, buf, strlen (BUF), 0);
	sprintf (buf, "content-type:text/html\r\n");
	Send (client, buf, strlen (BUF), 0);
	sprintf (buf, "\ r \ n");
	Send (client, buf, strlen (BUF), 0);
	sprintf (buf, "<p>error prohibited CGI execution.\r\n");
Send (client, buf, strlen (BUF), 0); }/**********************************************************************//* Print error message, see Advanced Programming for UNIX environment and terminate *//********
	/void Error_die (const char *SC) {perror (SC);
Exit (1); }/**********************************************************************//* Returns client 404 error message 404 (404 of all evils) *//*********

	/void Not_found (int client) {char buf[1024];
	sprintf (buf, "http/1.0 404 Not Found\r\n");
	Send (client, buf, strlen (BUF), 0);
	sprintf (buf, server_string);
	Send (client, buf, strlen (BUF), 0);
	sprintf (buf, "content-type:text/html\r\n"); Send (client, buf, strlen (BUF), 0);
	sprintf (buf, "\ r \ n");
	Send (client, buf, strlen (BUF), 0);
	sprintf (buf, " 

Reading data from a file to the client

/**********************************************************************//* Put the entire contents of a file out on a Soc  Ket. This function * was named after the UNIX "cat" command, because it might has been * easier just to doing something like Pip
 E, fork, and exec ("cat"). * Parameters:the Client Socket Descriptor * File pointer for the file to cat *//*unix shell command Cat FILE Print Data in file files *//**********************************************************************//*

 Sends data from the file structure pointer resource to client*/void cat (int client, file *resource) {char buf[1024]; Fgets (buf, sizeof (BUF), Resource);//Read data from the file structure pointer resource, save to BUF//process the remaining characters in the file stream while (!feof (Resource))//detect the file terminator on the stream, End of File returns non 0 value, end return 0 {send (client, buf, strlen (BUF), 0);//The characters in the stream are all sent to client fgets (buf, sizeof (BUF), Resource);/* from the file struct body pointer
                                     Resource reads up to bufsize-1 data (the BufSize Word assigned ' + ') reads one line at a time, if not enough bufsize, The end of the line is read.
            Here, the feof function is passed to determine if the fgets is terminated by an error.                         In addition, there is a file offset position, and the next read will continue from the last round of reading */}} 

Return file information to client

/**********************************************************************//* Return the informational HTTP headers About a file. */* parameters:the socket to print the headers on * The name of the file */* Returns the header information of the files *//*****************
	/void headers (int client, const char *filename) {char buf[1024];  (void) filename;
	/* could use filename to determine file type */strcpy (buf, "http/1.0 ok\r\n");
	Send (client, buf, strlen (BUF), 0);
	strcpy (buf, server_string);
	Send (client, buf, strlen (BUF), 0);
	sprintf (buf, "content-type:text/html\r\n");
	Send (client, buf, strlen (BUF), 0);
	strcpy (buf, "\ r \ n");
Send (client, buf, strlen (BUF), 0);  }/**********************************************************************//* Send a regular file to the client.
Use headers, and report * Errors to client if they occur.          * parameters:a Pointer to a file structure produced from the socket * File descriptor *   The name of the file to serve/* * Returns the data for the static page return *//*****************************************************************
	/void Serve_file (int client, const char *filename) {file *resource = NULL;
	int numchars = 1;

	Char buf[1024]; Buf[0] = ' A ';
	Buf[1] = ' + '; while ((NumChars > 0) && strcmp ("\ n", buf)/*/Read & Discard headers */numchars = get_line (client, buf

	, sizeof (BUF)); Resource = fopen (filename, "R");//read-only open file if (resource = = NULL) not_found (client);//If the file does not exist, return 404 error else {headers (client, filename);//First return file header information Cat (client, Resource);//Send data from resource descriptor specified file to client} fclose (Resource);//Close}
The following is the core Code section of the TYNYHTTPD server side.

In order to better understand the source code, the HTTP request message format is presented here


Server-side socket initialization settings

/**********************************************************************//* This function starts the process of  Listening for Web connections * on a specified port.
If the port is 0, then dynamically allocate a * port and modify the original port variable to reflect the actual * port. * Parameters:pointer to variable containing the port to connect on * returns:the Socket *//****************************
	/* Server-side socket initialization setting */int startup (U_short *port) {int httpd = 0;

	struct sockaddr_in name;
	httpd = socket (pf_inet, sock_stream, 0);//create server-side socket if (httpd = =-1) error_die ("socket");
	memset (&name, 0, sizeof (name)); name.sin_family = af_inet;//address cluster Name.sin_port = htons (*port);//Specify Port NAME.SIN_ADDR.S_ADDR = htonl (inaddr_any);//Wildcard address if
	(Bind (httpd, (struct sockaddr *) &name, sizeof (name)) < 0)//bind to the specified address and port Error_die ("bind");
		if (*port = = 0)/* If dynamically allocating a port *///is dynamically assigned one of the ports {int namelen = sizeof (name); /* in thePort number 0 After calling bind, GetSockName is used to return the local port number given by the kernel */if (GetSockName (httpd, (struct sockaddr *) &name, &namelen) = =-1) er
		Ror_die ("GetSockName"); *port = Ntohs (name.sin_port);//The network byte order is converted to host byte order, which returns the host byte-order representation of the number of} if (Listen (httpd, 5) < 0)//server listens for client requests.
	Maximum number of connections queued for sockets 5 Error_die ("Listen");
return (HTTPD); }
Receiving client's request message
/**********************************************************************//* A request have caused a call to accept () on th  E Server port to * return.
 Process the request appropriately. * Parameters:the socket connected to the client *//****************************************************************** //**********************************************************************//* HTTP protocol specifies that requests are sent from the client,
 The last server-side response should be requested and returned. * This is the current HTTP protocol, the server does not support the active response, so the current HTTP * Protocol version is based on the client request, and then respond to this model. The//*accept_request function parses the client request, determines whether the request is a static file or a CGI code (as determined by the request type and parameters), and if it is a static file, outputs the file to the front end, and if it is CGI, it enters the CGI processing function *//**************
 /void Accept_request (int client) {char buf[1024];
 int numchars;
 Char method[255];//request method Get or POST char url[255];//requested file path char path[512];//file relative path size_t i, J;
 struct STAT st;      int cgi = 0;

 /* becomes true if server decides this is a CGI * program */char *query_string = NULL; NumChars = Get_line (clieNT, buf, sizeof (BUF));//reads the specified size data from the client to buf i = 0;
 j = 0; Parsing the client's HTTP request message/* Receive character processing: Extract the characters before the space character, up to 254 */while (!
 ISspace (Buf[j]) && (I < sizeof (method)-1)) {Method[i] = buf[j];//According to the HTTP request message format, here is the request method i++; j + +;

 } Method[i] = ' + '; Ignores case comparison string, which is used to determine what type if (strcasecmp (method, "GET") && strcasecmp (method, "POST")) {unimplemented (client);//
 Both methods are not, informing the client that the method requested did not implement return;
 } if (strcasecmp (method, "POST") = = 0)//post type CGI = 1;//set flag bit i = 0;

while (ISspace (Buf[j]) && (J < sizeof (BUF))//Filter space character, the space is followed by the URL J + +; /* The non-whitespace characters in the buf are dumped into the URL buffer, with a space character or full exit */while (! ISspace (Buf[j]) && (i < sizeof (URL)-1) && (J < sizeof (BUF))) {Url[i] = buf[j];//Gets the URL (Internet standard The address of the source) i++;
 j + +;

 } Url[i] = ' + '; if (strcasecmp (method, "GET") = = 0)//get method {query_string = url;//request information while (*query_string! = '? ') && (*query_string! = ') ") Intercept '? ' The preceding character query_string++;//the question mark before the path, followed by the parameter if (*query_string = = '?')
   Have '? ', indicating dynamic request {CGI = 1;
   *query_string = ' + ';
  query_string++; }}//The following is the file sprintf (path, "htdocs%s", url) under the Htdocs file of the TINYHTTPD project;//Gets the request file path if (Path[strlen (path)-1] = = '/')//If the file type is Record (/), add index.html strcat (path, "index.html");//////////////////////////////////////////////////////////
   , the file did not find/* Discard all headers information */while ((NumChars > 0) && strcmp ("\ n", buf) */Read & Discard Headers */ NumChars = get_line (client, buf, sizeof (BUF));//Read data from client to BUF not_found (client);//respond to clients not found} else//get file information, execute success {/
  * If it is a directory, the default is to use the directory under the index.html file */if ((St.st_mode & s_ifmt) = = S_ifdir) strcat (Path, "/index.html");
      if ((St.st_mode & s_ixusr) | |
      (St.st_mode & s_ixgrp) | |
   (St.st_mode & S_ixoth))
  cgi = 1; if (!cgi)//static page request Serve_file (client, path);//return file information directly to the client, static page return else//dynamic page request execute_cgi (client, path, method, Qu ery_string);//execute CGI script} close (client);//close the terminal socket}
Execute CGI script, dynamic page request
/**********************************************************************//* Execute CGI (public NIC interface) script, need to set the appropriate environment variable * Parameters:client Socket Descriptor * Path to the CGI script *//*execute_cgi function is responsible for passing the request to the CGI program processing, between the server and the CGI through the pipeline Pipe communication, first initialize two pipelines, and create a sub-process to execute the CGI function////////////////child process to execute the CGI program, get the standard output of CGI to the parent process through the pipeline, sent by the parent process to the client//******************************* /void execute_cgi (int client, const char *path, const char *method, const char *Q
	uery_string) {char buf[1024];
	int cgi_output[2];
	int cgi_input[2];
	pid_t pid;
	int status;
	int i;
	char c;
	int numchars = 1;

	int content_length =-1; Buf[0] = ' A ';
	Buf[1] = ' + '; if (strcasecmp (method, "GET") = = 0)//get method: Generally used to get/query resource information while ((NumChars > 0) && strcmp ("\ n", buf)/* Read & Discard Headers Read and discard HTTP requests */NumChars = get_line (client, buf, sizeof (BUF));//read from client else/* POST is typically used to update resource information

		*/{NumChars = get_line (client, buf, sizeof (BUF)); Gets the transmission length of the HTTP message entity while ((NumChars > 0) && strcmp ("\ n", buf))//Is not empty and is not a newline character {buf[15] = ' + '; if (strcasecmp (buf, "content-length:") = = 0)//Is the content-length field content_length = Atoi (& (buf[16]));//content-len
		Gth is used to describe the transmission length of an HTTP message entity NumChars = get_line (client, buf, sizeof (BUF));
		if (content_length = =-1) {bad_request (client);//The requested page data is empty, no data, that is, we open the page often appear blank page return;

	}} sprintf (buf, "http/1.0 ok\r\n");//Send (client, buf, strlen (BUF), 0);
		Build pipeline, two channels cgi_output[0]: Read end, cgi_output[1]: Write End if (pipe (Cgi_output) < 0) {Cannot_execute (client);//pipeline Setup failed, print error message
	Return
		}//pipelines can only be performed between processes that have a common ancestor, and here is the parent-child process between if (pipe (Cgi_input) < 0) {Cannot_execute (client);
	Return
		}//fork the child process, creating an IPC channel if (PID = fork ()) < 0) {Cannot_execute (client) between parent and child processes;
	Return }//Implement the inter-process pipeline communication mechanism/* The child process inherits the pipe of the parent process, then writes the input pipeline by closing the output of the child process outputs pipeline, closing the write side of the output pipeline of the parent process, and outputting the input pipeline//Sub-process, if (PID =
		= 0)/* child:cgi script */{char meth_env[255];
		Char query_env[255];

	Char length_env[255];	Copy the file handle, redirect the process's standard input output//dup2 the first parameter descriptor off DUP2 (cgi_output[1], 1);//Standard output redirects to the output pipeline's write End dup2 (Cgi_input[0], 0);//Standard input weight Directed to the read side of the input pipeline close (cgi_output[0]),//Close the write end of the output pipeline close (cgi_input[1]),//Close the output sprintf (meth_env, "Request_method
		=%s ", method);
		Putenv (METH_ENV); if (strcasecmp (method, "GET") = = 0) {//get/* Set environment variable for QUERY_STRING */sprintf (query_env, "query_string=%s", Query_str
			ing);
		Putenv (QUERY_ENV);
			} else {/* POST/////* Set environment variable for CONTENT_LENGTH */sprintf (length_env, "content_length=%d", content_length);
		Putenv (LENGTH_ENV); } execl (path, path, NULL),//exec function cluster, execute CGI script, get the standard output of CGI as corresponding content to the client//through DUP2 redirection, the standard output content enters the input end of the pipeline output exit (0);//child process
		Exit} else {/* Parent/close (cgi_output[1]);//Close one end of the pipe so that the pipeline communication between parent-child processes can be established close (cgi_input[0]); /* Create a single-duplex channel between the parent and child processes by closing the channel for the corresponding pipe and redirecting a side of the child process if it is not redirected, it will be a typical full-duplex pipe communication mechanism */if (strcasecmp (method, "POST") = = 0)//pos T mode, send the specified good transmission length character/* To receive POST data */for (i = 0; i < content_length; i++{recv (client, &c, 1, 0);//Receive single character write (Cgi_input[1], &c, 1) from the client,//write input and redirect to standard input//data transfer process: input[1 ] (parent process)--input[0] (subprocess) [Execute CGI function]--> stdin--> STDOUT//--> output[1] (child process)--output[0] (parent process) [Send results to Guest User]} while (read (cgi_output[0], &c, 1) > 0)//read output of the pipeline to the client, output outputs for CGI scripts after the execution of the content send (client, &c, 1, 0);//The result of CGI execution is sent to the client, that is, send to the browser, if not post then only this processing close (cgi_output[0]);//Close the remaining pipe end, the child process after the execution of Dup2, has closed the pipeline one end channel close
		(Cgi_input[1]); Waitpid (PID, &status, 0);//waiting for child process to terminate}}
The above parent-child process of pipeline communication can be represented by the following diagram: The parent-child process, division of labor, through the pipeline to establish communication channels.

The final complete state above is the post mode, if not the post mode, then only the output[0]--> client.
This is the server-side program: Here is a simple list:
#define ISSPACE (x) ISspace ((int) (x))//If x is a space character, returns true

#define Server_string "server:jdbhttpd/0.1.0\r\n"

void accept_request (int);//The client sends a request to the server
void bad_request (int);//tells the client that a request error has been made, and that the $
void cat (int, FILE *);// Reads the file and sends it to the client
void Cannot_execute (int);//Notifies the client that the CGI script (perl)
void Error_die (const char *) cannot be executed;//print error message
void execute_cgi (int, const char *, const char *, const char *);//execute CGI script, internally call EXEC function cluster
int get_line (int, char *, int);//slave socket Reads the data, returns the number of characters read to
void headers (int, const char *);//returns the HTTP header file information
void not_found (int);//Notifies the client that the page was not found, 404
void Serve_file (int, const char *);//Send message to Client for static page return
int startup (u_short *);//server-side socket settings, create, Bind, listen (TCP protocol)
void unimplemented (int);//Notifies the client that the requested network method is not implemented (GET, POST)
The following is the server-side MAIN.C
int Main (void) {int server_sock =-1;
 U_short port = 0;//Incoming ports are 0, int client_sock =-1;
 struct sockaddr_in client_name;
 int client_name_len = sizeof (client_name);

 pthread_t Newthread;

 Server_sock = Startup (&port);//server-side Listen socket settings printf ("HTTPd running on port%d\n", port); /* Multithreaded Concurrent Server model */while (1) {///main thread Client_sock = Accept (Server_sock, (struct sockaddr *) &cl
 Ient_name, &client_name_len);//blocking waits for a client connection request if (Client_sock = =-1) error_die ("accept"); /* Accept_request (Client_sock); */if (Pthread_create (&newthread, NULL, Accept_request, client_sock)! = 0)//Create worker thread, execute callback function accept_request, parameter Client_
 Sock perror ("Pthread_create");
} close (Server_sock);//Close the socket, in the case of the protocol stack, close the TCP connection return (0); }

From the above we can work out Tinyhttp workflow: Server start, specify port or random Select port bind httpd service, listen for client connection request. (the startup function) receives the HTTP request from the client, deriving a thread to the corresponding client request (multithreaded server model), i.e. executing the Accept_request function server-side parsing client HTTP request message. Determine what method (get or POST) it is and get the URL. For the GET method, if you carry arguments, the query_string pointer points to the URL? The following GET parameter (HTTP protocol) copies the URL data to the path array, indicating the server file path requested by the browser, in TINYHTTPD the server file is under the Htdocs folder, if the URL ends with/ , or the URL itself is a directory (STA T file information), the default is to add index.html to the path, which means accessing the home page. If the file path is valid, if it is a static page access, output the server file directly to the browser, which is written to the client socket in HTTP format, and then jump to. If it is a dynamic page request (with the Get mode, post method, utl as the executable file), then go to invoke the EXCUTE_CGI function to execute the CGI script. Read the entire HTTP request and discard it, and if it is POST, find the content-length. Write the "http/1.0-ok\r\n" status code to the socket. Build two pipelines, cgi_input and cgi_output, and fork a process (must fork the sub-process, pipe pipeline only makes sense). Establish a communication mechanism between parent-child processes. In a child process, the pipeline under its process is redirected, and the corresponding environment variables (method, Query_string, content_length) are set to invoke the CGI script, and then the CGI script is run with EXECL to see that the CGI The execution of the script is performed in the child process, and the result is returned to the parent process through the pipeline and redirection. In the parent process, close the end of the pipe, if it is a post, write the post data to Cgi_intput, have been redirected to STDIN, read the Cgi_output pipeline output to the client (browser output), see the above pipeline final state diagram. Then close all the pipes and wait for the child process to finish. Close the connection to complete an HTTP request and response. HTTP is non-connected, in theThere is no need to establish a dedicated HTTP application-layer session connection before the WEB application, just use the transport layer to establish a good TCP transport connection for it. That is, although it is unreliable, no connection protocol, but the use of reliable TCP Transport layer protocol, so from the point of view of data transmission, HTTP message transmission is still reliable.

It is worth noting that this project is not directly in the Linux environment compiled to run, it was originally implemented on the Solaris, need to modify a few places, due to the length of the problem, the next tinyhttpd in Linux under the compilation of a modified place and the final run test results.

If wrong, welcome to point out, exchange progress, thank you.



Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.