已經有一個月沒有更新博客了,一方面是因為平時太忙了,另一方面是想積攢一些干貨進行分享。最近主要是做了一些開源項目的源碼分析工作,有c項目也有python項目,想提升一下內功,今天分享一下tinyhttpd源碼分析的成果。tinyhttpd是一個非常輕量型的http服務器,c代碼500行左右,可以幫助我們了解http服務器運行的實質。在分析之前,我們先說一下http報文。(我的新書《Python爬蟲開發與項目實戰》出版了,大家可以看一下樣章)
一.http請求
http請求由三部分組成,分別是:起始行、消息報頭、請求正文
Request Line<CRLF>
Header-Name: header-value<CRLF>
Header-Name: header-value<CRLF>
//一個或多個,均以<CRLF>結尾
<CRLF>
body//請求正文
1、起始行以一個方法符號開頭,以空格分開,后面跟著請求的URI和協議的版本,格式如下:
Method Request-URI HTTP-Version CRLF
其中 Method表示請求方法;Request-URI是一個統一資源標識符;HTTP-Version表示請求的HTTP協議版本;CRLF表示回車和換行(除了作為結尾的CRLF外,不允許出現單獨的CR或LF字符)。
2、請求方法(所有方法全為大寫)有多種,各個方法的解釋如下:
- GET 請求獲取Request-URI所標識的資源
- POST 在Request-URI所標識的資源后附加新的數據
- HEAD 請求獲取由Request-URI所標識的資源的響應消息報頭
- PUT 請求服務器存儲一個資源,并用Request-URI作為其標識
- DELETE 請求服務器刪除Request-URI所標識的資源
- TRACE 請求服務器回送收到的請求信息,主要用于測試或診斷
- CONNECT 保留將來使用
- OPTIONS 請求查詢服務器的性能,或者查詢與資源相關的選項和需求
應用舉例:?
GET方法:在瀏覽器的地址欄中輸入網址的方式訪問網頁時,瀏覽器采用GET方法向服務器獲取資源,eg:?
GET /form.html HTTP/1.1 (CRLF)
POST方法要求被請求服務器接受附在請求后面的數據,常用于提交表單。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表示消息報頭已經結束,在此之前為消息報頭
user=jeffrey&pwd=1234 //此行以下為提交的數據
?
二.tinyhttpd源碼分析
tinyhttpd總共包含以下函數:
void accept_request(int);//處理從套接字上監聽到的一個 HTTP 請求
void bad_request(int);//返回給客戶端這是個錯誤請求,400響應碼
void cat(int, FILE *);//讀取服務器上某個文件寫到 socket 套接字
void cannot_execute(int);//處理發生在執行 cgi 程序時出現的錯誤
void error_die(const char *);//把錯誤信息寫到 perror?
void execute_cgi(int, const char *, const char *, const char *);//運行cgi腳本,這個非常重要,涉及動態解析
int get_line(int, char *, int);//讀取一行HTTP報文
void headers(int, const char *);//返回HTTP響應頭
void not_found(int);//返回找不到請求文件
void serve_file(int, const char *);//調用 cat 把服務器文件內容返回給瀏覽器。
int startup(u_short *);//開啟http服務,包括綁定端口,監聽,開啟線程處理鏈接
void unimplemented(int);//返回給瀏覽器表明收到的 HTTP 請求所用的 method 不被支持。
建議源碼閱讀順序: main -> startup -> accept_request -> execute_cgi
按照以上順序,看一下瀏覽器和tinyhttpd交互的整個流程:
三.注釋版源碼
注釋版源碼已經放到github上了,以后所有的源碼分析都會上傳github上。由于tinyhttpd源碼較少,下面將完整的代碼貼出來。
/* 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))
//函數說明:檢查參數c是否為空格字符,
//也就是判斷是否為空格(' ')、定位字符(' \t ')、CR(' \r ')、換行(' \n ')、垂直定位字符(' \v ')或翻頁(' \f ')的情況。
//返回值:若參數c 為空白字符,則返回非 0,否則返回 0。#define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n"//定義server名稱void accept_request(int);//接收請求void bad_request(int);//無效請求
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 */
/**********************************************************************/
//接收客戶端的連接,并讀取請求數據
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報文數據numchars = get_line(client, buf, sizeof(buf));//i = 0; j = 0;//對于HTTP報文來說,第一行的內容即為報文的起始行,格式為<method> <request-URL> <version>,//每個字段用空白字符相連while (!ISspace(buf[j]) && (i < sizeof(method) - 1)){//提取其中的請求方式是GET還是POSTmethod[i] = buf[j];i++; j++;}method[i] = '\0';
//函數說明:strcasecmp()用來比較參數s1 和s2 字符串,比較時會自動忽略大小寫的差異。
//返回值:若參數s1 和s2 字符串相同則返回0。s1 長度大于s2 長度則返回大于0 的值,s1 長度若小于s2 長度則返回小于0 的值。if (strcasecmp(method, "GET") && strcasecmp(method, "POST")){//tinyhttp僅僅實現了GET和POSTunimplemented(client);return;}
//cgi為標志位,置1說明開啟cgi解析if (strcasecmp(method, "POST") == 0)
//如果請求方法為POST,需要cgi解析cgi = 1;i = 0;//將method后面的后邊的空白字符略過while (ISspace(buf[j]) && (j < sizeof(buf)))j++;//繼續讀取request-URLwhile (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))){url[i] = buf[j];i++; j++;}url[i] = '\0';
//如果是GET請求,url可能會帶有?,有查詢參數if (strcasecmp(method, "GET") == 0){query_string = url;while ((*query_string != '?') && (*query_string != '\0'))query_string++;if (*query_string == '?'){//如果帶有查詢參數,需要執行cgi,解析參數,設置標志位為1cgi = 1;//將解析參數截取下來*query_string = '\0';query_string++;}}
//以上已經將起始行解析完畢
//url中的路徑格式化到pathsprintf(path, "htdocs%s", url);
//學習到這里明天繼續TODO
//如果path只是一個目錄,默認設置為首頁index.htmlif (path[strlen(path) - 1] == '/')strcat(path, "index.html");//函數定義: int stat(const char *file_name, struct stat *buf); //函數說明: 通過文件名filename獲取文件信息,并保存在buf所指的結構體stat中 //返回值: 執行成功則返回0,失敗返回-1,錯誤代碼存于errno(需要include <errno.h>)if (stat(path, &st) == -1) {//假如訪問的網頁不存在,則不斷的讀取剩下的請求頭信息,并丟棄即可while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */numchars = get_line(client, buf, sizeof(buf));//最后聲明網頁不存在not_found(client);}else{//如果訪問的網頁存在則進行處理if ((st.st_mode & S_IFMT) == S_IFDIR)//S_IFDIR代表目錄//如果路徑是個目錄,那就將主頁進行顯示strcat(path, "/index.html");if ((st.st_mode & S_IXUSR) ||(st.st_mode & S_IXGRP) ||(st.st_mode & S_IXOTH) )//S_IXUSR:文件所有者具可執行權限//S_IXGRP:用戶組具可執行權限//S_IXOTH:其他用戶具可讀取權限 cgi = 1;if (!cgi)//將靜態文件返回serve_file(client, path);else//執行cgi動態解析execute_cgi(client, path, method, query_string);}close(client);//因為http是面向無連接的,所以要關閉
}/**********************************************************************/
/* Inform the client that a request it has made has a problem.* Parameters: client socket */
/**********************************************************************/
void bad_request(int client)
{char buf[1024];
//發送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)
{
//發送文件的內容char buf[1024];
//讀取文件到buf中fgets(buf, sizeof(buf), resource);while (!feof(resource))//判斷文件是否讀取到末尾{//讀取并發送文件內容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];
//發送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 */
/**********************************************************************/
//執行cgi動態解析
void execute_cgi(int client, const char *path,const char *method, const char *query_string)
{char buf[1024];int cgi_output[2];//聲明的讀寫管道,切莫被名稱給忽悠,會給出圖進行說明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請求//讀取并且丟棄頭信息while ((numchars > 0) && strcmp("\n", buf)) numchars = get_line(client, buf, sizeof(buf));else {//處理的請求為POSTnumchars = get_line(client, buf, sizeof(buf));while ((numchars > 0) && strcmp("\n", buf)){//循環讀取頭信息找到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) {//錯誤請求bad_request(client);return;}}
//返回正確響應碼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。參數數組包含pipe使用的兩個文件的描述符。fd[0]:讀管道,fd[1]:寫管道。
//必須在fork()中調用pipe(),否則子進程不會繼承文件描述符。
//兩個進程不共享祖先進程,就不能使用pipe。但是可以使用命名管道。
//pipe(cgi_output)執行成功后,cgi_output[0]:讀通道 cgi_output[1]:寫通道,這就是為什么說不要被名稱所迷惑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出一個子進程運行cgi腳本if (pid == 0) /* 子進程: 運行CGI 腳本 */{char meth_env[255];char query_env[255];char length_env[255];dup2(cgi_output[1], 1);//1代表著stdout,0代表著stdin,將系統標準輸出重定向為cgi_output[1]dup2(cgi_input[0], 0);//將系統標準輸入重定向為cgi_input[0],這一點非常關鍵,//cgi程序中用的是標準輸入輸出進行交互close(cgi_output[0]);//關閉了cgi_output中的讀通道close(cgi_input[1]);//關閉了cgi_input中的寫通道//CGI標準需要將請求的方法存儲環境變量中,然后和cgi腳本進行交互//存儲REQUEST_METHODsprintf(meth_env, "REQUEST_METHOD=%s", method);putenv(meth_env);if (strcasecmp(method, "GET") == 0) {//存儲QUERY_STRINGsprintf(query_env, "QUERY_STRING=%s", query_string);putenv(query_env);}else { /* POST *///存儲CONTENT_LENGTHsprintf(length_env, "CONTENT_LENGTH=%d", content_length);putenv(length_env);}// 表頭文件#include<unistd.h>// 定義函數// int execl(const char * path,const char * arg,....);// 函數說明// execl()用來執行參數path字符串所代表的文件路徑,接下來的參數代表執行該文件時傳遞過去的argv(0)、argv[1]……,最后一個參數必須用空指針(NULL)作結束。// 返回值// 如果執行成功則函數不會返回,執行失敗則直接返回-1,失敗原因存于errno中。execl(path, path, NULL);//執行CGI腳本exit(0);} else { /* 父進程 */close(cgi_output[1]);//關閉了cgi_output中的寫通道,注意這是父進程中cgi_output變量和子進程要區分開close(cgi_input[0]);//關閉了cgi_input中的讀通道if (strcasecmp(method, "POST") == 0)for (i = 0; i < content_length; i++) {//開始讀取POST中的內容recv(client, &c, 1, 0);//將數據發送給cgi腳本write(cgi_input[1], &c, 1);}//讀取cgi腳本返回數據while (read(cgi_output[0], &c, 1) > 0)//發送給瀏覽器send(client, &c, 1, 0);
//運行結束關閉close(cgi_output[0]);close(cgi_input[1]);
//定義函數:pid_t waitpid(pid_t pid, int * status, int options);
//函數說明:waitpid()會暫時停止目前進程的執行, 直到有信號來到或子進程結束.
//如果在調用wait()時子進程已經結束, 則wait()會立即返回子進程結束狀態值. 子進程的結束狀態值會由參數status 返回,
//而子進程的進程識別碼也會一快返回.
//如果不在意結束狀態值, 則參數status 可以設成NULL. 參數pid 為欲等待的子進程識別碼, 其他數值意義如下:
//1、pid<-1 等待進程組識別碼為pid 絕對值的任何子進程.
//2、pid=-1 等待任何子進程, 相當于wait().
//3、pid=0 等待進程組識別碼與目前進程相同的任何子進程.
//4、pid>0 等待任何子進程識別碼為pid 的子進程.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報文
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 */
//發送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 */
/**********************************************************************/
//將請求的文件發送回瀏覽器客戶端
void serve_file(int client, const char *filename)
{FILE *resource = NULL;int numchars = 1;char buf[1024];buf[0] = 'A'; buf[1] = '\0';//這個賦值不清楚是干什么的while ((numchars > 0) && strcmp("\n", buf)) //將HTTP請求頭讀取并丟棄numchars = get_line(client, buf, sizeof(buf));
//打開文件resource = fopen(filename, "r");if (resource == NULL)//如果文件不存在,則返回not_foundnot_found(client);else{//添加HTTP頭headers(client, filename);//并發送文件內容cat(client, resource);}fclose(resource);//關閉文件句柄
}/**********************************************************************/
/* 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 */
/**********************************************************************/
//啟動服務端
int startup(u_short *port)
{int httpd = 0;struct sockaddr_in name;
//設置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) /*動態分配一個端口 */{int namelen = sizeof(name);if (getsockname(httpd, (struct sockaddr *)&name, &namelen) == -1)error_die("getsockname");*port = ntohs(name.sin_port);}//監聽連接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];
//發送501說明相應方法沒有實現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;
//啟動server socketserver_sock = startup(&port);printf("httpd running on port %d\n", port);while (1){//接受客戶端連接client_sock = accept(server_sock,(struct sockaddr *)&client_name,&client_name_len);if (client_sock == -1)error_die("accept");/*啟動線程處理新的連接 */if (pthread_create(&newthread , NULL, accept_request, client_sock) != 0)perror("pthread_create");}
//關閉server socketclose(server_sock);return(0);
}
不過這個項目并不能直接在Linux上編譯運行。它本來是在solaris上實現的,貌似在socket和pthread的實現上和一般的Linux還是不一樣的,需要修改一部分內容。至于如何修改大家參考這篇文章,我也將修改版上傳到github上了,名稱為tinyhttpd-0.1.0_for_linux,大家可以clone下來,直接make編譯即可。下面演示一下如何運行tinyhttpd,編譯完成的效果如下:
下面運行./httpd,并在瀏覽器中訪問。
tinyhttpd默認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來實現cgi腳本,添加一些頁面,為了更加了解cgi程序的運行實質,不用python封裝好的cgi模塊,完全手工打造。首先在htdocs目錄下添加一個register.html頁面,html文檔內容如下:
<html><head><title>注冊信息</title><meta charset="utf-8"></head><body><form action="register.cgi" method="POST">賬號:<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="隱藏的內容" name="mihiddenma" size="10">愛好:<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>
這是一個表單,action指向register.cgi,method為post。下面看一下register.cgi,其實是個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'
代碼的意思是從標準輸入中讀取post中的數據,并將顯示數據輸出到標準輸出中,對比一下流程圖,更好理解。下面看一下運行效果。
?
今天的分享就到這里,下一篇繼續分析。如果大家覺得還可以呀,記得推薦呦。
參考文章:HTTP協議全覽,tinyhttpd在Linux編譯
歡迎大家支持我公眾號:
本文章屬于原創作品,歡迎大家轉載分享。尊重原創,轉載請注明來自:七夜的故事?http://www.cnblogs.com/qiyeboy/