4. 重啟xinetd服務器 sudo service xinetd restart



#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <ctype.h>
#include <stdio.h>
#include <time.h>
#define SERVER_NAME "xhttpd"
#define PROTOCOL "HTTP/1.1"
#define SERVER_URL "http://www.itcast.cn/"
#define FORMAT_DATE "%a, %d %b %Y %H:%M:%S GMT"
#define N 4096
#ifdef DEBUG
#define log(info, str)\
do{
fprintf(fp_tmp, "%s%s", info, str);
fflush(fp_tmp);
}while(0)
/* put them in the right place.
//fp_tmp = fopen(/home/akaedu/dir/log.text, "a");
//log("argv[1]", argv[1]);
//log("line:", line);
*/
#endif
//設置http協議頭部分
static void send_headers(int status, char *title, char *extra_header, char *mime_type, off_t length, time_t mod);
//返回出錯頁面
static void send_error(int status, char *title, char *extra_header, char *text);
//用於文件名編碼,對應strdecode
static void strencode(char *to, size_t tosize, const char *from);
//輸出文件相關屬性詳細信息
static void file_infos(char *dir, char *name);
//URL解碼
static void strdecode(char *to, char *from);
//通過文件名推斷文件的mime_type
static char *get_mime_type(char *name);
//十六進制轉換為十進制數
static int hexit(char c);
int main(int argc, char **argv)
{
char line[N*2], method[N*2], path[N*2], protocol[N*2], idx[N*4], location[N*4];
char *file;
size_t len;
int ich, i, n;
struct stat sb;
FILE *fp;
struct dirent **dl;
if (argc != 2) //xinetd中的server_args
send_error(500, "Internal Error", NULL, "Config error - no dir specified.");
//xinetd啟動時把根路徑(xhttpd服務器文檔的根目錄)傳給本程序,這步非常非常重要
if (chdir(argv[1]) < 0) //更改工作目錄
send_error(500, "Internal Error", NULL, "Config error - couldn't chdir.");
//http協議第一行 如:GET /hello.c HTTP/1.1
if (fgets(line, sizeof(line), stdin) == NULL) //提取請求行,包含請求方法/路徑/協議
send_error(400, "Bad Request", NULL, "No request found.");
/* "%[^ ] %[^ ] %[^ ]" 將一行字符串按空格切割為三個子串
應用舉例:
GET方法:在浏覽器的地址欄中輸入網址訪問網頁時,浏覽器采用GET方法向服務器獲取資源,
eg:GET /form.html HTTP/1.1
method = GET
path = /form.html
protocol = HTTP/1.1
返回值:成功返回實際讀到的字段個數,失敗返回-1
*/
if (sscanf(line, "%[^ ] %[^ ] %[^ ]", method, path, protocol) != 3) //提取方法/路徑/協議
send_error(400, "Bad Request", NULL, "Can't parse request.");
//讀到請求頭結束
while (fgets(line, sizeof(line), stdin) != NULL) { //注意stdin被dup2至xinetd管道的讀端
if (strcmp(line, "\n") == 0 || strcmp(line, "\r\n") == 0)
break;
}
//只支持GET請求,strcasecmp,忽略大小寫
if (strcasecmp(method, "GET") != 0)
send_error(501, "Not Implemented", NULL, "That method is not implemented.");
//path必須從‘/’開始。
if (path[0] != '/')
send_error(400, "Bad Request", NULL, "Bad filename.");
file = &(path[1]); //file = path+1
//解碼路徑名,搞掉%20之類的東西
strdecode(file, file);
if (file[0] == '\0')
file = "./";
//檢測是否為合法的文件格式,防止越界訪問到根目錄上級目錄
len = strlen(file);
if (file[0] == '/' || strcmp(file, "..") == 0
|| strncmp(file, "../", 3) == 0
|| strstr(file, "/../") != NULL
|| strcmp(&(file[len-3]), "/..") == 0)
{
send_error(400, "Bad Request", (char*)0, "Illegal filename.");
}
//測試有無請求之文件(或目錄),stat為獲取指定目錄位置文件file函數,sb為傳出參數。
if (stat(file, &sb) < 0)
send_error(404, "Not Found", (char*)0, "File not found.");
/*
*404頁面 當用戶輸入了錯誤的鏈接時,返回的頁面,用戶無法解決該錯誤
*標准化可配置HTTP協議錯誤,定位在400到505之間
*
*1、無法在所請求的端口上訪問Web站點
*2、Web服務擴展鎖定策略阻止本請求
*3、MIME映射測略阻止本請求
*
*/
//請求路徑是目錄嗎
if (S_ISDIR(sb.st_mode)) { //路徑名對應目錄
if (file[len-1] != '/') { //路徑名要以"/"結尾
snprintf(location, sizeof(location), "Location: %s/", path);
send_error(302, "Found", location, "Directories must end with a slash.");
}
//有index.html則處理之
snprintf(idx, sizeof(idx), "%sindex.html", file);
if (stat(idx, &sb) >= 0) {
file = idx;
goto do_file; //如果有index.html則跳到do_file:
}
//顯示請求目錄下的文件列表
send_headers(200, "Ok", NULL, "text/html", -1, sb.st_mtime);
printf("<html><head><title>Index of %s</title></head>"
"\n<body bgcolor=\"#99cc99\"><h4>Index of %s</h4>\n<pre>\n"
, file, file);
/*
* int scandir(const char *dirp,
* struct dirent ***namelist,
* int (*filter)(const struct dirent *),
* int (*compar)(const struct dirent **, const struct dirent **)
* );
* 依據匹配項目,掃描一個目錄
* 掃描dirp目錄下(不包括子目錄)滿足filter過濾模式的文件,
* 返回的結果是compare函數經過排序的,並保存在namelist中。
*
* scandir() 函數掃描目錄 dirp,對每一個目錄項(文件名)調用filter()。
* 把每一個filter() 返回非零項目保存在一個通過malloc(3) 分配的緩存區裡,
* 再通過比較函數是compar() 的qsort(3) 函數排序,最後收集在namelist 的數組裡,
* 這個數組也是通過malloc(3) 分配的。如果filter 是 NULL,所有項目都被選擇
*
* alphasort和versionsort是使用到的兩種排序的函數
*
* scandir() 函數返回被選擇的目錄條數,或者如果出錯返回 -1。
*/
n = scandir(file, &dl, NULL, alphasort); //讀取目錄下各個目錄項,並返回每個文件信息.
if (n < 0)
perror("scandir");
else
for (i = 0; i < n; ++i)
file_infos(file, dl[i]->d_name);
printf("</pre>\n<hr>\n<address><a href=\"%s\">%s</a></address>\n</body></html>\n"
, SERVER_URL, SERVER_NAME);
} else { //所請求非目錄而是一個文件
do_file: //獲取文件內容並返回給客戶端
fp = fopen(file, "r"); //只讀方式將文件打開
if (fp == (FILE*)0)
send_error(403, "Forbidden", (char*)0, "File is protected.");
//發送http協議頭,200表示成功, OK是隨便寫的
send_headers(200, "Ok", (char*)0, get_mime_type(file), sb.st_size, sb.st_mtime);
while ((ich = getc(fp)) != EOF)
putchar(ich);
}
fflush(stdout);
exit(0);
}
//輸出文件相關的詳細屬性信息,包括日期和時間
static void file_infos(char *dir, char *name)
{
static char encoded_name[N];
static char path[N];
char timestr[16];
struct stat sb;
strencode(encoded_name, sizeof(encoded_name), name);
snprintf(path, sizeof(path), "%s/%s", dir, name);
if (lstat(path, &sb) < 0)
//設置顯示格式,"-"表示左對齊,不加"-"即為右對齊;-32表示顯示內容占32列不足用空格補齊;32s表輸出字符串的32個字符.
printf("<a href=\"%s\">%-32.32s</a> ???\n", encoded_name, name);
else {
strftime(timestr, sizeof(timestr), "%d%b%Y %H:%M", localtime(&sb.st_mtime));
printf("<a href=\"%s\">%-32.32s</a> %15s %14lld\n", encoded_name, name, timestr, (int64_t)sb.st_size);
}
/*
*size_t strftime(char *s, size_t max, const char *format,
const struct tm *tm);
* 將當前系統的絕對時間,按格式輸出。
* format以百分號(%)開始的格式命令集合,格式化輸出結果放在s中。
*
* %d 十進制表示的日期(01~31)
* %b 月份英文單詞的簡寫
* %Y 帶世紀部分的十進制年份
* %H 24小時制的小時
* %M 十進制表示的分鐘數(00~59)
*/
}
/*
*出錯時,發送錯誤相應信息,返回出錯頁面
*
*status:錯誤號 title:錯誤名 text:錯誤描述
*extra_header:附加描述(特殊情況302時不退出程序,而直接顯示正常頁面)
*/
static void send_error(int status, char* title, char* extra_header, char* text)
{
send_headers(status, title, extra_header, "text/html", -1, -1);
printf("<html><head><title>%d %s</title></head>\n<body bgcolor=\"#cc9999\"><h4>%d %s</h4>\n",
status, title, status, title);
printf("%s\n", text);
printf("<hr>\n<address><a href=\"%s\">%s</a></address>\n</body></html>\n", SERVER_URL, SERVER_NAME);
fflush(stdout);
exit( 1 );
}
/*
*每個HTTP傳送都包含一個首部、一個空行和要發送的數據項。
*Content-Type: 數據項的類型(必選項)
*Content-length: 數據項的大小
*Content-Encoding: 數據項使用的編碼方式
*Content-Language: 數據項使用的語言
*
*首部中的每一行都包含一個關鍵字、一個冒號和信息。
*e.g.
*Content-Type: text/html; charset=iso-8859-1 指明屬性的首部
*Content-Length: 508
* 這是一行空行
*<html> 文檔內容 </html> 數據項
*/
static void
send_headers(int status, char* title, char* extra_header, char* mime_type, off_t length, time_t mod)
{
time_t now;
char timebuf[100];
printf("%s %d %s\r\n", PROTOCOL, status, title);//HTTP/1.0 200 OK
printf("Server: %s\r\n", SERVER_NAME);//Server: xhttpd
now = time((time_t*)0);
strftime(timebuf, sizeof(timebuf), FORMAT_DATE, gmtime(&now));
printf("Date: %s\r\n", timebuf);
// Date: Fri, 18 Jul 2014 14:34:26 GMT
if (extra_header != NULL)
printf("%s\r\n", extra_header);
if (mime_type != NULL)
printf("Content-Type: %s\r\n", mime_type);
if (length >= 0)
printf("Content-Length: %lld\r\n", (int64_t)length);
if (mod != (time_t)-1) { //強轉
strftime(timebuf, sizeof(timebuf), FORMAT_DATE, gmtime(&mod));
printf("Last-Modified: %s\r\n", timebuf);
}
printf("Connection: close\r\n");
printf("\r\n");
}
static char *get_mime_type(char *name)
{
char* dot;
dot = strrchr(name, '.'); //自右向左查找‘.’字符;如不存在返回NULL
/*
*charset=iso-8859-1 西歐的編碼,說明網站采用的編碼是英文;
*charset=gb2312 說明網站采用的編碼是簡體中文;
*charset=utf-8 代表世界通用的語言編碼;
* 可以用到中文、韓文、日文等世界上所有語言編碼上
*charset=euc-kr 說明網站采用的編碼是韓文;
*charset=big5 說明網站采用的編碼是繁體中文;
*
*以下是依據傳遞進來的文件名,使用後綴判斷是何種文件類型
*將對應的文件類型按照http定義的關鍵字發送回去
*/
if (dot == (char*)0)
return "text/plain; charset=iso-8859-1";
if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)
return "text/html; charset=iso-8859-1";
if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)
return "image/jpeg";
if (strcmp(dot, ".gif") == 0)
return "image/gif";
if (strcmp(dot, ".png") == 0)
return "image/png";
if (strcmp(dot, ".css") == 0)
return "text/css";
if (strcmp(dot, ".au") == 0)
return "audio/basic";
if (strcmp( dot, ".wav") == 0)
return "audio/wav";
if (strcmp(dot, ".avi") == 0)
return "video/x-msvideo";
if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0)
return "video/quicktime";
if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)
return "video/mpeg";
if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)
return "model/vrml";
if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)
return "audio/midi";
if (strcmp(dot, ".mp3") == 0)
return "audio/mpeg";
if (strcmp(dot, ".ogg") == 0)
return "application/ogg";
if (strcmp(dot, ".pac") == 0)
return "application/x-ns-proxy-autoconfig";
return "text/plain; charset=iso-8859-1";
}
/*
* 處理URL中%20之類的東西!是"解碼"過程。
* %20 URL編碼中的‘ ’(space)
* %21 '!' %22 '"' %23 '#' %24 '$'
* %25 '%' %26 '&' %27 ''' %28 '('......
*
* 相關知識html中的‘ ’(space)是
*/
static void strdecode(char *to, char *from)
{
for ( ; *from != '\0'; ++to, ++from) {
if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])) {
*to = hexit(from[1])*16 + hexit(from[2]);
from += 2;//移過已經處理的兩個字符(%21指針指向1),表達式3的++from還會再向後移一個字符
} else
*to = *from;
}
*to = '\0';
}
//16進制數轉化為10進制, return 0不會出現
static int hexit(char c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return 0;
}
//"編碼",用作回寫浏覽器的時候,將除字母數字及/_.-~以外的字符轉義後回寫。
//strencode(encoded_name, sizeof(encoded_name), name);
static void strencode(char* to, size_t tosize, const char* from)
{
int tolen;
for (tolen = 0; *from != '\0' && tolen + 4 < tosize; ++from) {
if (isalnum(*from) || strchr("/_.-~", *from) != (char*)0) {
*to = *from;
++to;
++tolen;
} else {
sprintf(to, "%%%02x", (int) *from & 0xff);
to += 3;
tolen += 3;
}
}
*to = '\0';
}