上文解析HTTP请求报文(GET、POST),介绍了计划实现的WEB服务器的五个功能:展示主页(GET)、用户登录(POST)、用户注册(POST)、获取图片(GET)、获取视频(GET)。
通过正则表达式与状态机解析HTTP请求报文后,就获得了客户端请求资源的路径(path),回送响应报文的过程就是:根据请求写出相应的HTTP响应头,然后再将path上的文件置于响应体,通过socket发往客户端。
首先是制作HTTP响应头:
HTTP/2 200 OK\r\n
last-modified: Sat, 24 Dec 2022 10:30:31 GMT\r\n
content-encoding: br\r\n
content-type: application/javascript\r\n
Connection: keep-alive\r\n
server: tencent-cos\r\n
cache-control: max-age=31536000\r\n
content-length: 96194\r\n
\r\n
第一行是状态行,由协议(HTTP/2)、状态码(200)、状态码的原因短语(OK)三部分构成
其余是响应头,由头部字段名称(Connection) :值(keep-alive) 构成
最后响应头与响应体之间还有一行\r\n的空行
本文实现的响应头,包含200、400、403、404四种状态码及其对应的原因短语
响应头部分有Connection、Content-type、Content-length三个字段
生成状态码的方法:
利用int stat(const char *path, struct stat *buf) 函数
//按location路径无法搜到文件,或搜到的是目录文件(404找不到)
if(stat(location.c_str(),&mmFileStat)<0||S_ISDIR(mmFileStat.st_mode))
{
code=404;
}
else if (!(mmFileStat.st_mode&S_IROTH))//其他用户不可读(403禁止访问)
{
code=403;
}
获取Content-type对应值的方法:
提取path的后缀,然后利用哈希表SUFFIX_TYPE,得到对应值(例如:文件后缀为html,则对应值应为text/html )
std::string HttpResponse::getFileType()
{
size_t idx=path.find_last_of('.');
if(idx==std::string::npos)
{
return "text/plain";
}
std::string suffix=path.substr(idx);
if(SUFFIX_TYPE.count(suffix))
{
return SUFFIX_TYPE.find(suffix)->second;
}
return "text/plain";
}
其次是制作响应体:
响应体内应为path对应的文件,为了提高服务器从磁盘中读取响应文件的速度,采用mmap方法,建立私有的可读内存映射。
void* mmRet=mmap(0,mmFileStat.st_size,PROT_READ,MAP_PRIVATE,srcFd,0);
mmFileStat.st_size的值就是Content-length的对应值
HttpResponse类结构 httpresponse.h
#ifndef HTTPRESPONSE_H
#define HTTPRESPONSE_H
#include <unordered_map>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include "../log/log.h"
#include "../buffer/buffer.h"
class HttpResponse
{
public:
HttpResponse();
~HttpResponse();
void init(const std::string& srcDir_,const std::string& path_,bool isKeepAlive_, int code_);
void makeResponse(Buffer& buffer);
void unmapFile();
char* getfile();
size_t getFileLen();
private:
void addStateLine(Buffer& buffer);
void addHeader(Buffer& buffer);
void addContent(Buffer& buffer);
void errorHtml();
std::string getFileType();
int code;
bool isKeepAlive;
std::string path;
std::string srcDir;
char* mmFile;
struct stat mmFileStat;
static const std::unordered_map<std::string,std::string> SUFFIX_TYPE;
static const std::unordered_map<int,std::string> CODE_STATUS;
static const std::unordered_map<int,std::string> CODE_PATH;
};
#endif // !HTTPRESPONSE_H
HttpResponse类实现 httpresponse.cpp
#include "httpresponse.h"
const std::unordered_map<std::string,std::string> HttpResponse::SUFFIX_TYPE=
{
{ ".html", "text/html" },
{ ".xml", "text/xml" },
{ ".xhtml", "application/xhtml+xml" },
{ ".txt", "text/plain" },
{ ".rtf", "application/rtf" },
{ ".pdf", "application/pdf" },
{ ".word", "application/nsword" },
{ ".png", "image/png" },
{ ".gif", "image/gif" },
{ ".jpg", "image/jpeg" },
{ ".jpeg", "image/jpeg" },
{ ".au", "audio/basic" },
{ ".mpeg", "video/mpeg" },
{ ".mpg", "video/mpeg" },
{ ".avi", "video/x-msvideo" },
{ ".mp4", "video/mp4" },
{ ".gz", "application/x-gzip" },
{ ".tar", "application/x-tar" },
{ ".css", "text/css "},
{ ".js", "text/javascript "}
};
const std::unordered_map<int,std::string> HttpResponse::CODE_STATUS=
{
{ 200, "OK" },
{ 400, "Bad Request" },
{ 403, "Forbidden" },
{ 404, "Not Found" },
};
const std::unordered_map<int,std::string> HttpResponse::CODE_PATH=
{
{ 400, "/400.html" },
{ 403, "/403.html" },
{ 404, "/404.html" },
};
HttpResponse::HttpResponse()
{
code=-1;
isKeepAlive=false;
mmFile=nullptr;
mmFileStat={0};
Log::getInstance()->init();
}
HttpResponse::~HttpResponse()
{
unmapFile();
}
void HttpResponse::unmapFile()//关闭内存映射
{
if(mmFile)
{
munmap(mmFile,mmFileStat.st_size);
mmFile=nullptr;
}
}
void HttpResponse::init(const std::string& srcDir_,const std::string& path_, bool isKeepAlive_, int code_)
{
assert(srcDir_!="");
if(mmFile)
{
unmapFile();
}
srcDir=srcDir_;
code=code_;
path=path_;
isKeepAlive=isKeepAlive_;
}
void HttpResponse::makeResponse(Buffer& buffer)
{
std::string location=srcDir+path;
//按location路径无法搜到文件,或搜到的是目录文件(404找不到)
if(stat(location.c_str(),&mmFileStat)<0||S_ISDIR(mmFileStat.st_mode))
{
code=404;
}
else if (!(mmFileStat.st_mode&S_IROTH))//其他用户不可读(403禁止访问)
{
code=403;
}
else if(code==-1)
{
code=400;
}
errorHtml();//如果状态码是CODE_PATH中的400、403、404,则更改回送资源的路径path
addStateLine(buffer);//添加状态行
addHeader(buffer);//添加响应头
addContent(buffer);//添加响应体
}
void HttpResponse::errorHtml()//如果状态码是CODE_PATH中的400、403、404,则更改回送资源的路径path
{
if(CODE_PATH.count(code))
{
path=CODE_PATH.find(code)->second;
std::string location=srcDir+path;
stat(location.c_str(),&mmFileStat);
}
}
void HttpResponse::addStateLine(Buffer& buffer)//添加状态行
{
std::string status;
if(CODE_STATUS.count(code))
{
status=CODE_STATUS.find(code)->second;
}
else
{
code=400;
status=CODE_STATUS.find(400)->second;
}
buffer.append("HTTP/1.1 "+std::to_string(code)+" "+status+"\r\n");
}
void HttpResponse::addHeader(Buffer& buffer)//添加响应头
{
buffer.append("Connection: ");
if(isKeepAlive)
{
buffer.append("keep-alive\r\n");
}
else
{
buffer.append("close\r\n");
}
buffer.append("Content-type: "+getFileType()+"\r\n");
}
void HttpResponse::addContent(Buffer& buffer)//添加响应体
{
std::string location=srcDir+path;
int srcFd=open(location.c_str(),O_RDONLY);
if(srcFd<0)
{
LOG_ERROR("%s","Response File NotFind!");
return ;
}
//建立私有的可读内存映射,提高读取磁盘文件的速率
void* mmRet=mmap(0,mmFileStat.st_size,PROT_READ,MAP_PRIVATE,srcFd,0);
if(mmRet==MAP_FAILED)
{
LOG_ERROR("%s","Response File NotFind!");
return ;
}
mmFile=(char*)mmRet;
close(srcFd);
buffer.append("Content-length: "+std::to_string(mmFileStat.st_size)+"\r\n\r\n");
}
std::string HttpResponse::getFileType()//获取请求资源的类型
{
size_t idx=path.find_last_of('.');
if(idx==std::string::npos)
{
return "text/plain";
}
std::string suffix=path.substr(idx);
if(SUFFIX_TYPE.count(suffix))
{
return SUFFIX_TYPE.find(suffix)->second;
}
return "text/plain";
}
char* HttpResponse::getfile()//返回映射文件的句柄
{
return mmFile;
}
size_t HttpResponse::getFileLen()//返回映射文件的大小
{
return mmFileStat.st_size;
}