通過上一講的講述Thttpd服務器接收到數據之後將會分析請求頭和請求首部,根據請求頭獲取需要使用的文件或者是CGI程序的路徑。
在這裏給大家講述一個項目中真是的文件上傳的例子,由於爲了加快處理的效率和速度對Thttpd程序進行的修改但是大致的步驟應該是一致的需要注意的地方我將會特別的標註出來。
我們假設提供文件上傳的可執行程序的名字叫做upload.cgi。
(1)用戶請求
當用戶需要上傳文件的時候將會發出請求xxxxx/upload.cgi的過程。大概的起始行應該是這樣的 post /upload.cgi HTTP/1.1/r/n
(2)服務器分析
當請求到達Thttpd服務器時將會對請求進行分析處理得知,要執行的程序的路徑在CGI文件路徑下名字叫做upload.cgi,並把環境變量和參數設置並調用execve函數執行此應用程序,預設置狀態碼爲200表示成功的執行。
(3)CGI程序的執行
上傳文件的主題部分也是主要分爲以下幾個部分:信息摘要碼開始,文件名稱,文件類型,文件內容,信息摘要碼結束,信息摘要碼的開始和結束是對文件信息的一個摘要計算開始和結束的值都是一樣的。其中信息摘要碼開始於文件名稱以一組回車換行符隔離,文件名稱和文件類型以一組回車換行符隔離,文件類型與文件內容以兩組回車換行符隔離,文件內容與信息摘要碼結束以一組回車換行符隔離。
所以整個程序的設計主要按照文件上傳正文部分的內容分隔分爲:主上傳處理,數據獲取,獲取信息摘要碼開始,獲取文件名,獲取文件類型,獲取文件內容,獲取信息摘要碼結束這幾個部分。由於我們做的項目返回的數據爲json格式所以我們引入了jansson庫。
(3.1)主上傳處理 main
創建json對象
設置回送的正文類型,緩存控制等信息。通過環境變量獲取正文的數據的長度存儲在val變量中這個變量是字符型轉換爲int型存儲在contentlen變量中,調度各個部分的執行過程。回送最終的信息。
(3.2)數據獲取 getdata
不斷的獲取數據直至需要讀取的數據爲0或者遇到了連續的回車換行符,返回讀取到的數據長度。
(3.3)獲取信息摘要碼開始 getstartcode
這一部分對於上傳文件可能並不是比較重要的部分如果不做信息摘要計算的話。
提取信息摘要碼開始,這裏耍了一個小聰明信息摘要碼開始比信息摘要碼結束部分少2個字節。使用codelen記錄信息摘要碼結束需要的字符數。可能存在問題,但是現在還沒有出現。
(3.4)文件名稱 getfilename
這一部分比較重要,需要從其中獲取文件的名稱具體的上傳格式爲 filename:xxxx.xxx\r\n。
所以主要的工作是獲取文件的名稱,去掉不需要的回車換行符,把文件名稱存儲在uploadfilename變量中。
(3.5)獲取文件類型 getfiletype
這一部分也不是很有意義,對於文件而言,只要支持二進制文件即支持所有的文件,所以都是用二進制的方式進行寫文件就可以了,但是要注意的是這部分和文件正文部分的間隔符使用了兩組回車換行符,所以這裏也使用了小聰明調用兩次getchar。
(3.6)獲取文件內容 getfilecontent
這一部分是真個上傳文件的重頭戲,設置文件存儲的位置,使用3.3部分的小聰明知道文件剩餘需要讀取的字節數和信息摘要碼結束需要使用的字節數就可以得到具體的正文部分的字節數,所以使用這個小手段直接計算需要寫的字節數寫在此文件中。
(3.7)信息摘要碼結束 getendcode
這一部分貌似也不是太重要但是繼續執行完畢吧,沒有測試過不進行這部分的數據讀取會出現什麼問題,連接關閉的話剩餘的數據應該是要被銷燬的,但是按照規程執行完吧。
(3.8)有與使用的是json類型文件,使用了jansson庫所以不需要的可以不使用,當然可以 手寫字符串形式的json格式數據,比如這裏的創建json對象失敗就是使用的手寫字符串形式的json格式數據。
#include <stdio.h>
#include <string.h>
#include <jansson.h>
char uploadfilename[64];
int contentlen;
int codelength;
void getstartcode();
int getfilename();
void getfiletype();
int getfilecontent();
void getendcode();
int getdata(char *data);
int main()
{
int flag=0;
char *val=NULL;
json_t *obj;
obj=json_object();
val=getenv("CONTENT_LENGTH");
printf("Content-type: application/json; charset=utf-8\r\n");
printf("Cache-Control: no-cache\r\n");
printf("Accept-Rangs:bytes\r\n");
printf("Connection:close\r\n");
printf("\r\n");
if(val!=NULL&&obj)
{
contentlen=atoi(val);
json_object_set_new(obj,"cmd",json_string("UploadFile"));
json_object_set_new(obj,"status",json_string("ERROR"));
json_object_set_new(obj,"error",json_null());
getstartcode();
if(!getfilename())
{
getfiletype();
int code=getfilecontent();
if(!code)
{
getendcode();
flag=1;
}
else
{
json_object_set(obj,"error",json_string("write file error"));
json_object_set_new(obj,"code",json_integer(code));
}
}
else
{
json_object_set(obj,"error",json_string("get file name error"));
}
}
else
{
printf("{\"cmd\":\"UploadFile\",\"status\":\"ERROR\",\"error\":\"init error\"}");
}
if(flag)
{
json_object_set(obj,"status",json_string("SUCCESS"));
}
char *str=NULL;
str=json_dumps(obj,JSON_PRESERVE_ORDER);
printf(str);
free(str);
if(str)
{
str=NULL;
}
printf("\r\n");
return 0;
}
void getstartcode()
{
char val[100];
codelength=getdata(val);
codelength+=2;
}
int getfilename()
{
int len=0;
char *name=NULL;
char filename[1024];
len=getdata(filename);
filename[len-3]=0;
name=strstr(filename,"filename");
if(name)
{
strcpy(uploadfilename,name+10);
return 0;
}
else
{
return 1;
}
}
void getfiletype()
{
char filetype[1024];
getdata(filetype);
getchar();
getchar();
contentlen-=2;
}
int getfilecontent()
{
int i;
ssize_t num;
int length=0;
char data[contentlen];
char path[256];
length=contentlen-codelength;
for(i=0;i<length;i++)
{
data[i]=getchar();
}
length-=2;
data[length]=0;
FILE *fp=NULL;
sprintf(path,"/tmp/%s",uploadfilename);
fp=fopen(path,"wb+");
if(fp)
{
if(length==fwrite(data,1,length,fp))
{
return 0;
}
else
{
return 1;
}
}
else
{
return 2;
}
}
void getendcode()
{
char data[100];
getdata(data);
}
int getdata(char *data)
{
int flag=0;
int n=0;
char ch;
while(contentlen>0)
{
ch=getchar();
data[n]=ch;
n++;
contentlen--;
if(flag)
{
flag=0;
if(ch==10)
{
data[n]=0;
return n;
}
}
else
{
if(ch==13)
{
flag=1;
}
}
}
return 0;
}