1. 程式人生 > >實現簡單HTTP伺服器-圖片與CGI

實現簡單HTTP伺服器-圖片與CGI

簡介

在學習了深入理解作業系統第11章的內容後,更加深刻的理解的Socket,IO流,fork,Cgi 以及Http等知識。並且學習了HTTP伺服器的程式碼實現。並不複雜,大概300行,就可以實現傳送圖片,和執行CGI程式將結果返回給客戶端。

具體實現過程

伺服器程式碼:

程式碼量比較少,而且添加了一些註釋,簡明易懂。
csapp.h是該書的通用標頭檔案,定義了一些常量,以及進一步封裝了一些系統呼叫(檢查返回值等)。對應的函式實現程式碼為csapp.c
(全書的程式碼網上有資源)

#include "csapp.h"

void doit(int fd);
void read_requesthdrs(rio_t *rp);
int
parse_uri(char *uri, char *filename, char *cgiargs); void serve_static(int fd, char *filename, int filesize); void get_filetype(char *filename, char *filetype); void serve_dynamic(int fd, char *filename, char *cgiargs); void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char
*longmsg); int main(int argc, char **argv) { int listenfd, connfd, port, clientlen; struct sockaddr_in clientaddr; /* 檢查命令列輸入 */ if (argc != 2) { fprintf(stderr, "usage: %s <port>\n", argv[0]); exit(1); } port = atoi(argv[1]); //獲取伺服器埠號 //迴圈監聽客戶端請求 listenfd = Open_listenfd(port); while
(1) { clientlen = sizeof(clientaddr); connfd = Accept(listenfd, (SA *)&clientaddr, &clientlen); //接收客戶端的請求 doit(connfd); //執行服務 Close(connfd); //關閉與客戶端的連線 } } /* $end tinymain */ /* * doit - 處理一個Http請求 */ /* $begin doit */ void doit(int fd) { int is_static; //是否為靜態請求 struct stat sbuf; char buf[MAXLINE], method[MAXLINE], //請求方法 uri[MAXLINE], //uri路徑 version[MAXLINE]; //MAXLINE:8192 char filename[MAXLINE], //檔名 cgiargs[MAXLINE]; //cgi程式的引數 rio_t rio; //讀取客戶的請求 以及HTTP標頭檔案 //Rio_readinitb是封裝過的IO讀取函式 Rio_readinitb(&rio, fd); Rio_readlineb(&rio, buf, MAXLINE); //讀取請求 sscanf(buf, "%s %s %s", method, uri, version); //解析請求 if (strcasecmp(method, "GET")) { //只能提供GET clienterror(fd, method, "501", "Not Implemented", "Tiny does not implement this method"); return; } read_requesthdrs(&rio); //讀取請求頭 //從瀏覽器的輸入欄中解析GET請求 is_static = parse_uri(uri, filename, cgiargs); //靜態圖片 / 動態加法請求檢查 if (stat(filename, &sbuf) < 0) { //沒有使用者所請求的檔案 clienterror(fd, filename, "404", "Not found", "Tiny couldn't find this file"); return; } //line:netp:doit:endnotfound if (is_static) { /* 靜態請求,返回圖片*/ if (!(S_ISREG(sbuf.st_mode)) || !(S_IRUSR & sbuf.st_mode)) { //line:netp:doit:readable clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't read the file"); return; } serve_static(fd, filename, sbuf.st_size); //將圖片傳送給客戶端 } else { /* 呼叫CGI程式執行加法運算*/ if (!(S_ISREG(sbuf.st_mode)) || !(S_IXUSR & sbuf.st_mode)) { clienterror(fd, filename, "403", "Forbidden", "Tiny couldn't run the CGI program"); return; } serve_dynamic(fd, filename, cgiargs); //動態服務,傳入引數給cgi程式 } } /* $end doit */ /* * read_requesthdrs - read and parse HTTP request headers */ /* $begin read_requesthdrs */ void read_requesthdrs(rio_t *rp) { char buf[MAXLINE]; Rio_readlineb(rp, buf, MAXLINE); while(strcmp(buf, "\r\n")) { //HTTP Header 規定以\r\n結尾 Rio_readlineb(rp, buf, MAXLINE); printf("%s", buf); } return; } /* $end read_requesthdrs */ /* * * 解析瀏覽器發來的URI中的檔名 或者 是 CGI引數(加法的兩個數) * 返回值:0為動態CGI,1為靜態圖片 */ /* $begin parse_uri */ int parse_uri(char *uri, char *filename, char *cgiargs) { char *ptr; if (!strstr(uri, "cgi-bin")) { /* Static content */ //line:netp:parseuri:isstatic strcpy(cgiargs, ""); //line:netp:parseuri:clearcgi strcpy(filename, "."); //line:netp:parseuri:beginconvert1 strcat(filename, uri); //line:netp:parseuri:endconvert1 if (uri[strlen(uri)-1] == '/') //line:netp:parseuri:slashcheck strcat(filename, "home.html"); //line:netp:parseuri:appenddefault return 1; } else { /* Dynamic content */ //line:netp:parseuri:isdynamic ptr = index(uri, '?'); //line:netp:parseuri:beginextract if (ptr) { strcpy(cgiargs, ptr+1); *ptr = '\0'; } else strcpy(cgiargs, ""); //line:netp:parseuri:endextract strcpy(filename, "."); //line:netp:parseuri:beginconvert2 strcat(filename, uri); //line:netp:parseuri:endconvert2 return 0; } } /* $end parse_uri */ /* * * 靜態服務 : 拷貝圖片返回給客戶 */ /* $begin serve_static */ void serve_static(int fd, char *filename, int filesize) { int srcfd; char *srcp, filetype[MAXLINE], buf[MAXBUF]; /* 傳送伺服器響應Header給客戶端 */ get_filetype(filename, filetype); //line:netp:servestatic:getfiletype sprintf(buf, "HTTP/1.0 200 OK\r\n"); //line:netp:servestatic:beginserve sprintf(buf, "%sServer: Tiny Web Server\r\n", buf); sprintf(buf, "%sContent-length: %d\r\n", buf, filesize); sprintf(buf, "%sContent-type: %s\r\n\r\n", buf, filetype); Rio_writen(fd, buf, strlen(buf)); //line:netp:servestatic:endserve /* 通過記憶體對映,傳送檔案 */ srcfd = Open(filename, O_RDONLY, 0); //line:netp:servestatic:open srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0);//line:netp:servestatic:mmap Close(srcfd); //line:netp:servestatic:close Rio_writen(fd, srcp, filesize); //line:netp:servestatic:write Munmap(srcp, filesize); //line:netp:servestatic:munmap } /* * * 獲取檔案型別 */ void get_filetype(char *filename, char *filetype) { if (strstr(filename, ".html")) strcpy(filetype, "text/html"); else if (strstr(filename, ".gif")) strcpy(filetype, "image/gif"); else if (strstr(filename, ".jpg")) strcpy(filetype, "image/jpeg"); else strcpy(filetype, "text/plain"); } /* $end serve_static */ /* * * 動態服務:執行CGI程式,將結果返回給客戶端(瀏覽器顯示加法結果) */ /* $begin serve_dynamic */ void serve_dynamic(int fd, char *filename, char *cgiargs) { char buf[MAXLINE], *emptylist[] = { NULL }; /* Return first part of HTTP response */ sprintf(buf, "HTTP/1.0 200 OK\r\n"); Rio_writen(fd, buf, strlen(buf)); sprintf(buf, "Server: Tiny Web Server\r\n"); Rio_writen(fd, buf, strlen(buf)); //建立子程序 if (Fork() == 0) { /* child */ //line:netp:servedynamic:fork /* Real server would set all CGI vars here */ //傳送環境變數 setenv("QUERY_STRING", cgiargs, 1); //重定向stdout Dup2(fd, STDOUT_FILENO); //執行伺服器上的CGI程式 Execve(filename, emptylist, environ); } Wait(NULL); // 父程序等待子程序結束並回收 } /* $end serve_dynamic */ /* * * 返回請求錯誤訊息給客戶端 */ /* $begin clienterror */ void clienterror(int fd, char *cause, char *errnum, char *shortmsg, char *longmsg) { char buf[MAXLINE], body[MAXBUF]; /* Build the HTTP response body */ sprintf(body, "<html><title>Tiny Error</title>"); sprintf(body, "%s<body bgcolor=""ffffff"">\r\n", body); sprintf(body, "%s%s: %s\r\n", body, errnum, shortmsg); sprintf(body, "%s<p>%s: %s\r\n", body, longmsg, cause); sprintf(body, "%s<hr><em>The Tiny Web server</em>\r\n", body); /* Print the HTTP response */ sprintf(buf, "HTTP/1.0 %s %s\r\n", errnum, shortmsg); Rio_writen(fd, buf, strlen(buf)); sprintf(buf, "Content-type: text/html\r\n"); Rio_writen(fd, buf, strlen(buf)); sprintf(buf, "Content-length: %d\r\n\r\n", (int)strlen(body)); Rio_writen(fd, buf, strlen(buf)); Rio_writen(fd, body, strlen(body)); } /* $end clienterror */

Cgi程式-加法器adder.c :

#include "csapp.h"

int main(void) {
    char *buf, *p;
    char arg1[MAXLINE], arg2[MAXLINE], content[MAXLINE];
    int n1=0, n2=0;

    /* Extract the two arguments */
    if ((buf = getenv("QUERY_STRING")) != NULL) {
    p = strchr(buf, '&');
    *p = '\0';
    strcpy(arg1, buf);
    strcpy(arg2, p+1);
    n1 = atoi(arg1);
    n2 = atoi(arg2);
    }

    /* Make the response body */
    sprintf(content, "Welcome to add.com: ");
    sprintf(content, "%sTHE Internet addition portal.\r\n<p>", content);
    sprintf(content, "%sThe answer is: %d + %d = %d\r\n<p>", 
        content, n1, n2, n1 + n2);
    sprintf(content, "%sThanks for visiting!\r\n", content);

    /* Generate the HTTP response */
    printf("Content-length: %d\r\n", (int)strlen(content));
    printf("Content-type: text/html\r\n\r\n");
    printf("%s", content);
    fflush(stdout);
    exit(0);
}
/* $end adder */

HTTP伺服器目錄結構

cgi-bin中存放編譯好的adder.c的二進位制adder.o檔案:
這裡寫圖片描述

執行HTTP伺服器

這裡寫圖片描述

linux環境下瀏覽器請求服務

1)靜態服務,返回圖片

這裡寫圖片描述

2)CGI動態服務,返回加法結果:

這裡寫圖片描述

伺服器輸出日誌

這裡寫圖片描述