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] = '\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] = '\0';

    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] = '\0';

    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] = '\0';

    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\n", fn);
        exit(1);
    }

    line = string_init();
    key = string_init();
    value = string_init();
    buf = key;
    lineno = 1;

    while((ch = fgetc(fp)) != EOF)
    {
        if (ch != '\n')
        {
            string_append_ch(line, ch);
        }

        if(ch == '\\')
        {
            continue;
        }    

        if (!is_str && (ch == ' ' || ch == '\t'))
        {
            continue;
        }

        if (ch == '"')
        {
            is_str = (is_str + 1) % 2;
            continue;
        }

        if (ch == '=')
        {
            buf = value;
            continue;
        }

        if (ch == '\n')
        {
            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, "\n*** FAILED TO LOAD CONFIG FILE ***\n");
    fprintf(stderr, "at line: %d\n", lineno);
    fprintf(stderr, ">> '%s' \n", line->ptr);
    fprintf(stderr, "%s\n", 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\n", 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\n");
        }
        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\n", 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\n", 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\n", 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\n");
                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] = '\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\n", 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 \n", 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 = '\0';
    }

    *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] == '\0')
    {
        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 == '\r' || c == '\n' || c == '\t')
            {
                return -1;
            }
            con->recv_state = HTTP_RECV_STATE_WORD2;
            break;
        }

        case HTTP_RECV_STATE_WORD2:
        {
            if (c == '\n')
            {
                con->request_len++;
                con->request->version = HTTP_VERSION_09;
                return 1;
            }
            else if (c == ' ')
            {
                con->recv_state = HTTP_RECV_STATE_SP2; 
            }
            else if (c == '\t')
            {
                return -1;
            }
            break;
        }

        case HTTP_RECV_STATE_SP2:
        {
            if (c == ' ')
            {
                continue;
            }
            if (c == '\r' || c == '\n' || c == '\t')
            {
                return -1;
            }
            con->recv_state = HTTP_RECV_STATE_WORD3;
            break;
        }

        case HTTP_RECV_STATE_WORD3:
        {
            if (c == '\n')
            {
                con->recv_state = HTTP_RECV_STATE_LF;
            }
            else if (c == ' ' || c == '\t')
            {
                return -1;
            }
            break;
        }

        case HTTP_RECV_STATE_LF:
        {
            if (c == '\n')
            {
                con->request_len++;
                return 1;
            }
            else if ( c != '\r')
            {
                 con->recv_state = HTTP_RECV_STATE_LINE;
            }
            break;
        }

        case HTTP_RECV_STATE_LINE:
        {
            if (c == '\n')
            {
                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, " \r\n");
    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, "\r\n");

    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, "\r\n");

        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] = '\0';
    }

    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, "\r\n");

    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, "\r\n");
    }

    string_append(buf, "\r\n");

    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\n",
                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, '\n');
        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实验中,想做完整实验的可以去实验楼学习。