zoukankan      html  css  js  c++  java
  • C语言实现webServer

    1.字符串管理模块:

      这个文件主要实现了能够自动扩展并灵活拼接的字符串类型,具体作用可以参考C++的string类型作用。

    /*stringutils.h*/
    #ifndef STRINGUTILS_H
    #define STRINGUTILS_H
    
    #include<stdlib.h>
    
    typedef struct 
    {
        char *ptr;
        size_t size;
        size_t len;
    }string;
    
    string* string_init();
    string* string_init_str(const char *str);
    void string_free(string *s);
    void string_reset(string *s);
    void string_extend(string *s, size_t new_len);
    
    int string_copy_len(string *s, const char *str, size_t str_len);
    int string_copy(string *s, const char *str);
    
    int string_append_string(string *s, string *s2);
    int string_append_int(string *s, int i);
    int string_append_len(string *s, const char *str, size_t str_len);
    int string_append(string *s, const char *str);
    int string_append_ch(string *s, char ch);
    
    #endif
    
    /*stringutils.c*/
    #include<assert.h>
    #include<string.h>
    #include<stdio.h>
    
    #include"stringutils.h"
    
    #define STRING_SIZE_INC 64
    
    string* string_init()
    {
        string *s;
        s = malloc(sizeof(*s));
        s->ptr = NULL;
        s->size = s->len = 0;
        return s;
    }
    string* string_init_str(const char *str)
    {
        string *s = string_init();
        string_copy(s, str);
        return s;
    }
    void string_free(string *s)
    {
        if (!s)
        {
            return;
        }
        free(s->ptr);
        free(s);
    }
    void string_reset(string *s)
    {
        assert( s != NULL);
        if (s->size > 0)
        {
            s->ptr[0] = '';
        }
        s->len = 0;
    }
    void string_extend(string *s, size_t new_len)
    {
        assert(s != NULL);
    
        if (new_len >= s->size)
        {
            s->size += new_len - s->size;
            s->size += STRING_SIZE_INC - (s->size % STRING_SIZE_INC);
            s->ptr = realloc(s->ptr, s->size);
        }
    }
    
    int string_copy_len(string *s, const char *str, size_t str_len)
    {
        assert(s != NULL);
        assert(str != NULL);
    
        if (str_len <= 0) return 0;
    
        string_extend(s, str_len+1);
    
        strncpy(s->ptr, str, str_len);
        s->len = str_len;
        s->ptr[s->len] = '';
    
        return str_len;
    }
    int string_copy(string *s, const char *str)
    {
        return string_copy_len(s, str, strlen(str));
    }
    
    int string_append_string(string *s, string *s2)
    {
        assert(s != NULL);
        assert(s2 != NULL);
    
        return string_append_len(s, s2->ptr,s2->len);
    }
    int string_append_int(string *s, int i)
    {
        assert(s != NULL);
    
        char buf[30];
        char digits[] = "0123456789";
        int len = 0;
        int minus = 0;
    
        if (i < 0)
        {
            minus = 1;
            i *= -1;
        }
        else if (0 == i)
        {
            string_append_ch(s, '0');
            return 1;
        }
    
        while(i)
        {
            buf[len++] = digits[i % 10];
            i = i / 10;
        }
    
        if (minus)
        {
            buf[len++] = '-';
        }
    
        for (int i = len - 1; i >= 0; i--)
        {
            string_append_ch(s, buf[i]);
        }
    
        return len;
        
    }
    int string_append_len(string *s, const char *str, size_t str_len)
    {
        assert(s != NULL);
        assert(str != NULL);
    
        if (str_len <= 0)
        {
            return 0;
        }
    
        string_extend(s, s->len + str_len + 1);
        memcpy(s->ptr + s->len, str, str_len);
        s->len += str_len;
        s->ptr[s->len] = '';
    
        return str_len;
    }
    
    int string_append(string *s, const char *str)
    {
        return string_append_len(s, str, strlen(str));
    }
    int string_append_ch(string *s, char ch)
    {
        assert(s != NULL);
        string_extend(s, s->len + 2);
        s->ptr[s->len++] = ch;
        s->ptr[s->len] = '';
    
        return 1;
    }

    2.配置文件模块:

    读取配置文件功能的模块,采用键值对方式的格式书写配置方式。

    主要注意事项是等号两边空格要被忽略,字符串需要被双引号括起来。

    具体实现思路是逐个字符判断然后拼接对比。

    /*config.h*/
    #ifndef CONFIG_H
    #define CONFIG_H
    
    #include<limits.h>
    
    typedef struct 
    {
        short port;
        char doc_root[PATH_MAX];
    }config;
    
    config* config_init();
    
    void config_free(config *conf);
    
    void config_load(config *conf, const char *fn);
    
    #endif
    
    
    /*config.c*/
    #include<errno.h>
    #include<stdlib.h>
    #include<stdio.h>
    #include<string.h>
    #include<sys/stat.h>
    #include<limits.h>
    
    #include"stringutils.h"
    #include"config.h"
    
    config* config_init()
    {
        config *conf;
        conf = malloc(sizeof(*conf));
        memset(conf, 0, sizeof(*conf));
    
        return conf;
    }
    
    void config_free(config *conf)
    {
        if (!conf)
        {
            return;
        }
    
        free(conf);
    }
    
    void config_load(config *conf, const char *fn)
    {
        char *errormsg;
        struct stat st;
        string *line;
        string *buf;
        string *key;
        string *value;
        FILE *fp;
        int lineno = 0;
        int is_str = 0;
        char ch;
    
        fp = fopen(fn, "r");
        if (!fp)
        {
            fprintf(stderr, "%s: failed to open config file
    ", fn);
            exit(1);
        }
    
        line = string_init();
        key = string_init();
        value = string_init();
        buf = key;
        lineno = 1;
    
        while((ch = fgetc(fp)) != EOF)
        {
            if (ch != '
    ')
            {
                string_append_ch(line, ch);
            }
    
            if(ch == '\')
            {
                continue;
            }    
    
            if (!is_str && (ch == ' ' || ch == '	'))
            {
                continue;
            }
    
            if (ch == '"')
            {
                is_str = (is_str + 1) % 2;
                continue;
            }
    
            if (ch == '=')
            {
                buf = value;
                continue;
            }
    
            if (ch == '
    ')
            {
                if ((key->len == 0 && value->len > 0) ||
                    (value->len == 0 && key->len > 0))
                    {
                        errormsg = "bad syntax";
                        goto configerr;
                    }
    
                if (value->len != 0 && key->len != 0)
                {
                    if (strcasecmp(key->ptr, "port") == 0)
                    {
                        conf->port = atoi(value->ptr);
                        if (conf->port == 0)
                        {
                            errormsg = "invalid port";
                            goto configerr;
                        }
                    }
                    else if (strcasecmp(key->ptr, "document-dir") == 0)
                    {
                        if (stat(value->ptr, &st) == 0)
                        {
                            if (!S_ISDIR(st.st_mode))
                            {
                                errormsg = "invalid directory";
                                goto configerr;
                            }
                        }
                        else
                        {
                            errormsg = strerror(errno);
                            goto configerr;
                        }
    
                        realpath(value->ptr, conf->doc_root);                                       
                    }
                    else
                    {
                        errormsg = "unsupported config setting";
                        goto configerr;
                    }             
            
                }
    
                string_reset(line);
                string_reset(key);
                string_reset(value);
    
                buf = key;
                lineno++;
                continue;
            }
            string_append_ch(buf, ch);
        }
    
        fclose(fp);
        string_free(key);
        string_free(value);
        string_free(line);
    
        return ;
    
    configerr:
        fprintf(stderr, "
    *** FAILED TO LOAD CONFIG FILE ***
    ");
        fprintf(stderr, "at line: %d
    ", lineno);
        fprintf(stderr, ">> '%s' 
    ", line->ptr);
        fprintf(stderr, "%s
    ", errormsg);
        exit(1);
    }

    配置文件加载模块实现了去除额外空格,等于号及换行符。并把解析出来的数据存到config数据结构中。

    3.服务器管理模块是核心模块。

    主要功能是服务器的启动和停止。

    注意点是采用了守护进程方式启动的。

    /*server.h*/
    #ifndef SERVER_H
    #define SERVER_H
    
    #include <unistd.h>
    #include <netdb.h>
    #include <limits.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include<signal.h>
    
    #include "stringutils.h"
    #include "config.h"
    
    typedef struct 
    {
        FILE *logfp;
        int sockfd;
        short port;
        int use_logfile;
        int is_daemon;
        int do_chroot;
        config *conf;
    }server;
    
    typedef enum
    {
        HTTP_METHOD_UNKNOWN = -1,
        HTTP_METHOD_NOT_SUPPORTED = 0,
        HTTP_METHOD_GET = 1,
        HTTP_METHOD_HEAD = 2
    }http_method;
    
    typedef enum
    {
        HTTP_VERSION_UNKNOWN,
        HTTP_VERSION_09,
        HTTP_VERSION_10,
        HTTP_VERSION_11
    }http_version;
    
    
    typedef struct 
    {
        string *key;
        string *value;
    }keyvalue;
    
    typedef struct 
    {
        keyvalue *ptr;
        size_t len;
        size_t size;
    }http_headers;
    
    typedef struct 
    {
        http_method method;
        http_version version;
        char *method_raw;
        char *version_raw;
        char *uri;
        http_headers *headers;
        int content_length;
    }http_request;
    
    typedef struct server
    {
        int content_length;
        string *entity_body;
        http_headers *headers;
    }http_response;
    
    typedef enum 
    {
        HTTP_RECV_STATE_WORD1,
        HTTP_RECV_STATE_WORD2,
        HTTP_RECV_STATE_WORD3,
        HTTP_RECV_STATE_SP1,
        HTTP_RECV_STATE_SP2,
        HTTP_RECV_STATE_LF,
        HTTP_RECV_STATE_LINE    
    }http_recv_state;
    
    typedef struct
    {
        int sockfd;
        int status_code;
        string *recv_buf;
        http_request *request;
        http_response *response;
        http_recv_state recv_state;
        struct sockaddr_in addr;
        size_t request_len;
    
        char real_path[PATH_MAX];
    }connection;
    
    #endif
    
    /*server.c*/
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <sys/stat.h>
    #include <sys/socket.h>
    #include <fcntl.h>
    #include <netdb.h>
    #include <arpa/inet.h>
    #include <signal.h>
    #include <netinet/in.h>
    
    #include <unistd.h>
    #include <getopt.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <errno.h>
    #include <limits.h>
    
    #include "server.h"
    #include "log.h"
    #include "connection.h"
    #include "config.h"
    
    #define DEFAULT_PORT 8080
    #define BACKLOG 10
    
    static server *server_init()
    {
        server *serv;
        serv = malloc(sizeof(*serv));
        memset(serv, 0, sizeof(*serv));
        return serv;
    }
    
    static void jail_server(server *serv, char *logfile, const char *chroot_path)
    {
        size_t root_len = strlen(chroot_path);
        size_t doc_len = strlen(serv->conf->doc_root);
        size_t log_len = strlen(logfile);
    
        if (root_len < doc_len && strncmp(chroot_path, serv->conf->doc_root, root_len) == 0)
        {
            strncpy(serv->conf->doc_root, &serv->conf->doc_root[0] + root_len, doc_len - root_len + 1);
        }
        else
        {
            fprintf(stderr, "document root %s is not a sub_directory in chroot %s
    ", serv->conf->doc_root, chroot_path);
            exit(1);
        }
    
        if (serv->use_logfile)
        {
            if (logfile[0] != '/')
            {
                fprintf(stderr, "warning: log file is not an absolute path, opening it will fail if it isn't in chroot
    ");
            }
            else if(root_len < log_len && strncmp(chroot_path, logfile, root_len) == 0)
            {
                strncpy(logfile, logfile + root_len, log_len - root_len + 1);
            }
            else
            {
                fprintf(stderr, "log file %s is not in chroot
    ", logfile);
                exit(1);
            }    
        }
    
        if (chroot(chroot_path) != 0)
        {
            perror("chroot");
            exit(1);
        }
    
        chdir("/");
        
    }
    
    static void daemonize(server *serv, int null_fd)
    {
        struct sigaction sa;
    
        int fd0, fd1, fd2;
    
        umask(0);
    
        switch (fork())
        {
        case 0:
            break;
        case -1:
            log_error(serv, "daemon fork 1: %s", strerror(errno));
            exit(1);
        
        default:
            exit(0);
        }
    
        setsid();
        sa.sa_handler = SIG_IGN;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = 0;
    
        if (sigaction(SIGHUP, &sa, NULL) < 0)
        {
            log_error(serv, "SIGHUP: %s", strerror(errno));
            exit(1);
        }
    
        switch (fork())
        {
        case 0:
            break;
        case -1:
            log_error(serv, "daemon fork 2: %s", strerror(errno));
            exit(1);
        
        default:
            exit(0);
        }   
    
        chdir("/");
    
        close(STDIN_FILENO);
        close(STDOUT_FILENO);
        close(STDERR_FILENO);
    
        fd0 = dup(null_fd);
        fd1 = dup(null_fd);
        fd2 = dup(null_fd);
    
        if (null_fd != -1)
        {
            close(null_fd);
        }
    
        if (fd0 != 0 || fd1 != 1 || fd2 != 2)
        {
            log_error(serv, "unexpected fds: %d %d %d", fd0, fd1, fd2);
            exit(1);
        }
    
        log_info(serv, "pid: $d", getpid());
    }
    
    static void bind_and_listen(server *serv)
    {
        struct sockaddr_in serv_addr;
        serv->sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
        if (serv->sockfd < 0)
        {
            perror("socket");
            log_error(serv, "socket: %s", strerror(errno));
            exit(1);
        }
    
        int yes = 0;
        if ((setsockopt(serv->sockfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int))) == -1)
        {
            perror("setsockopt");
            log_error(serv, "socket: %s", strerror(errno));
            exit(1);
        }
    
        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = INADDR_ANY;
        serv_addr.sin_port = htons(serv->port);
    
        if (bind(serv->sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
        {
            perror("bind");
            log_error(serv, "bind: %s", strerror(errno));
            exit(1);
        }
    
        if (listen(serv->sockfd, BACKLOG) < 0)
        {
            perror("listen");
            log_error(serv, "listen: %s", strerror(errno));
            exit(1);
        }
    }
    
    static void server_free(server *serv)
    {
        config_free(serv->conf);
        free(serv);
    }
    
    static void start_server(server *serv, const char *config, const char *chroot_path, char *logfile)
    {
        int null_fd = -1;
    
        serv->conf = config_init();
        config_load(serv->conf, config);
    
        if (serv->port == 0 && serv->conf->port != 0)
        {
            serv->port = serv->conf->port;
        }
        else if(serv->port == 0)
        {
            serv->port = DEFAULT_PORT;
        }
    
        printf("port: %d
    ", serv->port);
    
        if (serv->is_daemon)
        {
            null_fd = open("/dev/null", O_RDWR);
        }
    
        if (serv->do_chroot)
        {
            jail_server(serv, logfile, chroot_path);
        }
    
        log_open(serv, logfile);
    
        if (serv->is_daemon)
        {
            daemonize(serv, null_fd);
        }
    
        bind_and_listen(serv);
    
    }
    
    static void sigchld_handler(int s)
    {
        pid_t pid;
        while((pid = waitpid(-1, NULL, WNOHANG)) > 0);
    }
    
    static void do_fork_strategy(server *serv)
    {
        pid_t pid;
        struct sigaction sa;
        connection *con;
    
        sa.sa_handler = sigchld_handler;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = SA_RESTART;
    
        if (sigaction(SIGCHLD, &sa, NULL) == -1)
        {
            perror("sigaction");
            exit(1);
        } 
    
        while(1)
        {
            if ((con = connection_accept(serv)) == NULL)
            {
                continue;
            }
    
            if ((pid = fork()) == 0)
            {
                close(serv->sockfd);
                connection_handler(serv, con);
                connection_close(con);
                exit(0);
            } 
    
            printf("child process: %d
    ", pid);
            connection_close(con);
        }
    }
    
    int main(int argc, char **argv)
    {
        server *serv;
        char logfile[PATH_MAX];
        char chroot_path[PATH_MAX];
        int opt;
    
        serv = server_init();
    
        while((opt = getopt(argc, argv, "p:l:r:d")) != -1)
        {
            switch (opt)
            {
            case 'p':
                serv->port = atoi(optarg);
                if (serv->port == 0)
                {
                    fprintf(stderr, "error: port must be an integer
    ");
                    exit(1);
                }
                break;
            case 'd':
                serv->is_daemon = 1;
                break;
            case 'l':
                strcpy(logfile, optarg);
                serv->use_logfile = 1;
                break;
    
            case 'r':
                if (realpath(optarg, chroot_path) == NULL)
                {
                    perror("chroot");
                    exit(1);
                }
                serv->do_chroot = 1;
                break;
            default:
                break;
            }
        }
    
        start_server(serv, "web.conf", chroot_path, logfile);
    
        do_fork_strategy(serv);
        log_close(serv);
        server_free(serv);
    
        return 0;
    }

    4.客户端管理模块。

    该模块主要对应的是connectio.h和connection.c

    客户端模块主要完成的是连接socket以及HTTP请求和HTTP响应。

    /*connection.h*/
    #ifndef CONNECTION_H
    #define CONNECTION_H
    
    #include"server.h"
    
    connection *connection_accept(server *serv);
    
    void connection_close(connection *con);
    
    int connection_handler(server *serv, connection *con);
    
    #endif
    
    /*connection.c*/
    
    #include <sys/socket.h>
    #include <unistd.h>
    #include <netdb.h>
    #include <string.h>
    #include <errno.h>
    #include <stdio.h>
    
    #include "log.h"
    #include "connection.h"
    #include "request.h"
    #include "response.h"
    #include "stringutils.h"
    
    void connection_close(connection *con)
    {
        if (!con)
        {
            return;
        }
    
        http_request_free(con->request);
        http_response_free(con->response);
    
        string_free(con->recv_buf);
        if (con->sockfd > -1)
        {
            close(con->sockfd);
        }
    
        free(con);
    }
    
    connection *connection_accept(server *serv)
    {
        struct sockaddr_in addr;
        connection *con;
    
        int sockfd;
        socklen_t addr_len = sizeof(addr);
    
        sockfd = accept(serv->sockfd, (struct sockaddr*)&addr, &addr_len);
    
        if (sockfd < 0)
        {
            log_error(serv, "accept: %s", strerror(errno));
            perror("accept");
            return NULL;
        }
    
        con = malloc(sizeof(*con));
    
        con->status_code = 0;
        con->request_len = 0;
        con->sockfd = sockfd;
        con->real_path[0] = '';
    
        con->recv_state = HTTP_RECV_STATE_WORD1;
        con->request = http_request_init();
        con->response = http_response_init();
        con->recv_buf = string_init();
        memcpy(&con->addr, &addr, addr_len);
    
        return con;
    }
    
    int connection_handler(server *serv, connection *con)
    {
        char buf[512];
        int nbytes;
        int ret;
    
        printf("socket: %d
    ", con->sockfd);
    
        while((nbytes = recv(con->sockfd, buf, sizeof(buf), 0)) > 0)
        {
            string_append_len(con->recv_buf, buf, nbytes);
    
            if (http_request_complete(con) != 0)
            {
                break;
            }
        }
    
        if (nbytes <= 0)
        {
            ret = -1;
            if (nbytes == 0)
            {
                printf("socket %d closed 
    ", con->sockfd);
                log_info(serv, "socket %d closed", con->sockfd);
            }
            else if (nbytes < 0)
            {
                perror("read");
                log_error(serv, "read: %s", strerror(errno));
            }
        }
        else 
        {
            ret = 0;
        }
    
        http_request_parse(serv, con);
        http_response_send(serv, con);
        log_request(serv, con);
    
        return ret;
    }

    5.HTTP请求管理模块。

    该模块主要完成HTTP请求相关功能:

    • 1.HTTP请求创建与初始化
    • 2.HTTP请求释放
    • 3.HTTP请求合法性验证
    • 4.HTTP请求解析。

    主要的是HTTP请求解析过程中需要解析的以下几方面内容:

    • 1.HTTP方法
    • 2.请求URI
    • 3.访问的资源
    • 4.HTTP版本
    • 5.HTTP消息头部
    /*request.h*/
    #ifndef REQUEST_H
    #define REQUEST_H
    
    #include"server.h"
    
    http_request* http_request_init();
    void http_request_free(http_request *req);
    
    int http_request_complete(connection *con);
    
    void http_request_parse(server *serv, connection *con);
    
    #endif
    
    /*request.c*/
    #include<limits.h>
    #include<string.h>
    #include<stdlib.h>
    #include<ctype.h>
    #include<assert.h>
    #include<stdio.h>
    
    #include"request.h"
    #include"http_header.h"
    
    
    http_request* http_request_init()
    {
        http_request *req;
        req = malloc(sizeof(*req));
        req->content_length = 0;
        req->version = HTTP_METHOD_UNKNOWN;
        req->content_length = -1;
        req->headers = http_headers_init();
    
        return req;
    }
    
    void http_request_free(http_request *req)
    {
        if (!req)
        {
            return;
        }
    
        http_headers_free(req->headers);
        free(req);
    }
    
    static char* match_until(char **buf, const char *delims)
    {
        char *match = *buf;
        char *end_match = match + strcspn(*buf, delims);
        char *end_delims = end_match + strspn(end_match, delims);
    
        for (char *p = end_match; p < end_delims; p++)
        {
            *p = '';
        }
    
        *buf = end_delims;
    
        return (end_match != end_delims) ? match : NULL;
        
    }
    
    static http_method get_method(const char *method)
    {
        if (strcasecmp(method, "GET") == 0)
        {
            return HTTP_METHOD_GET;
        }
        else if(strcasecmp(method, "HEAD") == 0)
        {
            return HTTP_METHOD_HEAD;
        }
        else if (strcasecmp(method, "POST") == 0 || strcasecmp(method, "PUT"))
        {
            return HTTP_METHOD_NOT_SUPPORTED;
        }
    
        return HTTP_METHOD_UNKNOWN;
    }
    
    static int resolve_uri(char *resolved_path, char *root, char *uri)
    {
        int ret = 0;
        string *path = string_init_str(root);
        string_append(path, uri);
    
        char *res = realpath(path->ptr, resolved_path);
    
        if (!res)
        {
            ret = -1;
            goto cleanup;
        }
    
        size_t resolved_path_len = strlen(resolved_path);
        size_t root_len = strlen(root);
    
        if (resolved_path_len < root_len)
        {
            ret = -1;
        }
        else if(strncmp(resolved_path, root, root_len) != 0)
        {
            ret = -1;
        }
        else if (uri[0] == '/' && uri[1] == '')
        {
            strcat(resolved_path, "/index.html");
        }
    
    cleanup:
        string_free(path);
    
        return ret;
    }
    
    static void try_set_status(connection *con, int status_code)
    {
        if (con->status_code == 0)
        {
            con->status_code = status_code;
        }
    }
    
    int http_request_complete(connection *con)
    {
        char c;
        for (; con->request_len < con->recv_buf->len; con->request_len++)
        {
            c = con->recv_buf->ptr[con->request_len];
            switch (con->recv_state)
            {
            case HTTP_RECV_STATE_WORD1:
            {
                if (c == ' ')
                {
                    con->recv_state = HTTP_RECV_STATE_SP1;
                }
                else if(!isalpha(c))
                {
                    return -1;
                }
                break;
            }
            
            case HTTP_RECV_STATE_SP1:
            {
                if (c == ' ')
                {
                    continue;
                }
                if (c == '
    ' || c == '
    ' || c == '	')
                {
                    return -1;
                }
                con->recv_state = HTTP_RECV_STATE_WORD2;
                break;
            }
    
            case HTTP_RECV_STATE_WORD2:
            {
                if (c == '
    ')
                {
                    con->request_len++;
                    con->request->version = HTTP_VERSION_09;
                    return 1;
                }
                else if (c == ' ')
                {
                    con->recv_state = HTTP_RECV_STATE_SP2; 
                }
                else if (c == '	')
                {
                    return -1;
                }
                break;
            }
    
            case HTTP_RECV_STATE_SP2:
            {
                if (c == ' ')
                {
                    continue;
                }
                if (c == '
    ' || c == '
    ' || c == '	')
                {
                    return -1;
                }
                con->recv_state = HTTP_RECV_STATE_WORD3;
                break;
            }
    
            case HTTP_RECV_STATE_WORD3:
            {
                if (c == '
    ')
                {
                    con->recv_state = HTTP_RECV_STATE_LF;
                }
                else if (c == ' ' || c == '	')
                {
                    return -1;
                }
                break;
            }
    
            case HTTP_RECV_STATE_LF:
            {
                if (c == '
    ')
                {
                    con->request_len++;
                    return 1;
                }
                else if ( c != '
    ')
                {
                     con->recv_state = HTTP_RECV_STATE_LINE;
                }
                break;
            }
    
            case HTTP_RECV_STATE_LINE:
            {
                if (c == '
    ')
                {
                    con->recv_state = HTTP_RECV_STATE_LF;
                }
                break;
            }
            default:
                break;
            }
        }
        return 0;
    }
    
    void http_request_parse(server *serv, connection *con)
    {
        http_request *req = con->request;
        char *buf = con->recv_buf->ptr;
    
        req->method_raw = match_until(&buf, " ");
        if (!req->method_raw)
        {
            con->status_code = 400;
            return;
        }
    
        req->method = get_method(req->method_raw);
    
        if (req->method == HTTP_METHOD_NOT_SUPPORTED)
        {
            try_set_status(con, 501);
        }
        else if(req->method == HTTP_METHOD_UNKNOWN)
        {
            con->status_code = 400;
            return;
        }
    
        req->uri = match_until(&buf, " 
    ");
        if (!req->uri)
        {
            con->status_code = 400;
            return;
        }
    
        if (resolve_uri(con->real_path, serv->conf->doc_root, req->uri) == -1)
        {
            try_set_status(con, 404);
        }
    
        if (req->version == HTTP_VERSION_09)
        {
            try_set_status(con, 200);
            req->version_raw = "";
            return;
        }
    
        req->version_raw = match_until(&buf, "
    ");
    
        if (!req->version_raw)
        {
            con->status_code = 400;
            return;
        }
    
        if (strcasecmp(req->version_raw, "HTTP/1.0") == 0)
        {
            req->version = HTTP_VERSION_10;
        }
        else if (strcasecmp(req->version_raw, "HTTP/1.1") == 0)
        {
            req->version = HTTP_VERSION_11;
        }
        else
        {
            try_set_status(con, 400);
        }
    
        if (con->status_code > 0)
        {
            return;
        }
        
        char *p = buf;
        char *endp = con->recv_buf->ptr + con->request_len;
    
        while (p < endp)
        {
            const char *key = match_until(&p, ": ");
            const char *value = match_until(&p, "
    ");
    
            if (!key || !value)
            {
                con->status_code = 400;
                return;
            }
    
            http_headers_add(req->headers, key, value);
        }
    
        con->status_code = 200;
    }

    6.HTTP响应管理模块

    该模块完成HTTP响应相关功能。

    1.HTTP响应创建与初始化

    2.HTTP响应释放

    3.构建并发送HTTP响应

    /*response.h*/
    #ifndef RESPONSE_H
    #define RESPONSE_H
    
    #include"server.h"
    
    http_response* http_response_init();
    
    void http_response_free(http_response *resp);
    
    void http_response_send(server *serv, connection *con);
    
    #endif
    
    /*response.c*/
    #include <limits.h>
    #include <sys/stat.h>
    #include <sys/socket.h>
    #include <unistd.h>
    #include <string.h>
    #include <stdio.h>
    
    #include "log.h"
    #include "http_header.h"
    #include "response.h"
    
    typedef struct 
    {
        const char *ext;
        const char *mime;
    }mime;
    
    static mime mime_types[] = 
    {
        {".html", "text/html"},
        {".css", "/text/css"},
        {".js", "application/javascript"},
        {".jpg", "image/jpg"},
        {".png", "image/png"}
    };
    
    http_response* http_response_init()
    {
        http_response *resp;
        resp = malloc(sizeof(*resp));
        memset(resp, 0 , sizeof(*resp));
    
        resp->headers = http_headers_init();
        resp->entity_body = string_init();
        resp->content_length = -1;
    
        return resp;
    }
    
    void http_response_free(http_response *resp)
    {
        if (!resp)
        {
            return;
        }
    
        http_headers_free(resp->headers);
        string_free(resp->entity_body);
    
        free(resp);
    }
    
    static char err_file[PATH_MAX];
    static const char *default_err_msg = "<HTML><HEAD><TITLE>Error</TITLE></HEAD>"
                                          "<BODY><H1>Something went wrong</H1>"
                                          "</BODY></HTML>";
    
    static const char *reason_pharse(int status_code)
    {
        switch (status_code)
        {
        case 200:
        {
            return "OK";
        }
        case 400:
        {
            return "Bad Request";
        }
        case 403:
        {
            return "Forbidden";
        }
        case 404:
        {
            return "Not Found";
        }
        case 500:
        {
            return "Internal Server Error";
        }
        case 501:
        {
            return "Not Implemented";
        }    
        default:
            break;
        }
    
        return "";
    }
    
    static int send_all(connection *con, string *buf)
    {
        int bytes_sent = 0;
        int bytes_left = buf->len;
        int nbytes = 0;
    
        while(bytes_sent < bytes_left)
        {
            nbytes = send(con->sockfd, buf->ptr+bytes_sent, bytes_left, 0);
            if (nbytes == -1)
            {
                break;
            }
            bytes_sent += nbytes;
            bytes_left -= nbytes;
        }
    
        return nbytes != -1 ? bytes_sent : -1;
    }
    
    static const char* get_mime_type(const char *path, const char *default_mime)
    {
        size_t path_len = strlen(path);
        size_t mime_types_len = sizeof(mime_types);
        for (size_t i = 0; i < mime_types_len; i++)
        {
            size_t ext_len = strlen(mime_types[i].ext);
            const char *path_ext = path + path_len - ext_len;
    
            if (ext_len <= path_len && strcmp(path_ext, mime_types[i].ext) == 0)
            {
                return mime_types[i].mime;
            }
        }
        return default_mime;
    }
    
    static int check_file_attrs(connection *con, const char *path)
    {
        struct stat s;
        con->response->content_length = -1;
        if (-1 == stat(path, &s))
        {
            con->status_code = 404;
            return -1;
        }
    
        if (!S_ISREG(s.st_mode))
        {
            con->status_code = 403;
            return -1;
        }
    
        con->response->content_length = s.st_size;
    
        return 0;
    }
    
    static int read_file(string *buf, const char *path)
    {
        FILE *fp;
        int fsize;
    
        fp = fopen(path, "r");
        if (!fp)
        {
            return -1;
        }
    
        fseek(fp, 0, SEEK_END);
        fsize = ftell(fp);
    
        fseek(fp, 0, SEEK_SET);
    
        string_extend(buf, fsize + 1);
    
        if (fread(buf->ptr, fsize, 1, fp) > 0)
        {
            buf->len += fsize;
            buf->ptr[buf->len] = '';
        }
    
        fclose(fp);
    
        return fsize;
    }
    
    static int read_err_file(server *serv, connection *con, string *buf)
    {
        snprintf(err_file, sizeof(err_file), "%s/%d.html", serv->conf->doc_root, con->status_code);
        int len = read_file(buf, err_file);
    
        if (len <= 0)
        {
            string_append(buf, default_err_msg);
            len = buf->len;
        }
        return len;
    }
    
    static void build_and_send_response(connection *con)
    {
        string *buf = string_init();
        http_response *resp = con->response;
        
        string_append(buf, "HTTP/1.0 ");
        string_append_int(buf, con->status_code);
        string_append_ch(buf, ' ');
        string_append(buf, reason_pharse(con->status_code));
        string_append(buf, "
    ");
    
        for (size_t i = 0; i < resp->headers->len; i++)
        {
            string_append_string(buf, resp->headers->ptr[i].key);
            string_append(buf, ": ");
            string_append_string(buf, resp->headers->ptr[i].value);
            string_append(buf, "
    ");
        }
    
        string_append(buf, "
    ");
    
        if (resp->content_length > 0 && con->request->method != HTTP_METHOD_HEAD)
        {
            string_append_string(buf, resp->entity_body);
        }
    
        send_all(con, buf);
        string_free(buf);
    }
    
    static void send_err_response(server *serv, connection *con)
    {
        http_response *resp = con->response;
        snprintf(err_file, sizeof(err_file), "%s/%d.html", serv->conf->doc_root, con->status_code);
    
        if (check_file_attrs(con, err_file) == -1)
        {
            resp->content_length = strlen(default_err_msg);
            log_error(serv, "failed to open file %s", err_file);
        }
    
        http_headers_add(resp->headers, "Content-Type", "text/html");
        http_headers_add_int(resp->headers, "Content-Length", resp->content_length);
    
        if (con->request->method != HTTP_METHOD_HEAD)
        {
            read_err_file(serv, con, resp->entity_body);
        }
    
        build_and_send_response(con);
    }
    
    static void send_response(server *serv, connection *con)
    {
        http_response *resp = con->response;
        http_request *req = con->request;
    
        http_headers_add(resp->headers, "Server", "cserver");
    
        if (con->status_code != 200)
        {
            send_err_response(serv, con);
            return ;
        }
    
        if (check_file_attrs(con, con->real_path) == -1)
        {
            send_err_response(serv, con);
            return;
        }
    
        if (req->method != HTTP_METHOD_HEAD)
        {
            read_file(resp->entity_body, con->real_path);
        }
    
        const char *mime = get_mime_type(con->real_path, "text/plain");
        http_headers_add(resp->headers, "Content-Type", mime);
        http_headers_add_int(resp->headers, "Content_length", resp->content_length);
    
        build_and_send_response(con);
    }
    
    static void send_http09_response(server *serv, connection *con)
    {
        http_response *resp = con->response;
    
        if (con->status_code == 200 && check_file_attrs(con, con->real_path) == 0)
        {
            read_file(resp->entity_body, con->real_path);
        }
        else
        {
            read_err_file(serv, con, resp->entity_body);
        }
    
        send_all(con, resp->entity_body);
        
    }
    
    void http_response_send(server *serv, connection *con)
    {
        if (con->request->version == HTTP_VERSION_09)
        {
            send_http09_response(serv, con);
        }
        else
        {
            send_response(serv, con);
        }
        
    }

    7.HTTP头部管理消息模块

    该模块的头部实际就是一组键值对组成。其中功能包括:

    HTTP消息头部创建与初始化

    HTTP消息头部释放

    HTTP消息头部添加新的键值对

    /*http_header.h*/
    #ifndef HTTP_HEADER_H
    #define HTTP_HEADER_H
    
    #include "server.h"
    
    http_headers *http_headers_init();
    
    void http_headers_free(http_headers *h);
    
    
    void http_headers_add(http_headers *h, const char *key, const char *value);
    void http_headers_add_int(http_headers *h, const char *key, int value);
    
    #endif
    
    /*http_header.c*/
    #include<assert.h>
    #include<string.h>
    
    #include"http_header.h"
    #define HEADER_SIZE_INC 20
    
    http_headers *http_headers_init()
    {
        http_headers *h;
        h = malloc(sizeof(*h));
        memset(h, 0, sizeof(*h));
        return h;
    }
    
    void http_headers_free(http_headers *h)
    {
        if (!h)
        {
            return ;
        }
    
        for (size_t i = 0; i < h->len; i++)
        {
            string_free(h->ptr[i].key);
            string_free(h->ptr[i].value);
        }
    
        free(h->ptr);
        free(h);
    }
    
    static void extend(http_headers *h)
    {
        if (h->len >= h->size)
        {
            h->size += HEADER_SIZE_INC;
            h->ptr = realloc(h->ptr, h->size * sizeof(keyvalue));
        }
    }
    
    
    void http_headers_add(http_headers *h, const char *key, const char *value)
    {
        assert(h != NULL);
    
        extend(h);
    
        h->ptr[h->len].key = string_init_str(key);
        h->ptr[h->len].value = string_init_str(value);
        h->len++;
    }
    
    void http_headers_add_int(http_headers *h, const char *key, int value)
    {
        assert(h != NULL);
        extend(h);
    
        string *value_str = string_init();
        string_append_int(value_str, value);
        
        h->ptr[h->len].key = string_init_str(key);
        h->ptr[h->len].value = value_str;
        h->len++;
    }

    8.日志记录模块

    实现中使用标准的Linux系统日志的格式,具体类似与:

    IP, 时间, 访问方法, URI, 版本, 状态, 内容长度

    /*log.h*/
    #ifndef LOG_H
    #define LOG_H
    
    #include"server.h"
    
    void log_open(server *serv, const char *logfile);
    
    void log_close(server *serv);
    
    void log_request(server *serv, connection *con);
    
    void log_error(server *serv, const char *format, ...);
    
    void log_info(server *serv, const char *format, ...);
    
    #endif
    
    /*log.h*/
    #include<arpa/inet.h>
    #include<syslog.h>
    #include<string.h>
    #include<stdio.h>
    #include<time.h>
    #include<errno.h>
    #include<stdarg.h>
    #include"stringutils.h"
    #include"log.h"
    
    void log_open(server *serv, const char *logfile)
    {
        if (serv->use_logfile)
        {
            serv->logfp = fopen(logfile, "a");
            if (!serv->logfp)
            {
                perror(logfile);
                exit(-1);
            }
        return ;
        }
        openlog("webserver", LOG_NDELAY | LOG_PID , LOG_DAEMON);
    }
    
    void log_close(server *serv)
    {
        if (serv->logfp)
        {
            fclose(serv->logfp);
        }
        closelog();
    }
    
    static void date_str(string *s)
    {
        struct tm *ti;
        time_t rawtime;
    
        char local_data[100];
        char zone_str[20];
        int zone;
        char zone_sign;
    
        time(&rawtime);
        ti = localtime(&rawtime);
        zone = ti->tm_gmtoff / 60;
    
        if (ti->tm_zone < 0)
        {
            zone_sign = '-';
            zone = -zone;
        }
        else
        {
            zone_sign = '+';
        }
    
        zone = (zone / 60) * 100 + zone % 60;
    
        strftime(local_data, sizeof(local_data), "%d/%b/%Y:%X", ti);
        snprintf(zone_str, sizeof(zone_str), " %c%.4d", zone_sign, zone);
    
        string_append(s, local_data);
        string_append(s, zone_str);    
    }
    
    void log_request(server *serv, connection *con)
    {
        http_request *req = con->request;
        http_response *resp = con->response;
        char host_ip[INET_ADDRSTRLEN];
        char content_len[20];
    
        string *date = string_init();
    
        if (!serv || !con)
        {
            return;
        }
    
        if (resp->content_length > -1 && req->method != HTTP_METHOD_HEAD)
        {
            snprintf(content_len, sizeof(content_len), "%d", resp->content_length);
        }
        else
        {
            strcpy(content_len, "-");
        }
    
        inet_ntop(con->addr.sin_family, &con->addr.sin_addr, host_ip, INET_ADDRSTRLEN);
        date_str(date);
    
        if (serv->use_logfile)
        {
            fprintf(serv->logfp, "%s -- [%s] " %s %s %s " %d %s
    ",
                    host_ip, date->ptr, req->method_raw, req->uri,
                    req->version_raw, con->status_code, content_len);
        }
    
        string_free(date);
        
    }
    
    static void log_write(server *serv, const char *type, const char *format, va_list ap)
    {
        string *output = string_init();
    
        if (serv->use_logfile)
        {
            string_append_ch(output, '[');
            date_str(output);
            string_append(output, "] ");
        }
    
        string_append(output, "[");
        string_append(output, type);
        string_append(output, "] ");
    
        string_append(output, format);
    
        if (serv->use_logfile)
        {
            string_append_ch(output, '
    ');
            vfprintf(serv->logfp, output->ptr, ap);
            fflush(serv->logfp);
        }
        else
        {
            vsyslog(LOG_ERR, output->ptr, ap);
        }
    
        string_free(output);
        
    }
    
    void log_error(server *serv, const char *format, ...)
    {
        va_list ap;
        va_start(ap, format);
        log_write(serv, "error", format, ap);
        va_end(ap);
    }
    
    void log_info(server *serv, const char *format, ...)
    {
        va_list ap;
        va_start(ap, format);
        log_write(serv, "error", format, ap);
        va_end(ap);   
    }

    9.makefile

    CC = gcc
    LD = gcc
    CFLAGS = -g -Wall -std=gnu99
    LDFLAGS = -g
    RM = rm -rf
    
    SRCS = server.c connection.c http_header.c request.c response.c log.c config.c stringutils.c
    OBJS = $(addsuffix .o, $(basename $(SRCS)))
    
    PROG = web
    
    all: $(PROG)
    
    $(PROG): $(OBJS)
        $(LD) $(LDFLAGS) $(OBJS) -o $(PROG)
    
    %.o: %.c
        $(CC) $(CFLAGS) -c $<
    
    .PHONY: clean
    clean:
        $(RM) $(PROG) $(OBJS)

    具体配置文件和网页不放出来了。本实验来自实验楼会员的C实验中,想做完整实验的可以去实验楼学习。

  • 相关阅读:
    fdfs上传图片成功在浏览器中访问不到404 Not Found
    G1 GC日志:Application time: 0.8766273 seconds
    nginx编译问题:make[1]: *** [/usr/local/pcre//Makefile] Error 127
    Datatable报错Uncaught TypeError: Cannot read property 'cell' of undefined
    信息: Error parsing HTTP request header Note: further occurrences of HTTP header parsing errors will be logged at DEBUG level
    [HDU1176]免费馅饼(DP)
    [程序员代码面试指南]二叉树问题-找到二叉树中的最大搜索二叉树(树形dp)
    [程序员代码面试指南]二叉树问题-在二叉树中找到累加和为指定值的最长路径长度
    [程序员代码面试指南]数组和矩阵-求最短通路值(BFS)
    [程序员代码面试指南]9-判断点在三角形内
  • 原文地址:https://www.cnblogs.com/wanghao-boke/p/12101805.html
Copyright © 2011-2022 走看看