HTTP服务器的本质:tinyhttpd源码分析及拓展
已經(jīng)有一個(gè)月沒(méi)有更新博客了,一方面是因?yàn)槠綍r(shí)太忙了,另一方面是想積攢一些干貨進(jìn)行分享。最近主要是做了一些開(kāi)源項(xiàng)目的源碼分析工作,有c項(xiàng)目也有python項(xiàng)目,想提升一下內(nèi)功,今天分享一下tinyhttpd源碼分析的成果。tinyhttpd是一個(gè)非常輕量型的http服務(wù)器,c代碼500行左右,可以幫助我們了解http服務(wù)器運(yùn)行的實(shí)質(zhì)。在分析之前,我們先說(shuō)一下http報(bào)文。(我的新書(shū)《Python爬蟲(chóng)開(kāi)發(fā)與項(xiàng)目實(shí)戰(zhàn)》出版了,大家可以看一下樣章)
一.http請(qǐng)求
http請(qǐng)求由三部分組成,分別是:起始行、消息報(bào)頭、請(qǐng)求正文
Request Line<CRLF> Header-Name: header-value<CRLF> Header-Name: header-value<CRLF> //一個(gè)或多個(gè),均以<CRLF>結(jié)尾 <CRLF> body//請(qǐng)求正文1、起始行以一個(gè)方法符號(hào)開(kāi)頭,以空格分開(kāi),后面跟著請(qǐng)求的URI和協(xié)議的版本,格式如下:
Method Request-URI HTTP-Version CRLF其中 Method表示請(qǐng)求方法;Request-URI是一個(gè)統(tǒng)一資源標(biāo)識(shí)符;HTTP-Version表示請(qǐng)求的HTTP協(xié)議版本;CRLF表示回車(chē)和換行(除了作為結(jié)尾的CRLF外,不允許出現(xiàn)單獨(dú)的CR或LF字符)。
2、請(qǐng)求方法(所有方法全為大寫(xiě))有多種,各個(gè)方法的解釋如下:
- GET 請(qǐng)求獲取Request-URI所標(biāo)識(shí)的資源
- POST 在Request-URI所標(biāo)識(shí)的資源后附加新的數(shù)據(jù)
- HEAD 請(qǐng)求獲取由Request-URI所標(biāo)識(shí)的資源的響應(yīng)消息報(bào)頭
- PUT 請(qǐng)求服務(wù)器存儲(chǔ)一個(gè)資源,并用Request-URI作為其標(biāo)識(shí)
- DELETE 請(qǐng)求服務(wù)器刪除Request-URI所標(biāo)識(shí)的資源
- TRACE 請(qǐng)求服務(wù)器回送收到的請(qǐng)求信息,主要用于測(cè)試或診斷
- CONNECT 保留將來(lái)使用
- OPTIONS 請(qǐng)求查詢(xún)服務(wù)器的性能,或者查詢(xún)與資源相關(guān)的選項(xiàng)和需求
應(yīng)用舉例:?
GET方法:在瀏覽器的地址欄中輸入網(wǎng)址的方式訪(fǎng)問(wèn)網(wǎng)頁(yè)時(shí),瀏覽器采用GET方法向服務(wù)器獲取資源,eg:?
POST方法要求被請(qǐng)求服務(wù)器接受附在請(qǐng)求后面的數(shù)據(jù),常用于提交表單。eg:
POST /reg.jsp HTTP/ (CRLF) Accept:image/gif,image/x-xbit,... (CRLF) ... HOST:www.guet.edu.cn (CRLF) Content-Length:22 (CRLF) Connection:Keep-Alive (CRLF) Cache-Control:no-cache (CRLF) (CRLF) //該CRLF表示消息報(bào)頭已經(jīng)結(jié)束,在此之前為消息報(bào)頭 user=jeffrey&pwd=1234 //此行以下為提交的數(shù)據(jù)?
二.tinyhttpd源碼分析
tinyhttpd總共包含以下函數(shù):
建議源碼閱讀順序: main -> startup -> accept_request -> execute_cgi
按照以上順序,看一下瀏覽器和tinyhttpd交互的整個(gè)流程:
三.注釋版源碼
注釋版源碼已經(jīng)放到github上了,以后所有的源碼分析都會(huì)上傳github上。由于tinyhttpd源碼較少,下面將完整的代碼貼出來(lái)。
/* J. David's webserver */ /* This is a simple webserver.* Created November 1999 by J. David Blackstone.* CSE 4344 (Network concepts), Prof. Zeigler* University of Texas at Arlington*/ /* This program compiles for Sparc Solaris 2.6.* To compile for Linux:* 1) Comment out the #include <pthread.h> line.* 2) Comment out the line that defines the variable newthread.* 3) Comment out the two lines that run pthread_create().* 4) Uncomment the line that runs accept_request().* 5) Remove -lsocket from the Makefile.*/ #include <stdio.h> #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <ctype.h> #include <strings.h> #include <string.h> #include <sys/stat.h> #include <pthread.h> #include <sys/wait.h> #include <stdlib.h>#define ISspace(x) isspace((int)(x)) //函數(shù)說(shuō)明:檢查參數(shù)c是否為空格字符, //也就是判斷是否為空格(' ')、定位字符(' \t ')、CR(' \r ')、換行(' \n ')、垂直定位字符(' \v ')或翻頁(yè)(' \f ')的情況。 //返回值:若參數(shù)c 為空白字符,則返回非 0,否則返回 0。#define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"//定義server名稱(chēng)void accept_request(int);//接收請(qǐng)求void bad_request(int);//無(wú)效請(qǐng)求 void cat(int, FILE *); void cannot_execute(int); void error_die(const char *); void execute_cgi(int, const char *, const char *, const char *); int get_line(int, char *, int); void headers(int, const char *); void not_found(int); void serve_file(int, const char *); int startup(u_short *); void unimplemented(int);/**********************************************************************/ /* A request has caused a call to accept() on the server port to* return. Process the request appropriately.* Parameters: the socket connected to the client */ /**********************************************************************/ //接收客戶(hù)端的連接,并讀取請(qǐng)求數(shù)據(jù) void accept_request(int client) {char buf[1024];int numchars;char method[255];char url[255];char path[512];size_t i, j;struct stat st;int cgi = 0; /* becomes true if server decides this is a CGI* program */char *query_string = NULL; //獲取一行HTTP報(bào)文數(shù)據(jù)numchars = get_line(client, buf, sizeof(buf));//i = 0; j = 0;//對(duì)于HTTP報(bào)文來(lái)說(shuō),第一行的內(nèi)容即為報(bào)文的起始行,格式為<method> <request-URL> <version>,//每個(gè)字段用空白字符相連while (!ISspace(buf[j]) && (i < sizeof(method) - 1)){//提取其中的請(qǐng)求方式是GET還是POSTmethod[i] = buf[j];i++; j++;}method[i] = '\0'; //函數(shù)說(shuō)明:strcasecmp()用來(lái)比較參數(shù)s1 和s2 字符串,比較時(shí)會(huì)自動(dòng)忽略大小寫(xiě)的差異。 //返回值:若參數(shù)s1 和s2 字符串相同則返回0。s1 長(zhǎng)度大于s2 長(zhǎng)度則返回大于0 的值,s1 長(zhǎng)度若小于s2 長(zhǎng)度則返回小于0 的值。if (strcasecmp(method, "GET") && strcasecmp(method, "POST")){//tinyhttp僅僅實(shí)現(xiàn)了GET和POSTunimplemented(client);return;} //cgi為標(biāo)志位,置1說(shuō)明開(kāi)啟cgi解析if (strcasecmp(method, "POST") == 0) //如果請(qǐng)求方法為POST,需要cgi解析cgi = 1;i = 0;//將method后面的后邊的空白字符略過(guò)while (ISspace(buf[j]) && (j < sizeof(buf)))j++;//繼續(xù)讀取request-URLwhile (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))){url[i] = buf[j];i++; j++;}url[i] = '\0'; //如果是GET請(qǐng)求,url可能會(huì)帶有?,有查詢(xún)參數(shù)if (strcasecmp(method, "GET") == 0){query_string = url;while ((*query_string != '?') && (*query_string != '\0'))query_string++;if (*query_string == '?'){//如果帶有查詢(xún)參數(shù),需要執(zhí)行cgi,解析參數(shù),設(shè)置標(biāo)志位為1cgi = 1;//將解析參數(shù)截取下來(lái)*query_string = '\0';query_string++;}} //以上已經(jīng)將起始行解析完畢 //url中的路徑格式化到pathsprintf(path, "htdocs%s", url); //學(xué)習(xí)到這里明天繼續(xù)TODO //如果path只是一個(gè)目錄,默認(rèn)設(shè)置為首頁(yè)index.htmlif (path[strlen(path) - 1] == '/')strcat(path, "index.html");//函數(shù)定義: int stat(const char *file_name, struct stat *buf); //函數(shù)說(shuō)明: 通過(guò)文件名filename獲取文件信息,并保存在buf所指的結(jié)構(gòu)體stat中 //返回值: 執(zhí)行成功則返回0,失敗返回-1,錯(cuò)誤代碼存于errno(需要include <errno.h>)if (stat(path, &st) == -1) {//假如訪(fǎng)問(wèn)的網(wǎng)頁(yè)不存在,則不斷的讀取剩下的請(qǐng)求頭信息,并丟棄即可while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */numchars = get_line(client, buf, sizeof(buf));//最后聲明網(wǎng)頁(yè)不存在not_found(client);}else{//如果訪(fǎng)問(wèn)的網(wǎng)頁(yè)存在則進(jìn)行處理if ((st.st_mode & S_IFMT) == S_IFDIR)//S_IFDIR代表目錄//如果路徑是個(gè)目錄,那就將主頁(yè)進(jìn)行顯示strcat(path, "/index.html");if ((st.st_mode & S_IXUSR) ||(st.st_mode & S_IXGRP) ||(st.st_mode & S_IXOTH) )//S_IXUSR:文件所有者具可執(zhí)行權(quán)限//S_IXGRP:用戶(hù)組具可執(zhí)行權(quán)限//S_IXOTH:其他用戶(hù)具可讀取權(quán)限 cgi = 1;if (!cgi)//將靜態(tài)文件返回serve_file(client, path);else//執(zhí)行cgi動(dòng)態(tài)解析execute_cgi(client, path, method, query_string);}close(client);//因?yàn)閔ttp是面向無(wú)連接的,所以要關(guān)閉 }/**********************************************************************/ /* Inform the client that a request it has made has a problem.* Parameters: client socket */ /**********************************************************************/ void bad_request(int client) {char buf[1024]; //發(fā)送400sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");send(client, buf, sizeof(buf), 0);sprintf(buf, "Content-type: text/html\r\n");send(client, buf, sizeof(buf), 0);sprintf(buf, "\r\n");send(client, buf, sizeof(buf), 0);sprintf(buf, "<P>Your browser sent a bad request, ");send(client, buf, sizeof(buf), 0);sprintf(buf, "such as a POST without a Content-Length.\r\n");send(client, buf, sizeof(buf), 0); }/**********************************************************************/ /* Put the entire contents of a file out on a socket. This function* is named after the UNIX "cat" command, because it might have been* easier just to do something like pipe, fork, and exec("cat").* Parameters: the client socket descriptor* FILE pointer for the file to cat */ /**********************************************************************/ void cat(int client, FILE *resource) { //發(fā)送文件的內(nèi)容char buf[1024]; //讀取文件到buf中fgets(buf, sizeof(buf), resource);while (!feof(resource))//判斷文件是否讀取到末尾{//讀取并發(fā)送文件內(nèi)容send(client, buf, strlen(buf), 0);fgets(buf, sizeof(buf), resource);} }/**********************************************************************/ /* Inform the client that a CGI script could not be executed.* Parameter: the client socket descriptor. */ /**********************************************************************/ void cannot_execute(int client) {char buf[1024]; //發(fā)送500sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "Content-type: text/html\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "<P>Error prohibited CGI execution.\r\n");send(client, buf, strlen(buf), 0); }/**********************************************************************/ /* Print out an error message with perror() (for system errors; based* on value of errno, which indicates system call errors) and exit the* program indicating an error. */ /**********************************************************************/ void error_die(const char *sc) {perror(sc);exit(1); }/**********************************************************************/ /* Execute a CGI script. Will need to set environment variables as* appropriate.* Parameters: client socket descriptor* path to the CGI script */ /**********************************************************************/ //執(zhí)行cgi動(dòng)態(tài)解析 void execute_cgi(int client, const char *path,const char *method, const char *query_string) {char buf[1024];int cgi_output[2];//聲明的讀寫(xiě)管道,切莫被名稱(chēng)給忽悠,會(huì)給出圖進(jìn)行說(shuō)明int cgi_input[2];//pid_t pid;int status;int i;char c;int numchars = 1;int content_length = -1;buf[0] = 'A'; buf[1] = '\0';if (strcasecmp(method, "GET") == 0)//如果是GET請(qǐng)求//讀取并且丟棄頭信息while ((numchars > 0) && strcmp("\n", buf)) numchars = get_line(client, buf, sizeof(buf));else {//處理的請(qǐng)求為POSTnumchars = get_line(client, buf, sizeof(buf));while ((numchars > 0) && strcmp("\n", buf)){//循環(huán)讀取頭信息找到Content-Length字段的值buf[15] = '\0';//目的是為了截取Content-Length:if (strcasecmp(buf, "Content-Length:") == 0)//"Content-Length: 15"content_length = atoi(&(buf[16]));//獲取Content-Length的值numchars = get_line(client, buf, sizeof(buf));}if (content_length == -1) {//錯(cuò)誤請(qǐng)求bad_request(client);return;}} //返回正確響應(yīng)碼200sprintf(buf, "HTTP/1.0 200 OK\r\n");send(client, buf, strlen(buf), 0); //#include<unistd.h> //int pipe(int filedes[2]); //返回值:成功,返回0,否則返回-1。參數(shù)數(shù)組包含pipe使用的兩個(gè)文件的描述符。fd[0]:讀管道,fd[1]:寫(xiě)管道。 //必須在fork()中調(diào)用pipe(),否則子進(jìn)程不會(huì)繼承文件描述符。 //兩個(gè)進(jìn)程不共享祖先進(jìn)程,就不能使用pipe。但是可以使用命名管道。 //pipe(cgi_output)執(zhí)行成功后,cgi_output[0]:讀通道 cgi_output[1]:寫(xiě)通道,這就是為什么說(shuō)不要被名稱(chēng)所迷惑if (pipe(cgi_output) < 0) {cannot_execute(client);return;}if (pipe(cgi_input) < 0) {cannot_execute(client);return;}if ( (pid = fork()) < 0 ) {cannot_execute(client);return;}//fork出一個(gè)子進(jìn)程運(yùn)行cgi腳本if (pid == 0) /* 子進(jìn)程: 運(yùn)行CGI 腳本 */{char meth_env[255];char query_env[255];char length_env[255];dup2(cgi_output[1], 1);//1代表著stdout,0代表著stdin,將系統(tǒng)標(biāo)準(zhǔn)輸出重定向?yàn)閏gi_output[1]dup2(cgi_input[0], 0);//將系統(tǒng)標(biāo)準(zhǔn)輸入重定向?yàn)閏gi_input[0],這一點(diǎn)非常關(guān)鍵,//cgi程序中用的是標(biāo)準(zhǔn)輸入輸出進(jìn)行交互close(cgi_output[0]);//關(guān)閉了cgi_output中的讀通道close(cgi_input[1]);//關(guān)閉了cgi_input中的寫(xiě)通道//CGI標(biāo)準(zhǔn)需要將請(qǐng)求的方法存儲(chǔ)環(huán)境變量中,然后和cgi腳本進(jìn)行交互//存儲(chǔ)REQUEST_METHODsprintf(meth_env, "REQUEST_METHOD=%s", method);putenv(meth_env);if (strcasecmp(method, "GET") == 0) {//存儲(chǔ)QUERY_STRINGsprintf(query_env, "QUERY_STRING=%s", query_string);putenv(query_env);}else { /* POST *///存儲(chǔ)CONTENT_LENGTHsprintf(length_env, "CONTENT_LENGTH=%d", content_length);putenv(length_env);}// 表頭文件#include<unistd.h>// 定義函數(shù)// int execl(const char * path,const char * arg,....);// 函數(shù)說(shuō)明// execl()用來(lái)執(zhí)行參數(shù)path字符串所代表的文件路徑,接下來(lái)的參數(shù)代表執(zhí)行該文件時(shí)傳遞過(guò)去的argv(0)、argv[1]……,最后一個(gè)參數(shù)必須用空指針(NULL)作結(jié)束。// 返回值// 如果執(zhí)行成功則函數(shù)不會(huì)返回,執(zhí)行失敗則直接返回-1,失敗原因存于errno中。execl(path, path, NULL);//執(zhí)行CGI腳本exit(0);} else { /* 父進(jìn)程 */close(cgi_output[1]);//關(guān)閉了cgi_output中的寫(xiě)通道,注意這是父進(jìn)程中cgi_output變量和子進(jìn)程要區(qū)分開(kāi)close(cgi_input[0]);//關(guān)閉了cgi_input中的讀通道if (strcasecmp(method, "POST") == 0)for (i = 0; i < content_length; i++) {//開(kāi)始讀取POST中的內(nèi)容recv(client, &c, 1, 0);//將數(shù)據(jù)發(fā)送給cgi腳本write(cgi_input[1], &c, 1);}//讀取cgi腳本返回?cái)?shù)據(jù)while (read(cgi_output[0], &c, 1) > 0)//發(fā)送給瀏覽器send(client, &c, 1, 0); //運(yùn)行結(jié)束關(guān)閉close(cgi_output[0]);close(cgi_input[1]); //定義函數(shù):pid_t waitpid(pid_t pid, int * status, int options); //函數(shù)說(shuō)明:waitpid()會(huì)暫時(shí)停止目前進(jìn)程的執(zhí)行, 直到有信號(hào)來(lái)到或子進(jìn)程結(jié)束. //如果在調(diào)用wait()時(shí)子進(jìn)程已經(jīng)結(jié)束, 則wait()會(huì)立即返回子進(jìn)程結(jié)束狀態(tài)值. 子進(jìn)程的結(jié)束狀態(tài)值會(huì)由參數(shù)status 返回, //而子進(jìn)程的進(jìn)程識(shí)別碼也會(huì)一快返回. //如果不在意結(jié)束狀態(tài)值, 則參數(shù)status 可以設(shè)成NULL. 參數(shù)pid 為欲等待的子進(jìn)程識(shí)別碼, 其他數(shù)值意義如下: //1、pid<-1 等待進(jìn)程組識(shí)別碼為pid 絕對(duì)值的任何子進(jìn)程. //2、pid=-1 等待任何子進(jìn)程, 相當(dāng)于wait(). //3、pid=0 等待進(jìn)程組識(shí)別碼與目前進(jìn)程相同的任何子進(jìn)程. //4、pid>0 等待任何子進(jìn)程識(shí)別碼為pid 的子進(jìn)程.waitpid(pid, &status, 0);} }/**********************************************************************/ /* Get a line from a socket, whether the line ends in a newline,* carriage return, or a CRLF combination. Terminates the string read* with a null character. If no newline indicator is found before the* end of the buffer, the string is terminated with a null. If any of* the above three line terminators is read, the last character of the* string will be a linefeed and the string will be terminated with a* null character.* Parameters: the socket descriptor* the buffer to save the data in* the size of the buffer* Returns: the number of bytes stored (excluding null) */ /**********************************************************************/ //解析一行http報(bào)文 int get_line(int sock, char *buf, int size) {int i = 0;char c = '\0';int n;while ((i < size - 1) && (c != '\n')){n = recv(sock, &c, 1, 0);/* DEBUG printf("%02X\n", c); */if (n > 0){if (c == '\r'){n = recv(sock, &c, 1, MSG_PEEK);/* DEBUG printf("%02X\n", c); */if ((n > 0) && (c == '\n'))recv(sock, &c, 1, 0);elsec = '\n';}buf[i] = c;i++;}elsec = '\n';}buf[i] = '\0';return(i); }/**********************************************************************/ /* Return the informational HTTP headers about a file. */ /* Parameters: the socket to print the headers on* the name of the file */ /**********************************************************************/ void headers(int client, const char *filename) {char buf[1024];(void)filename; /* could use filename to determine file type */ //發(fā)送HTTP頭strcpy(buf, "HTTP/1.0 200 OK\r\n");send(client, buf, strlen(buf), 0);strcpy(buf, SERVER_STRING);send(client, buf, strlen(buf), 0);sprintf(buf, "Content-Type: text/html\r\n");send(client, buf, strlen(buf), 0);strcpy(buf, "\r\n");send(client, buf, strlen(buf), 0); }/**********************************************************************/ /* Give a client a 404 not found status message. */ /**********************************************************************/ void not_found(int client) {char buf[1024];//返回404sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, SERVER_STRING);send(client, buf, strlen(buf), 0);sprintf(buf, "Content-Type: text/html\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "<HTML><TITLE>Not Found</TITLE>\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "<BODY><P>The server could not fulfill\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "your request because the resource specified\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "is unavailable or nonexistent.\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "</BODY></HTML>\r\n");send(client, buf, strlen(buf), 0); }/**********************************************************************/ /* Send a regular file to the client. Use headers, and report* errors to client if they occur.* Parameters: a pointer to a file structure produced from the socket* file descriptor* the name of the file to serve */ /**********************************************************************/ //將請(qǐng)求的文件發(fā)送回瀏覽器客戶(hù)端 void serve_file(int client, const char *filename) {FILE *resource = NULL;int numchars = 1;char buf[1024];buf[0] = 'A'; buf[1] = '\0';//這個(gè)賦值不清楚是干什么的while ((numchars > 0) && strcmp("\n", buf)) //將HTTP請(qǐng)求頭讀取并丟棄numchars = get_line(client, buf, sizeof(buf)); //打開(kāi)文件resource = fopen(filename, "r");if (resource == NULL)//如果文件不存在,則返回not_foundnot_found(client);else{//添加HTTP頭headers(client, filename);//并發(fā)送文件內(nèi)容cat(client, resource);}fclose(resource);//關(guān)閉文件句柄 }/**********************************************************************/ /* This function starts the process of listening for web connections* on a specified port. If the port is 0, then dynamically allocate a* port and modify the original port variable to reflect the actual* port.* Parameters: pointer to variable containing the port to connect on* Returns: the socket */ /**********************************************************************/ //啟動(dòng)服務(wù)端 int startup(u_short *port) {int httpd = 0;struct sockaddr_in name; //設(shè)置http sockethttpd = socket(PF_INET, SOCK_STREAM, 0);if (httpd == -1)error_die("socket");memset(&name, 0, sizeof(name));name.sin_family = AF_INET;name.sin_port = htons(*port);name.sin_addr.s_addr = htonl(INADDR_ANY);//綁定端口if (bind(httpd, (struct sockaddr *)&name, sizeof(name)) < 0)error_die("bind");if (*port == 0) /*動(dòng)態(tài)分配一個(gè)端口 */{int namelen = sizeof(name);if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)error_die("getsockname");*port = ntohs(name.sin_port);}//監(jiān)聽(tīng)連接if (listen(httpd, 5) < 0)error_die("listen");return(httpd); }/**********************************************************************/ /* Inform the client that the requested web method has not been* implemented.* Parameter: the client socket */ /**********************************************************************/ void unimplemented(int client) {char buf[1024]; //發(fā)送501說(shuō)明相應(yīng)方法沒(méi)有實(shí)現(xiàn)sprintf(buf, "HTTP/1.0 501 Method Not Implemented\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, SERVER_STRING);send(client, buf, strlen(buf), 0);sprintf(buf, "Content-Type: text/html\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "<HTML><HEAD><TITLE>Method Not Implemented\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "</TITLE></HEAD>\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "<BODY><P>HTTP request method not supported.\r\n");send(client, buf, strlen(buf), 0);sprintf(buf, "</BODY></HTML>\r\n");send(client, buf, strlen(buf), 0); }/**********************************************************************/int main(void) {int server_sock = -1;u_short port = 0;int client_sock = -1;struct sockaddr_in client_name;int client_name_len = sizeof(client_name);pthread_t newthread; //啟動(dòng)server socketserver_sock = startup(&port);printf("httpd running on port %d\n", port);while (1){//接受客戶(hù)端連接client_sock = accept(server_sock,(struct sockaddr *)&client_name,&client_name_len);if (client_sock == -1)error_die("accept");/*啟動(dòng)線(xiàn)程處理新的連接 */if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0)perror("pthread_create");} //關(guān)閉server socketclose(server_sock);return(0); }不過(guò)這個(gè)項(xiàng)目并不能直接在Linux上編譯運(yùn)行。它本來(lái)是在solaris上實(shí)現(xiàn)的,貌似在socket和pthread的實(shí)現(xiàn)上和一般的Linux還是不一樣的,需要修改一部分內(nèi)容。至于如何修改大家參考這篇文章,我也將修改版上傳到github上了,名稱(chēng)為tinyhttpd-0.1.0_for_linux,大家可以clone下來(lái),直接make編譯即可。下面演示一下如何運(yùn)行tinyhttpd,編譯完成的效果如下:
下面運(yùn)行./httpd,并在瀏覽器中訪(fǎng)問(wèn)。
tinyhttpd默認(rèn)cgi腳本是perl腳本,比如color.cgi,位于htdocs目錄下。
#!/usr/bin/perl -Twuse strict; use CGI;my($cgi) = new CGI;print $cgi->header; my($color) = "blue"; $color = $cgi->param('color') if defined $cgi->param('color');print $cgi->start_html(-title => uc($color),-BGCOLOR => $color); print $cgi->h1("This is $color"); print $cgi->end_html;下面我想用python來(lái)實(shí)現(xiàn)cgi腳本,添加一些頁(yè)面,為了更加了解cgi程序的運(yùn)行實(shí)質(zhì),不用python封裝好的cgi模塊,完全手工打造。首先在htdocs目錄下添加一個(gè)register.html頁(yè)面,html文檔內(nèi)容如下:
<html><head><title>注冊(cè)信息</title><meta charset="utf-8"></head><body><form action="register.cgi" method="POST">賬號(hào):<input type="text" name="zhanghao" value="" size="10" maxlength="5"><br><br>密碼:<input type="password" value="" name="mima" size="10"><br><br><input type="hidden" value="隱藏的內(nèi)容" name="mihiddenma" size="10">愛(ài)好:<input type="checkbox" name="tiyu" checked="checked">體育<input type="checkbox" name="changge">唱歌<br><br>性別:<input type="radio" name="sex" checked="checked">男<input type="radio" name="sex">女<br><br>自我介紹:<br><textarea cols="35" rows="10" name="ziwojieshao">這里是自我介紹</textarea><br><br>地址:<select name="dizhi"><option value="sichuan">四川</option><option value="beijing">北京</option><option value="shanghai">上海</option></select><br><br><input type="submit" value="提交"><input type="reset" value="重置"></form></body> </html>這是一個(gè)表單,action指向register.cgi,method為post。下面看一下register.cgi,其實(shí)是個(gè)python腳本。
#!/usr/bin/python #coding:utf-8 import sys,os length = os.getenv('CONTENT_LENGTH')if length:postdata = sys.stdin.read(int(length))print "Content-type:text/html\n"print '<html>' print '<head>' print '<title>POST</title>' print '</head>' print '<body>' print '<h2> POST data </h2>'print '<ul>'for data in postdata.split('&'):print '<li>'+data+'</li>'print '</ul>'print '</body>'print '</html>'else:print "Content-type:text/html\n"print 'no found'代碼的意思是從標(biāo)準(zhǔn)輸入中讀取post中的數(shù)據(jù),并將顯示數(shù)據(jù)輸出到標(biāo)準(zhǔn)輸出中,對(duì)比一下流程圖,更好理解。下面看一下運(yùn)行效果。
?
今天的分享就到這里,下一篇繼續(xù)分析。如果大家覺(jué)得還可以呀,記得推薦呦。
參考文章:HTTP協(xié)議全覽,tinyhttpd在Linux編譯 歡迎大家支持我公眾號(hào):
本文章屬于原創(chuàng)作品,歡迎大家轉(zhuǎn)載分享。尊重原創(chuàng),轉(zhuǎn)載請(qǐng)注明來(lái)自:七夜的故事?http://www.cnblogs.com/qiyeboy/
轉(zhuǎn)載于:https://www.cnblogs.com/qiyeboy/p/6296387.html
總結(jié)
以上是生活随笔為你收集整理的HTTP服务器的本质:tinyhttpd源码分析及拓展的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 【python之路14】发送邮件实例
- 下一篇: SQL大圣之路笔记——PowerDesi