通过上一讲的讲述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/Thttpd源程序解析8 一个完整的HTTP过程之文件上传-cgi文件",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;
}