zoukankan      html  css  js  c++  java
  • tinyhttpd服务器源码学习

    下载地址:http://sourceforge.net/projects/tinyhttpd/

    /* 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>
    
    #define ISspace(x) isspace((int)(x))
    
    #define SERVER_STRING "Server: jdbhttpd/0.1.0
    "
    
    void accept_request(int);
    void bad_request(int);
    void cat(int, FILE *);
    void cannot_execute(int);
    void error_die(const char *);
    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];
        int numchars;
        char method[255];
        char url[255];
        char path[512];
        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));
        i = 0; j = 0;
        while (!ISspace(buf[j]) && (i < sizeof(method) - 1))
        {
            method[i] = buf[j];
            i++; j++;
        }
        method[i] = '';
    
        if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
        {
            unimplemented(client);
            return;
        }
    
        if (strcasecmp(method, "POST") == 0)
        {
            cgi = 1;
        }
    
        i = 0;
        while (ISspace(buf[j]) && (j < sizeof(buf)))
        {
            j++;
        }
        while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf)))
        {
            url[i] = buf[j];
            i++; j++;
        }
        url[i] = '';
    
        if (strcasecmp(method, "GET") == 0)
        {
            query_string = url;
            while ((*query_string != '?') && (*query_string != ''))
            {
                query_string++;
            }
            if (*query_string == '?')
            {
                cgi = 1;
                *query_string = '';
                query_string++;
            }
        }
    
        sprintf(path, "htdocs%s", url);
        if (path[strlen(path) - 1] == '/')
        {
            strcat(path, "index.html");
        }
        if (stat(path, &st) == -1) 
        {
            while ((numchars > 0) && strcmp("
    ", buf))  /* read & discard headers */
            {
                numchars = get_line(client, buf, sizeof(buf));
            }
            not_found(client);
        }
        else
        {
            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)
                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
    ");
        send(client, buf, sizeof(buf), 0);
        sprintf(buf, "Content-type: text/html
    ");
        send(client, buf, sizeof(buf), 0);
        sprintf(buf, "
    ");
        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.
    ");
        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)
    {
        char buf[1024];
    
        sprintf(buf, "HTTP/1.0 500 Internal Server Error
    ");
        send(client, buf, strlen(buf), 0);
        sprintf(buf, "Content-type: text/html
    ");
        send(client, buf, strlen(buf), 0);
        sprintf(buf, "
    ");
        send(client, buf, strlen(buf), 0);
        sprintf(buf, "<P>Error prohibited CGI execution.
    ");
        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(const char *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] = '';
        if (strcasecmp(method, "GET") == 0)
        {
            while ((numchars > 0) && strcmp("
    ", buf))  /* read & discard headers */
            {
                numchars = get_line(client, buf, sizeof(buf));
            }
        }
        else    /* POST */
        {
            numchars = get_line(client, buf, sizeof(buf));
            while ((numchars > 0) && strcmp("
    ", buf))
            {
                buf[15] = '';
                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
    ");
        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;
        }
        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 = '';
        int n;
    
        while ((i < size - 1) && (c != '
    '))
        {
            n = recv(sock, &c, 1, 0);
            /* DEBUG printf("%02X
    ", c); */
            if (n > 0)
            {
                if (c == '
    ')
                {
                    n = recv(sock, &c, 1, MSG_PEEK);
                    /* DEBUG printf("%02X
    ", c); */
                    if ((n > 0) && (c == '
    '))
                        recv(sock, &c, 1, 0);
                    else
                        c = '
    ';
                }
                buf[i] = c;
                i++;
            }
            else
                c = '
    ';
        }
        buf[i] = '';
    
        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];
        (void)filename;  /* could use filename to determine file type */
    
        strcpy(buf, "HTTP/1.0 200 OK
    ");
        send(client, buf, strlen(buf), 0);
        strcpy(buf, SERVER_STRING);
        send(client, buf, strlen(buf), 0);
        sprintf(buf, "Content-Type: text/html
    ");
        send(client, buf, strlen(buf), 0);
        strcpy(buf, "
    ");
        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
    ");
        send(client, buf, strlen(buf), 0);
        sprintf(buf, SERVER_STRING);
        send(client, buf, strlen(buf), 0);
        sprintf(buf, "Content-Type: text/html
    ");
        send(client, buf, strlen(buf), 0);
        sprintf(buf, "
    ");
        send(client, buf, strlen(buf), 0);
        sprintf(buf, "<HTML><TITLE>Not Found</TITLE>
    ");
        send(client, buf, strlen(buf), 0);
        sprintf(buf, "<BODY><P>The server could not fulfill
    ");
        send(client, buf, strlen(buf), 0);
        sprintf(buf, "your request because the resource specified
    ");
        send(client, buf, strlen(buf), 0);
        sprintf(buf, "is unavailable or nonexistent.
    ");
        send(client, buf, strlen(buf), 0);
        sprintf(buf, "</BODY></HTML>
    ");
        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] = '';
        while ((numchars > 0) && strcmp("
    ", 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_STREAM, 0);
        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, (struct sockaddr *)&name, sizeof(name)) < 0)
        {
            error_die("bind");
        }
    
        if (*port == 0)  /* if dynamically allocating a port */
        {
            int namelen = sizeof(name);
            if (getsockname(httpd, (struct sockaddr *)&name, (socklen_t*)&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) // 针对没有实现的方式做的回复(即除了GET和POST)
    {
        char buf[1024];
    
        sprintf(buf, "HTTP/1.0 501 Method Not Implemented
    ");
        send(client, buf, strlen(buf), 0);
        sprintf(buf, SERVER_STRING);
        send(client, buf, strlen(buf), 0);
        sprintf(buf, "Content-Type: text/html
    ");
        send(client, buf, strlen(buf), 0);
        sprintf(buf, "
    ");
        send(client, buf, strlen(buf), 0);
        sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented
    ");
        send(client, buf, strlen(buf), 0);
        sprintf(buf, "</TITLE></HEAD>
    ");
        send(client, buf, strlen(buf), 0);
        sprintf(buf, "<BODY><P>HTTP request method not supported.
    ");
        send(client, buf, strlen(buf), 0);
        sprintf(buf, "</BODY></HTML>
    ");
        send(client, buf, strlen(buf), 0);
    }
    
    /**********************************************************************/
    
    int main(void)
    {
        int server_sock = -1;
        u_short port = 0;
        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
    ", port);
    
        while (1)
        {
            client_sock = accept(server_sock, (struct sockaddr *)&client_name, (socklen_t*)&client_name_len);
            if (client_sock == -1)
            {
                error_die("accept");
            }
            accept_request(client_sock);
            /*
            if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0)
            perror("pthread_create");
            */
        }
    
        close(server_sock);
    
        return(0);
    }
    源代码

    这是一个非常简单的服务器,只是实现了服务器的工作原理。没有考虑其他的性能等问题,全部500行代码左右。

    执行流程:

    1、开启监听端口。

    2、接收客户端连接。

    3、解析客户端连接地址,判断是GET还是POST,或者其他,则返回错误,仅支持GET和POST。

    4、判断是返回一个网页,还是调用CGI脚本,根据传过来的参数执行。

    5、结束。

    其中示例CGI脚本是用perl写的。功能就是根据客户端传过来的颜色值设置将要返回的网页的背景色。

    其实服务器的执行流程或者说工作原理很简单,它其实就是使用socket来通信,但是通信的内容需要符合HTTP协议,即客户端--服务器交互使用的信息需要有HTTP协议头,其中协议头包含了请求的方法(GET、POST),请求的网页或者文件的路径等。

  • 相关阅读:
    HDU 2116 Has the sum exceeded
    HDU 1233 还是畅通工程
    HDU 1234 开门人和关门人
    HDU 1283 最简单的计算机
    HDU 2552 三足鼎立
    HDU 1202 The calculation of GPA
    HDU 1248 寒冰王座
    HDU 1863 畅通工程
    HDU 1879 继续畅通工程
    颜色对话框CColorDialog,字体对话框CFontDialog使用实例
  • 原文地址:https://www.cnblogs.com/lit10050528/p/4611367.html
Copyright © 2011-2022 走看看