一、HTTP响应报文格式
cpp
HTTP/1.1 200 OK
Bdpagetype: 1
Bdqid: 0xf3c9743300024ee4
Cache-Control: private
Connection: keep-alive
Content-Encoding: gzip
Content-Type: text/html;charset=utf-8
Date: Fri, 26 Feb 2021 08:44:35 GMT
Expires: Fri, 26 Feb 2021 08:44:35 GMT
Server: BWS/1.1
Set-Cookie: BDSVRTM=13; path=/
Set-Cookie: BD_HOME=1; path=/
Set-Cookie: H_PS_PSSID=33514_33257_33273_31660_33570_26350; path=/; domain=.baidu.com
Strict-Transport-Security: max-age=172800
Traceid: 1614329075128412289017566699583927635684
X-Ua-Compatible: IE=Edge,chrome=1
Transfer-Encoding: chunked
二、根据解析出的原始数据,对客户端的请求做出处理 processHttpRequest
cpp
// 处理http请求协议
bool processHttpRequest(struct HttpRequest* req,struct HttpResponse* response);
cpp
// 处理基于get的http请求
bool processHttpRequest(struct HttpRequest* req,struct HttpResponse* response) {
if(strcasecmp(req->method,"get") != 0) {
return -1;
}
decodeMsg(req->url,req->url); // 解码字符串
// 处理客户端请求的静态资源(目录或者文件)
char* file = NULL;
if(strcmp(req->url,"/") == 0) {
file = "./";
}else {
file = req->url + 1;
}
// 获取文件属性
struct stat st;
int ret = stat(file,&st);
if(ret == -1) {
// 文件不存在 -- 回复404
// sendHeadMsg(cfd,404,"Not Found",getFileType(".html"),-1);
// sendFile("404.html",cfd);
strcpy(response->fileName,"404.html");
response->statusCode = NotFound;
strcpy(response->statusMsg,"Not Found");
// 响应头
httpResponseAddHeader(response,"Content-Type",getFileType(".html"));
response->sendDataFunc = sendFile;
return 0;
}
strcpy(response->fileName,file);
response->statusCode = OK;
strcpy(response->statusMsg,"OK!");
// 判断文件类型
if(S_ISDIR(st.st_mode)) {
// 把这个目录中的内容发送给客户端
// sendHeadMsg(cfd,200,"OK",getFileType(".html"),-1);
// sendDir(file,cfd);
// 响应头
httpResponseAddHeader(response,"Content-Type",getFileType(".html"));
response->sendDataFunc = sendDir;
}
else {
// 把文件的内容发送给客户端
// sendHeadMsg(cfd,200,"OK",getFileType(file),st.st_size);
// sendFile(file,cfd);
// 响应头
char tmp[12] = {0};
sprintf(tmp,"%ld",st.st_size);
httpResponseAddHeader(response,"content-type",getFileType(file));
httpResponseAddHeader(response,"content-length",tmp);
response->sendDataFunc = sendFile;
}
return 0;
}
1.解码字符串
- 解决浏览器无法访问带特殊字符的文件得到问题
cpp
// 将字符转换为整型数
int hexToDec(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;
}
// 解码字符串
// to 存储解码之后的数据, 传出参数, from被解码的数据, 传入参数
void decodeMsg(char* to,char* from) {
for(;*from!='\0';++to,++from) {
// isxdigit -> 判断字符是不是16进制格式, 取值在 0-f
// Linux%E5%86%85%E6%A0%B8.jpg
if(from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])){
// 将16进制的数 -> 十进制 将这个数值赋值给了字符 int -> char
// B2 == 178
// 将3个字符, 变成了一个字符, 这个字符就是原始数据
// *to = (hexToDec(from[1]) * 16) + hexToDec(from[2]);
*to = (hexToDec(from[1]) << 4) + hexToDec(from[2]);
// 跳过 from[1] 和 from[2] ,因此在当前循环中已经处理过了
from += 2;
}else{
// 字符拷贝,赋值
*to = *from;
}
}
*to = '\0';
}
2.判断文件扩展名,返回对应的 Content-Type(Mime-Type)
cpp
const char* getFileType(const char* name);
cpp
const char* getFileType(const char* name) {
// a.jpg a.mp4 a.html
// 自右向左查找 '.' 字符,如不存在返回NULL
const char* dot = strrchr(name,'.');
if(dot == NULL)
return "text/plain; charset=utf-8";//纯文本
if(strcmp(dot,".html") == 0 || strcmp(dot,".htm") == 0)
return "text/html; charset=utf-8";
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";
if(strcmp(dot,".pdf") == 0)
return "application/pdf";
return "text/plain; charset=utf-8";//纯文本
}
3.发送文件 sendFile
cpp
// 发送文件
void sendFile(const char* fileName,struct Buffer* sendBuf,int cfd);
cpp
void sendFile(const char* fileName,struct Buffer* sendBuf,int cfd) {
// 打开文件
int fd = open(fileName,O_RDONLY);
assert(fd > 0);
#if 1
while (1) {
char buf[1024];
int len = read(fd,buf,sizeof(buf));
if(len > 0) {
// send(cfd,buf,len,0);
bufferAppendData(sendBuf,buf,len);
}
else if(len == 0) {
break;
}
else{
close(fd);
perror("read");
}
}
#else
// 把文件内容发送给客户端
off_t offset = 0;
int size = lseek(fd,0,SEEK_END);// 文件指针移动到了尾部
lseek(fd,0,SEEK_SET);// 移动到文件头部
while (offset < size){
int ret = sendfile(cfd,fd,&offset,size - offset);
printf("ret value: %d\n",ret);
if (ret == -1 && errno == EAGAIN) {
printf("没数据...\n");
}
}
#endif
close(fd);
}
4.发送目录
cpp
// 发送目录
void sendDir(const char* dirName,struct Buffer* sendBuf,int cfd);
cpp
void sendDir(const char* dirName,struct Buffer* sendBuf,int cfd) {
char buf[4096] = {0};
sprintf(buf,"<html><head><title>%s</title></head><body><table>",dirName);
struct dirent** nameList;
int num = scandir(dirName,&nameList,NULL,alphasort);
for(int i=0;i<num;i++) {
// 取出文件名 nameList 指向的是一个指针数组 struct dirent* tmp[]
char* name = nameList[i]->d_name;
struct stat st;
char subPath[1024] = {0};
sprintf(subPath,"%s/%s",dirName,name);
stat(subPath,&st);
if(S_ISDIR(st.st_mode)) {
// 从当前目录跳到子目录里边,/
sprintf(buf+strlen(buf),
"<tr><td><a href=\"%s/\">%s</a></td><td>%ld</td></tr>",
name,name,st.st_size);
}else{
sprintf(buf+strlen(buf),
"<tr><td><a href=\"%s\">%s</a></td><td>%ld</td></tr>",
name,name,st.st_size);
}
// send(cfd,buf,strlen(buf),0);
bufferAppendString(sendBuf,buf);
memset(buf,0,sizeof(buf));
free(nameList[i]);
}
sprintf(buf,"</table></body></html>");
// send(cfd,buf,strlen(buf),0);
bufferAppendString(sendBuf,buf);
free(nameList);
}
三、解析http请求协议 parseHttpRequest
可以看这篇关于httpResponsePrepareMsg 函数具体是如何组织响应数据的:HttpResponse的定义和初始化 以及组织 HttpResponse 响应消息https://blog.csdn.net/weixin_41987016/article/details/135464760
cpp
// 解析http请求协议
bool parseHttpRequest(struct HttpRequest* req,struct Buffer* readBuf,
struct HttpResponse* response,struct Buffer* sendBuf,int socket);
cpp
// 解析http请求协议
bool parseHttpRequest(struct HttpRequest* req,struct Buffer* readBuf,
struct HttpResponse* response,struct Buffer* sendBuf,int socket) {
bool flag = true;
while(req->curState!=ParseReqDone) {
switch(req->curState) {
case ParseReqLine:
// 解析请求行
flag = parseHttpRequestLine(req,readBuf);
break;
case ParseReqHeaders:
// 解析请求头
flag = parseHttpRequestHeader(req,readBuf);
break;
case ParseReqBody:
break;
default:
break;
}
if(!flag) {
return flag;
}
// 判断是否解析完毕了,如果完毕了,需要准备回复的数据
if(req->curState==ParseReqDone) {
// 1.根据解析出的原始数据,对客户端的请求做出处理
processHttpRequest(req,response);
// 2.组织响应数据并发送给客户端
httpResponsePrepareMsg(response,sendBuf,socket);
}
}
req->curState = ParseReqLine;// 状态还原,保证还能继续处理第二条及以后的请求
return flag;
}