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