原理介绍
原创性申明: http协议的作用原理
连接:web浏览器与web服务器建立连接,打开一个称为socket(套接字)的虚拟文件,此文件的建立标志着连接建立成功。
请求:web浏览器通过socket向web服务器提交请求。http的请求一般是get或post命令(post用于form参数的传递)。get命令的格式为:
get 路径/文件名 http/1.0
文件名指出所访问的文件,http/1.0指出web浏览器使用的http版本。
应答:web浏览器提交请求后,通过http协议传送给web服务器。web服务器接到后,进行事务处理,处理结果又通过http传回给web浏览器,从而在web浏览器上显示出所请求的页面。
然后返回 网页内容到客户端,客户端更具 conntent-type 来解析
代码实现 其实web服务器也就是一个一般的socket的服务器,只不过它遵守http协议来实现的罢了
//// server.c//// david j. malan// malan@harvard.edu//// feature test macro requirements#define _gnu_source#define _xopen_source 700#define _xopen_source_extended// limits on an http request's size, based on apache's// http://httpd.apache.org/docs/2.2/mod/core.html#define limitrequestfields 50#define limitrequestfieldsize 4094#define limitrequestline 8190// number of octets for buffered reads#define octets 512// header files#include #include #include #include #include #include #include #include #include #include #include // typestypedef char octet;// prototypesbool connected(void);bool error(unsigned short code);void handler(int signal);ssize_t load(void);const char* lookup(const char* extension);ssize_t parse(void);void reset(void);void start(short port, const char* path);void stop(void);// server's rootchar* root = null;// file descriptor for socketsint cfd = -1, sfd = -1;// buffer for requestoctet* request = null;// file pointer for filesfile* file = null;// buffer for response-bodyoctet* body = null;int main(int argc, char* argv[]){ // a global variable defined in errno.h that's set by system // calls and some library functions [to a nonzero value] // in the event of an error to indicate what went wrong errno = 0; // default to a random port int port = 0; // usage const char* usage = usage: server [-p port] /path/to/root; // parse command-line arguments int opt; while ((opt = getopt(argc, argv, hp:)) != -1) { switch (opt) { // -h case 'h': printf(%s\n, usage); return 0; // -p port case 'p': port = atoi(optarg); break; } } // ensure port is a non-negative short and path to server's root is specified if (port shrt_max || argv[optind] == null || strlen(argv[optind]) == 0) { // announce usage printf(%s\n, usage); // return 2 just like bash's builtins return 2; } // start server start(port, argv[optind]); // listen for sigint (aka control-c) signal(sigint, handler); // accept connections one at a time while (true) { // reset server's state reset(); // wait until client is connected if (connected()) { // parse client's http request ssize_t octets = parse(); if (octets == -1) { continue; } // extract request's request-line // http://www.w3.org/protocols/rfc2616/rfc2616-sec5.html const char* haystack = request; char* needle = strstr(haystack, \r\n); if (needle == null) { error(400); continue; } else if (needle - haystack + 2 > limitrequestline) { error(414); continue; } char line[needle - haystack + 2 + 1]; strncpy(line, haystack, needle - haystack + 2); line[needle - haystack + 2] = '\0'; // log request-line printf(\n%s, line); // todo: validate request-line char* method = line; char* request_target; char* version; char* absolute_path; char* s1; s1 = strstr (line, ); if (s1 == null || (version = strstr(s1 + 1, )) == null || strncmp(line + sizeof(line) - 2, \r\n, 2) == 0) { error(400); continue; } request_target = strdup(s1 + 1); request_target[version - s1 - 1] = '\0'; //printf(request_target is %s, version is %s, request_target, version); if (strncmp(method,get,3) != 0) { error(405); continue; } if (strncmp(request_target, /, 1) != 0) { error(501); continue; } if (strchr(request_target, '\') != null) { error(400); continue; } if (strncmp (version + 1,http/1.1,8) != 0) { error(505); continue; } if (strchr(request_target, '.') == null) { error(501); continue; } // todo: extract query from request-target char *query = strchr(request_target, '?'); if (query == null) query = ; else query = query + 1; //printf(query is %s\n, query); // todo: concatenate root and absolute-path absolute_path = strdup(request_target);// printf(%s. %d\n, absolute_path, query-request_target); if (strcmp(query, ) != 0) absolute_path[query - request_target - 1] = '\0'; //printf(absolute path is %s\n, absolute_path); char *path = (char*)malloc(1000); strcpy(path, root);// strcat(path, /); strcat(path, absolute_path); //printf(path is %s\n, path); // todo: ensure path exists if (access(path, f_ok) == -1) { error(404); continue; } // todo: ensure path is readable if (access(path, r_ok) == -1) { error(403); continue; } // todo: extract path's extension char* extension; if ((extension = strrchr(absolute_path, '.')) == null) { error(501); continue; } extension = extension + 1; //printf(extension is %s\n, extension); // dynamic content if (strcasecmp(php, extension) == 0) { // open pipe to php interpreter char* format = query_string=\%s\ redirect_status=200 script_filename=\%s\ php-cgi; char command[strlen(format) + (strlen(path) - 2) + (strlen(query) - 2) + 1]; sprintf(command, format, query, path); file = popen(command, r); if (file == null) { error(500); continue; } // load file ssize_t size = load(); if (size == -1) { error(500); continue; } // subtract php-cgi's headers from body's size to get content's length haystack = body; needle = memmem(haystack, size, \r\n\r\n, 4); if (needle == null) { error(500); continue; } size_t length = size - (needle - haystack + 4); // respond to client if (dprintf(cfd, http/1.1 200 ok\r\n) 599) { return false; } // determine status-line's phrase // http://www.w3.org/protocols/rfc2616/rfc2616-sec6.html#sec6.1 const char* phrase = null; switch (code) { case 400: phrase = bad request; break; case 403: phrase = forbidden; break; case 404: phrase = not found; break; case 405: phrase = method not allowed; break; case 413: phrase = request entity too large; break; case 414: phrase = request-uri too long; break; case 418: phrase = i'm a teapot; break; case 500: phrase = internal server error; break; case 501: phrase = not implemented; break; case 505: phrase = http version not supported; break; } if (phrase == null) { return false; } // template char* template = %i %s%i %s; char content[strlen(template) + 2 * ((int) log10(code) + 1 - 2) + 2 * (strlen(phrase) - 2) + 1]; int length = sprintf(content, template, code, phrase, code, phrase); // respond with status-line if (dprintf(cfd, http/1.1 %i %s\r\n, code, phrase) 0) { body = realloc(body, size + octets); if (body == null) { return -1; } memcpy(body + size, buffer, octets); size += octets; } // check for eof if (feof(file) != 0) { break; } } return size;}/** * handles signals. */void handler(int signal){ // control-c if (signal == sigint) { // ensure this isn't considered an error // (as might otherwise happen after a recent 404) errno = 0; // announce stop printf(\033[33m); printf(stopping server\n); printf(\033[39m); // stop server stop(); }}/** * returns mime type for supported extensions, else null. */const char* lookup(const char* extension){ // todo if (strcasecmp(extension, css)) return text/css; if (strcasecmp(extension, html)) return text/html; if (strcasecmp(extension, gif)) return image/gif; if (strcasecmp(extension, ico)) return image/x-icon; if (strcasecmp(extension, jpg)) return image/jpeg; if (strcasecmp(extension, js)) return text/javascript; if (strcasecmp(extension, png)) return image/png; return null;}/** * parses an http request. */ssize_t parse(void){ // ensure client's socket is open if (cfd == -1) { return -1; } // ensure request isn't already parsed if (request != null) { return -1; } // buffer for octets octet buffer[octets]; // parse request ssize_t length = 0; while (true) { // read from socket ssize_t octets = read(cfd, buffer, sizeof(octet) * octets); if (octets == -1) { error(500); return -1; } // if octets have been read, remember new length if (octets > 0) { request = realloc(request, length + octets); if (request == null) { return -1; } memcpy(request + length, buffer, octets); length += octets; } // else if nothing's been read, socket's been closed else { return -1; } // search for crlf crlf int offset = (length - octets = limitrequestline + limitrequestfields * limitrequestfieldsize) { error(413); return -1; } } return length;}/** * resets server's state, deallocating any resources. */void reset(void){ // free response's body if (body != null) { free(body); body = null; } // close file if (file != null) { fclose(file); file = null; } // free request if (request != null) { free(request); request = null; } // close client's socket if (cfd != -1) { close(cfd); cfd = -1; }}/** * starts server. */void start(short port, const char* path){ // path to server's root root = realpath(path, null); if (root == null) { stop(); } // ensure root exists if (access(root, f_ok) == -1) { stop(); } // ensure root is executable if (access(root, x_ok) == -1) { stop(); } // announce root printf(\033[33m); printf(using %s for server's root, size is %ld, root, sizeof(root)); printf(\033[39m\n); // create a socket sfd = socket(af_inet, sock_stream, 0); if (sfd == -1) { stop(); } // allow reuse of address (to avoid address already in use) int optval = 1; setsockopt(sfd, sol_socket, so_reuseaddr, &optval, sizeof(optval)); // assign name to socket struct sockaddr_in serv_addr; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = af_inet; serv_addr.sin_port = htons(port); serv_addr.sin_addr.s_addr = htonl(inaddr_any); if (bind(sfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1) { stop(); } // listen for connections if (listen(sfd, somaxconn) == -1) { stop(); } // announce port in use struct sockaddr_in addr; socklen_t addrlen = sizeof(addr); if (getsockname(sfd, (struct sockaddr*) &addr, &addrlen) == -1) { stop(); } printf(\033[33m); printf(listening on port %i, ntohs(addr.sin_port)); printf(\033[39m\n);}/** * stop server, deallocating any resources. */void stop(void){ // preserve errno across this function's library calls int errsv = errno; // reset server's state reset(); // free root, which was allocated by realpath if (root != null) { free(root); } // close server socket if (sfd != -1) { close(sfd); } // terminate process if (errsv == 0) { // success exit(0); } else { // announce error printf(\033[33m); printf(%s, strerror(errsv)); printf(\033[39m\n); // failure exit(1); }}
编译方法
gcc -o server server.c -lm
运行方法
./server -p 8080 public
其中public目录是存放html和php文件的