Tiny Web server code analysis

Source: Internet
Author: User

Tiny Web server code analysis

A small but fully functional web server called Tiny has been developed in "deep understanding of computer systems". Here is the source code parsing of the Tiny server.

1. Tiny main program

Tiny is an iterative server that calls the Open_listenfd () function to open a listening socket through the port value passed in the command line, and then Tiny executes an infinite loop: the server is blocked in accept, wait for the connection request on the listener descriptor listenfd. When the server returns connfd from accept, it indicates that it has established a connection with the client, executed the transaction, and closed the end of the connection, perform the next loop.

#include "csapp.h"void doit(int fd);void read_requesthdrs(rio_t *rp);int parse_uri(char *uri, char *filename, char *cgiargs);void serve_static(int fd, char *filename, int filesize);void get_filetype(char *filename, char *filetype);void serve_dynamic(int fd, char *filename, char *cgiargs);void clienterror(int fd, char *cause, char *errnum,                char *shorting,char *longmsg);int main(int argc,char *argv[]){    int listenfd,connfd,port,clientlen;    struct sockaddr_in clientaddr;    if(argc != 2)    {        fprintf(stderr,"usage: %s <port>\n",argv[0]);        exit(0);    }    port = atoi(argv[1]);    listenfd = Open_listenfd(port);    while(1)    {        clientlen = sizeof(clientaddr);        connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen);        doit(connfd);        Close(connfd);    }}
2. doit () handles HTTP transactions

First, let's take a look at the composition of HTTP requests. An HTTP request is composed of one request line followed by zero or more request headers, and an empty text line followed to terminate the header list.
The format of the HTTP request line is as follows.

<method><uri><version>

HTTP supports many methods, including GET, POST, OPTIONS, HEAD, PUT, DELETE, and TRACE. Currently, Tiny only supports the GET method. The GET method guides the server to generate and return the content of the URI identifier. URI is the suffix of a URL, including the file name and parameters. The version field indicates the HTTP Version followed by the request, including HTTP/1.0 and HTTP/1.1.
Doit () processes HTTP transactions.
Read and parse the request line. In the code, use Rio_readlineb () to read a row of data from fd to buf, and then write the variable method, uri, and version respectively.
Tiny does not use any information in the request header. The read_requesthdrs () function is used to ignore the header information.
Extract URI information from the request, use parse_uri () to extract file names and request parameters from the URI, and return values to identify static or dynamic content. Use stat () to get the file status and save it to sbuf. If the execution succeeds, 0 is returned. If the execution fails,-1 is returned, indicating that the file does not exist on the disk.
If the request is static content, you need to verify that the file is a normal file (st_mode = S_ISREG) and we have read permission. If yes, we will provide static content to the client. Similarly, if dynamic content is requested, You need to verify that the file is an executable file. If so, we provide dynamic content to the client.

void doit(int fd){    int is_static;    struct stat sbuf;    char buf[MAXLINE], method[MAXLINE],uri[MAXLINE],version[MAXLINE];    char filename[MAXLINE],cgiargs[MAXLINE];    rio_t rio;    /*read request line and headers*/    Rio_readinitb(&rio, fd);    Rio_readlineb(&rio, buf, MAXLINE);    sscanf(buf, "%s %s %s", method, uri, version);    if(strcasecmp(method,"GET"))    {        clienterror(fd, method, "501","Not Implemented",            "Tiny does not implement this method");        return;    }    read_requesthdrs(&rio);    /*prase URI from GET request*/    is_static = parse_uri(uri, filename, cgiargs);    if(stat(filename, &sbuf) < 0)    {        clienterror(fd, filename, "404","Not Found",            "Tiny couldn't find this file");        return;    }    if(is_static)//server static content    {        if(!(S_ISREG(sbuf.st_mode) || !(S_IRUSR & sbuf.st_mode)))        {            clienterror(fd, filename, "403","Forbidden",                "Tiny couldn't read the file");            return;        }        serve_static(fd, filename, sbuf.st_size);    }    else//server dynamic content    {        if(!(S_ISREG(sbuf.st_mode) || !(S_IXUSR & sbuf.st_mode)))        {            clienterror(fd, filename, "403","Forbidden",                "Tiny couldn't run the CGI program");            return;        }        serve_dynamic(fd, filename, cgiargs);       }}
3. clienterror () is used to check for some errors.

First, let's take a look at the composition of HTTP Response lines. The HTTP response is similar to the HTTP request. An HTTP response consists of one response line followed by zero or more response headers, one empty line of the termination header followed by the response body. The response line format is:

<version><status code><status message>

The version field describes the HTTP version that the response follows. The status code is a three-digit positive integer that specifies the processing of the request. The status message provides an English description equivalent to the error code.
Clienterror () sends an HTTP response packet to the client. The response line contains the status code and status message. The response body contains an HTML file to explain the error to the user.

void clienterror(int fd, char *cause, char *errnum,                char *shortmsg, char *longmsg){    char buf[MAXLINE], body[MAXBUF];    /*Build the HTTP response body*/    sprintf(body, "
4. read_requesthdrs () is used to skip the request header information until an empty message line indicating the end Of the header is met.
void read_requesthdrs(rio_t *rp){    char buf[MAXLINE];    Rio_readlineb(rp, buf, MAXLINE);    while(strcmp(buf, "\r\n"))    {        Rio_readlineb(rp, buf, MAXLINE);        printf("%s", buf);    }    return;}
5. parse_uri () Parse URI parameters

Parse_uri () parses the URI into a file name and an optional CGI parameter string. For static content, we will clear the CGI parameter string (that is, leave cgiargs blank) and convert the URI to a relative UNIX path name, such as./home.html. If the URI ends with a slash (/), the file name home.html must be added later. For dynamic content, the file name and CGI parameter in the URI are as follows? To extract the CGI parameter and file name.

int parse_uri(char *uri, char *filename, char *cgiargs){    char *ptr;    if(!strstr(uri, "cgi-bin"))//static content    {        strcpy(cgiargs, "");        strcpy(filename, ".");        strcat(filename, uri);        if(uri[strlen(uri)-1] == '/')            strcat(uri, "home.html");        return 1;    }    else    {        ptr = index(uri, '?');        if(ptr)        {            strcpy(cgiargs, ptr+1);            *ptr = '\0';        }        else            strcpy(cgiargs, "");        strcpy(filename, ".");        strcat(filename, uri);        return 0;    }}
6. serve_static () Processing static content

Tiny provides four types of static content: HTML files, unformatted text files, GIF and JPEG images.
The serve_static () function sends an HTTP response. The response body includes the content of a local file. First, check the file name suffix to determine the file type and display it in the Content-type field of the response header. Then we send the response line and response header to the client, and use an empty line to terminate the header.
Then we open the requested file in read-only mode to obtain the file handle srcfd, and map the file to the virtual memory space using the mmap function, in the code, the first filesize byte of the file directed to srcfd is mapped to a read-only private virtual memory area where the address starts from srcp. Once the ing is successful, we can use srcp to operate the file without the file handle, so we disable srcfd. Then we use Rio_writen () to transfer the file to the client. Now that the processing of static content is complete, we release the virtual memory ing of srcp.

void serve_static(int fd, char *filename, int filesize){    int srcfd;    char *srcp, filetype[MAXLINE], buf[MAXBUF];    /*Send response headers to client*/    get_filetype(filename,filetype);    sprintf(buf, "HTTP/1.0 200 OK\r\n");    sprintf(buf, "%sServer: Tiny Web Server\r\n", buf);    sprintf(buf, "%sContent-lenght: %d\r\n", buf, filesize);    sprintf(buf, "%sContent-type: %s\r\n\r\n", buf, filetype);    Rio_writen(fd, buf, strlen(buf));    /*Send response body to client*/    srcfd = Open(filename, O_RDONLY, 0);    srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE,srcfd,0);    close(srcfd);    Rio_writen(fd, srcp, filesize);    Munmap(srcp, filesize);}void get_filetype(char *filename, char *filetype){    if(strstr(filename, ".html"))        strcpy(filetype, "text/html");    else if(strstr(filename, ".gif"))        strcpy(filetype, "image/gif");    else if(strstr(filename, ".jpg"))        strcpy(filetype, "image/jpeg");    else         strcpy(filetype, "text/plain");}
7. serve_dynamic () processes Dynamic Content

Tiny provides various types of dynamic content by deriving a child process and running a CGI program in the child process.
The program first sends a response line indicating success, and also includes the Server header with information to the client. The CGI program is responsible for sending the rest of the response.
Then we derive a sub-process that uses the CGI parameter from the request URI to initialize the QUERY_STRING environment variable. The CGI program obtains the CGI parameter value through this variable. Next, the sub-process redirects the standard output to the connected file descriptor, and then loads and runs the CGI program. Because the CGI program runs in the context of the sub-process, it can access all open files and environment variables that existed before the execve function was called. Therefore, the data written by the CGI program to the standard output will be directly sent to the client process without interference from the parent process. The flagship parent process is blocked in the wait () call. When the child process ends, the resources allocated to the child process by the operating system are reclaimed.

void serve_dynamic(int fd, char *filename, char *cgiargs){    char buf[MAXLINE], *emptylist[]={NULL};    /*Return first part of HTTP response*/    sprintf(buf, "HTTP/1.0 200 OK\r\n");    Rio_writen(fd, buf,strlen(buf));    sprintf(buf, "Server: Tiny Web Server\r\n");    Rio_writen(fd, buf,strlen(buf));    if(Fork()==0)    {        setenv("QUERY_STRING", cgiargs, 1);        Dup2(fd, STDOUT_FILENO);        Execve(filename, emptylist,environ);    }    Wait(NULL);}

In-depth understanding of computer systems (original book version 2nd) PDF clear Chinese Version

Run Tiny server

This article permanently updates the link address:

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.