實現簡單HTTP伺服器-圖片與CGI
阿新 • • 發佈:2019-02-08
簡介
在學習了深入理解作業系統第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檔案: