http server 之tinyhttpd源码学习

1 篇文章 0 订阅

下面是tinyhttpd-0.1.0的httpd.c的源码及个人增加的代码注释,有些注释有空补上。。


/* J. David's webserver */

/* This is a simple webserver.

 * Created November 1999 by J. David Blackstone.

 * CSE 4344 (Network concepts), Prof. Zeigler

 * University of Texas at Arlington

 */

/* This program compiles for Sparc Solaris 2.6.

 * To compile for Linux:

 *  1) Comment out the #include <pthread.h> line.

 *  2) Comment out the line that defines the variable newthread.

 *  3) Comment out the two lines that run pthread_create().

 *  4) Uncomment the line that runs accept_request().

 *  5) Remove -lsocket from the Makefile.

 */

#include <stdio.h>

#include <sys/socket.h>

#include <sys/types.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <unistd.h>

#include <ctype.h>

#include <strings.h>

#include <string.h>

#include <sys/stat.h>

#include <pthread.h>

#include <sys/wait.h>

#include <stdlib.h>

#include <errno.h>

#define ISspace(x) isspace((int)(x))


#define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"


void accept_request(int);

void bad_request(int);

void cat(int,FILE *);

void cannot_execute(int);

void error_die(constchar *);

void execute_cgi(int,const char *,const char *,const char *);

int get_line(int,char *, int);

void headers(int,const char *);

void not_found(int);

void serve_file(int,const char *);

int startup(u_short *);

void unimplemented(int);


/**********************************************************************/

/* A request has caused a call to accept() on the server port to

 * return.  Process the request appropriately.

 * Parameters: the socket connected to the client */

/**********************************************************************/

void accept_request(int client)

{

    char buf[1024];//子线程处理http请求

   int numchars;

   char method[255];

   char url[255];

   char path[512];

   size_t i, j;

   struct stat st;//文件描述结构体

    

    /* becomes true if server decides this is a CGI program */

   int cgi = 0;

    

   char *query_string = NULL;

    //获取请求的每行数据

    numchars =get_line(client, buf, sizeof(buf));

    

    i =0; j = 0;

    

    //method赋给method

   while (!ISspace(buf[j]) && (i <sizeof(method) - 1))

    {

        method[i] = buf[j];

        i++; j++;

    }

    method[i] ='\0';

    

   if (strcasecmp(method,"GET") && strcasecmp(method,"POST"))

    {

        unimplemented(client);//如果method不是POST或者GET,则报不支持该方法。

       return;

    }

    //如果methodpost的,说明是调用cgi,因此cgi=1

   if (strcasecmp(method,"POST") == 0)

        cgi =1;

    

    i =0;

    //跳过Method后面的空白

   while (ISspace(buf[j]) && (j <sizeof(buf)))

        j++;

    //Mehod后面的方法赋值给url

   while (!ISspace(buf[j]) && (i <sizeof(url) - 1) && (j <sizeof(buf)))

    {

        url[i] = buf[j];

        i++; j++;

    }

    url[i] ='\0';

    

   if (strcasecmp(method,"GET") == 0)

    {   //处理url的参数值,即'?'后面的查询条件

        query_string = url;

       while ((*query_string != '?') && (*query_string != '\0'))

            query_string++;

       if (*query_string == '?')

        {

            cgi =1;

            *query_string ='\0';

            query_string++;

        }

    }

    //这里的path表示请求资源的绝对地址

    sprintf(path,"/Users/xxxx/xxx/lib/tinyhttpd-0.1.0/htdocs%s", url);

    //如果path没有给出具体文件,默认请求的是index.html

   if (path[strlen(path) -1] == '/'){

       strcat(path, "index.html");

    }

    

    //判断资源是否存在,不存在时值为-1

   int result = stat(path, &st);

   if (result == -1) {

        //循环跳过请求头信息

       while ((numchars > 0) && strcmp("\n", buf)){

            numchars =get_line(client, buf, sizeof(buf));

        }

        //提示资源找不到。

       not_found(client);

    }

   else

    {

        //如果是目录,则默认加上index.html,即默认首页是index.html

       if ((st.st_mode &S_IFMT) == S_IFDIR)

           strcat(path, "/index.html");

        //请求调用cgi

       if ((st.st_mode &S_IXUSR) ||

            (st.st_mode &S_IXGRP) ||

            (st.st_mode &S_IXOTH)    )

            cgi =1;

       if (!cgi)

           serve_file(client, path);

       else

           execute_cgi(client, path, method, query_string);

    }

    

   close(client);

}


/**********************************************************************/

/* Inform the client that a request it has made has a problem.

 * Parameters: client socket */

/**********************************************************************/

void bad_request(int client)

{

   char buf[1024];

    

    sprintf(buf,"HTTP/1.0 400 BAD 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);

}


/**********************************************************************/

/* Put the entire contents of a file out on a socket.  This function

 * is named after the UNIX "cat" command, because it might have been

 * easier just to do something like pipe, fork, and exec("cat").

 * Parameters: the client socket descriptor

 *             FILE pointer for the file to cat */

/**********************************************************************/

void cat(int client,FILE *resource)

{

    //返回文件数据

   char buf[1024];

    

   fgets(buf, sizeof(buf), resource);

   while (!feof(resource))

    {

       send(client, buf, strlen(buf), 0);

       fgets(buf, sizeof(buf), resource);

    }

}


/**********************************************************************/

/* Inform the client that a CGI script could not be executed.

 * Parameter: the client socket descriptor. */

/**********************************************************************/

void cannot_execute(int client)

{

    //返回500服务器内部错误

   char buf[1024];

    

    sprintf(buf,"HTTP/1.0 500 Internal 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 out an error message with perror() (for system errors; based

 * on value of errno, which indicates system call errors) and exit the

 * program indicating an error. */

/**********************************************************************/

void error_die(constchar *sc)

{

   perror(sc);

   exit(1);

}


/**********************************************************************/

/* Execute a CGI script.  Will need to set environment variables as

 * appropriate.

 * Parameters: client socket descriptor

 *             path to the CGI script */

/**********************************************************************/

void execute_cgi(int client,const char *path,

                const char *method,const char *query_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] ='\0';

   if (strcasecmp(method,"GET") == 0)

       while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */

            numchars =get_line(client, buf, sizeof(buf));

    else   /* POST */

    {

        numchars =get_line(client, buf, sizeof(buf));

       while ((numchars > 0) && strcmp("\n", buf))

        {

            buf[15] ='\0';

           if (strcasecmp(buf,"Content-Length:") == 0)

                content_length =atoi(&(buf[16]));

            numchars =get_line(client, buf, sizeof(buf));

        }

       if (content_length == -1) {

           bad_request(client);

           return;

        }

    }

    

    sprintf(buf,"HTTP/1.0 200 OK\r\n");

   send(client, buf, strlen(buf), 0);

    

   if (pipe(cgi_output) <0) {

       cannot_execute(client);

       return;

    }

   if (pipe(cgi_input) <0) {

       cannot_execute(client);

       return;

    }

    

   if ( (pid = fork()) <0 ) {

       cannot_execute(client);

       return;

    }


    //这里采用fork来执行

    if (pid ==0)  /* child: CGI script */

    {

       char meth_env[255];

       char query_env[255];

       char length_env[255];

        

       dup2(cgi_output[1],1);

       dup2(cgi_input[0],0);

       close(cgi_output[0]);

       close(cgi_input[1]);

       sprintf(meth_env, "REQUEST_METHOD=%s", method);

       putenv(meth_env);

       if (strcasecmp(method,"GET") == 0) {

           sprintf(query_env, "QUERY_STRING=%s", query_string);

           putenv(query_env);

        }

       else {   /* POST */

           sprintf(length_env, "CONTENT_LENGTH=%d", content_length);

           putenv(length_env);

        }

       execl(path, path, NULL);

       exit(0);

    }else {    /* parent */

       close(cgi_output[1]);

       close(cgi_input[0]);

       if (strcasecmp(method,"POST") == 0)

           for (i = 0; i < content_length; i++) {

               recv(client, &c, 1,0);

               write(cgi_input[1], &c,1);

            }

       while (read(cgi_output[0], &c,1) > 0)

           send(client, &c, 1,0);

        

       close(cgi_output[0]);

       close(cgi_input[1]);

       waitpid(pid, &status, 0);

    }

}


/**********************************************************************/

/* Get a line from a socket, whether the line ends 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 is terminated with a null.  If any of

 * the above three line terminators is read, the last character of the

 * string will be a linefeed and the string will be terminated with a

 * null character.

 * Parameters: the socket descriptor

 *             the buffer to save the data in

 *             the size of the buffer

 * Returns: the number of bytes stored (excluding null) */

/**********************************************************************/

int get_line(int sock,char *buf, int size)

{

   int i = 0;

   char c = '\0';

   int n;

    

   while ((i < size - 1) && (c != '\n'))

    {

        n =recv(sock, &c, 1,0);

        /* DEBUG printf("%02X\n", c); */

       if (n > 0)

        {

           if (c == '\r')

            {

                n =recv(sock, &c, 1,MSG_PEEK);

                /* DEBUG printf("%02X\n", c); */

               if ((n > 0) && (c =='\n'))

                   recv(sock, &c, 1,0);

               else

                    c ='\n';

            }

            buf[i] = c;

            i++;

        }

       else

            c ='\n';

    }

    buf[i] ='\0';

    

   return(i);

}


/**********************************************************************/

/* Return the informational HTTP headers about a file. */

/* Parameters: the socket to print the headers on

 *             the name of the file */

/**********************************************************************/

void headers(int client,const char *filename)

{

   char buf[1024];

    //传进请求资源文件,是为了可以使用该文件的信息来决定返回响应头的文件类型,这里没使用,直接返回content-type:text/html

    (void)filename; /* could use filename to determine file type */

    

    strcpy(buf,"HTTP/1.0 200 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);

}


/**********************************************************************/

/* Give a client a 404 not found status message. */

/**********************************************************************/

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,"<HTML><TITLE>Not Found</TITLE>\r\n");

   send(client, buf, strlen(buf), 0);

    sprintf(buf,"<BODY><P>The server could not fulfill\r\n");

   send(client, buf, strlen(buf), 0);

    sprintf(buf,"your request because the resource specified\r\n");

   send(client, buf, strlen(buf), 0);

    sprintf(buf,"is unavailable or nonexistent.\r\n");

   send(client, buf, strlen(buf), 0);

    sprintf(buf,"</BODY></HTML>\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 */

/**********************************************************************/

void serve_file(int client,const char *filename)

{

   FILE *resource = NULL;

   int numchars = 1;

   char buf[1024];

    

    //下面这里似乎做无用功,因为这只是跳过请求头信息

    buf[0] ='A'; buf[1] ='\0';

   while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */

        numchars =get_line(client, buf, sizeof(buf));

    

    //打开请求资源文件,

    resource =fopen(filename, "r");

   if (resource == NULL)

       not_found(client);

   else

    {

       headers(client, filename);

       cat(client, resource);

    }

   fclose(resource);

}


/**********************************************************************/

/* 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 */

/**********************************************************************/

int startup(u_short *port)

{

   int httpd = 0;

   struct sockaddr_in name;

    

    httpd =socket(PF_INET,SOCK_STREAM0);

   if (httpd == -1)

       error_die("socket");

   memset(&name, 0,sizeof(name));

    name.sin_family =AF_INET;

    name.sin_port =htons(*port);

    name.sin_addr.s_addr =htonl(INADDR_ANY);

   if (bind(httpd, (structsockaddr *)&name, sizeof(name)) < 0)

       error_die("bind");

    if (*port ==0)  /* if dynamically allocating a port */

    {

       int namelen = sizeof(name);

       if (getsockname(httpd, (structsockaddr *)&name, &namelen) == -1)

           error_die("getsockname");

        *port =ntohs(name.sin_port);

    }

   if (listen(httpd,5) < 0)

       error_die("listen");

   return(httpd);

}


/**********************************************************************/

/* Inform the client that the requested web method has not been

 * implemented.

 * Parameter: the client socket */

/**********************************************************************/

void unimplemented(int client)

{

   char buf[1024];

    

    sprintf(buf,"HTTP/1.0 501 Method Not Implemented\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,"<HTML><HEAD><TITLE>Method Not Implemented\r\n");

   send(client, buf, strlen(buf), 0);

    sprintf(buf,"</TITLE></HEAD>\r\n");

   send(client, buf, strlen(buf), 0);

    sprintf(buf,"<BODY><P>HTTP request method not supported.\r\n");

   send(client, buf, strlen(buf), 0);

    sprintf(buf,"</BODY></HTML>\r\n");

   send(client, buf, strlen(buf), 0);

}


/**********************************************************************/


//用于输出客户端连接信息

void PrintSocketAddress(conststruct sockaddr *address,FILE *stream) {

    // Test for address and stream

   if (address == NULL || stream ==NULL)

       return;

    

    void *numericAddress;// Pointer to binary address

    // Buffer to contain result (IPv6 sufficient to hold IPv4)

   char addrBuffer[INET6_ADDRSTRLEN];

    in_port_t port;// Port to print

    // Set pointer to address based on address family

   switch (address->sa_family) {

       case AF_INET:

            numericAddress = &((structsockaddr_in *) address)->sin_addr;

            port =ntohs(((structsockaddr_in *) address)->sin_port);

           break;

       case AF_INET6:

            numericAddress = &((structsockaddr_in6 *) address)->sin6_addr;

            port =ntohs(((structsockaddr_in6 *) address)->sin6_port);

           break;

       default:

           fputs("[unknown type]", stream);// Unhandled type

           return;

    }

    // Convert binary to printable address

   if (inet_ntop(address->sa_family, numericAddress, addrBuffer,

                 sizeof (addrBuffer)) == NULL)

       fputs("[invalid address]", stream);// Unable to convert

   else {

       fprintf(stream, "来自%s", addrBuffer);

        if (port !=0// Zero not valid in any socket addr

           fprintf(stream, "-%u\n", port);

        

    }

}





int main(int argc,char *argv[])

{

   int server_sock = -1;

   u_short port = 8081;

   int client_sock = -1;

   struct sockaddr_in client_name;

   int client_name_len = sizeof(client_name);

   pthread_t newthread;

    

    server_sock =startup(&port);

    printf("httpd running on port %d\n", port);

    

    

   while (1)

    {

        client_sock =accept(server_sock, (structsockaddr *)&client_name, &client_name_len);

        

        //这里的if代码段是我增加,不加会报错"Interrupted system call"

       if (client_sock == -1){

           if (errno ==EINTR){

               continue;

            }

           error_die("accept");

        }

//这个函数原版本没有,其他版本有,因此补上,函数功能主要用于输出客户端连接信息。

       PrintSocketAddress((structsockaddr*) &client_name, stdout);

        

        /* accept_request(client_sock); */

       if (pthread_create(&newthread ,NULLaccept_request, client_sock) !=0){

            perror("pthread_create");

        }

    }

    

   close(server_sock);

    

   return(0);

}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值