目录
一、请求的是Web根目录
二、GET方法通过URL传参
三、根据资源类型对应出Content-Type值
四、Http代码
项目完整源代码:Http · 周不才/cpp_linux study - 码云 - 开源中国
一、请求的是Web根目录
如果URL中请求的资源是Web根目录,则自动跳转到主页
例如QQ官网https://im.qq.com/,实际访问时会自动跳转到主页https://im.qq.com/index/
实现代码:
static const std::string homePage="index.html";//主页资源
//解析请求的资源路径path,如果请求的是web根目录
if(_path[_path.size()-1]=='/')
{
_path+=homePage;
}
二、GET方法通过URL传参
GET方法和POST方法都可以通过表单等方式向服务器传递数据
GET方法会将表单的数据存放到URL中,再发送给服务器。符号?后面就是参数
https://fanyi.baidu.com/mtpe-individual/multimodal?query=%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96&lang=zh2en
POST方法会将表单的数据存放到请求正文中,再发送给服务器
- POST传参的数据可以很大,因为存放在正文中;GET方法传参的数据一定很小,因为存放在URL中
- POST传参比GET传参安全,因为GET传参的数据会直接暴露在URL中。但这两种传参方法都不安全,需要对参数加密,即Https协议
提取GET方法传递的参数代码:
static const std::string argSep="?";//URL中参数的位置标识
//解析URL中携带的参数(GET方法)
if(strcasecmp(_method.c_str(),"GET")==0)//请求方法是GET
{
auto pos=_url.find(argSep);
if(pos!=std::string::npos)//URL中携带参数
{
_reqContent=_url.substr(pos+argSep.size());//提取URL中的参数,存放到请求正文中
_url.resize(pos);//URL中去除参数部分
}
}
三、根据资源类型对应出Content-Type值
根据请求后缀,如.html .jpg .png .gif等对应出Content-Type值
//--------------------------------------------------------
//建立资源后缀和Http协议中的资源类型之间的映射关系
//--------------------------------------------------------
_resourceType.insert({".html", "text/html"});
_resourceType.insert({".htm", "text/html"});
_resourceType.insert({".css", "text/css"});
_resourceType.insert({".txt", "text/plain"});
_resourceType.insert({".md", "text/markdown"});
// 脚本类
_resourceType.insert({".js", "application/javascript"});
_resourceType.insert({".mjs", "application/javascript"});
// 图像类
_resourceType.insert({".jpg", "image/jpeg"});
_resourceType.insert({".jpeg", "image/jpeg"});
_resourceType.insert({".png", "image/png"});
_resourceType.insert({".gif", "image/gif"});
_resourceType.insert({".webp", "image/webp"});
_resourceType.insert({".svg", "image/svg+xml"});
_resourceType.insert({".ico", "image/x-icon"});
// 字体类
_resourceType.insert({".woff", "font/woff"});
_resourceType.insert({".woff2", "font/woff2"});
_resourceType.insert({".ttf", "font/ttf"});
// 多媒体类
_resourceType.insert({".mp3", "audio/mpeg"});
_resourceType.insert({".wav", "audio/wav"});
_resourceType.insert({".mp4", "video/mp4"});
_resourceType.insert({".webm", "video/webm"});
// 文档类
_resourceType.insert({".pdf", "application/pdf"});
_resourceType.insert({".doc", "application/msword"});
_resourceType.insert({".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"});
_resourceType.insert({".xls", "application/vnd.ms-excel"});
_resourceType.insert({".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"});
_resourceType.insert({".ppt", "application/vnd.ms-powerpoint"});
_resourceType.insert({".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"});
// 压缩包类
_resourceType.insert({".zip", "application/zip"});
_resourceType.insert({".tar", "application/x-tar"});
_resourceType.insert({".gz", "application/gzip"});
// 数据格式类
_resourceType.insert({".json", "application/json"});
_resourceType.insert({".xml", "application/xml"});
_resourceType.insert({".csv", "text/csv"});
// 二进制流默认类型
_resourceType.insert({".bin", "application/octet-stream"});
大类 | Content-Type值 | 描述 |
---|---|---|
文本类型 | text/plain | 纯文本,无格式(如TXT文件) |
text/html | HTML文档,用于网页 | |
text/css | CSS样式表 | |
text/javascript | JavaScript代码(旧标准,推荐使用 application/javascript ) | |
text/markdown | Markdown格式文本 | |
图像类型 | image/jpeg | JPEG图像 |
image/png | PNG图像 | |
image/gif | GIF图像(支持动画) | |
image/webp | WebP格式图像(现代高效压缩格式) | |
image/svg+xml | SVG矢量图(基于XML) | |
应用程序类型 | application/json | JSON格式数据,常用于API交互 |
application/xml | XML数据 | |
application/pdf | PDF文档 | |
application/octet-stream | 二进制流(如文件下载) | |
application/x-www-form-urlencoded | 表单提交的默认编码格式 | |
application/zip | ZIP压缩文件 | |
application/javascript | JavaScript代码(现代标准) | |
application/wasm | WebAssembly模块(用于高性能Web应用) | |
多媒体类型 | audio/mpeg | MP3音频文件 |
audio/wav | WAV音频文件 | |
video/mp4 | MP4视频文件 | |
video/webm | WebM开放格式视频 | |
多部分类型 | multipart/form-data | 表单文件上传(支持二进制数据) |
multipart/byteranges | 分块传输响应(用于HTTP范围请求) | |
字体类型 | font/woff | Web开放字体格式(WOFF 1.0) |
font/woff2 | WOFF 2.0字体(更高压缩率) | |
其他类型 | application/vnd.ms-excel | Excel文件(旧版,如 .xls ) |
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet | 新版Excel文件(.xlsx ) |
四、Http代码
//应用层协议:HTTP协议
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <sstream>
#include <cstring>//提供strcasecmp,忽略大小写比较字符串
#include <memory>//智能指针
#include <fstream>//文件操作
static const std::string baseSep="\r\n";//行与行之间的分隔符
static const std::string lineSep=" ";//请求行中各个属性的分隔符
static const std::string argSep="?";//URL中参数的位置标识
static const std::string homePage="index.html";//主页资源
static const std::string suffixSep=".";//资源后缀符号
static const std::string headerSep=": ";//单个报头属性之间的分隔符
static const std::string webRootDir="wwwroot";//Web根目录(path初始值即为web根目录)
static const std::string httpVersion="HTTP/1.0";//默认的Http协议版本(用于响应)
//Http请求
class HttpRequest
{
private:
//Http协议请求的基本属性
std::string _reqLine;//请求行
std::vector<std::string> _reqHeaders;//请求报头
std::string _blankLine;//空行
std::string _reqContent;//请求正文
//Http协议请求的详细属性
std::string _method;//请求方法
std::string _url;//请求资源的路径URL
std::string _version;//Http版本
std::unordered_map<std::string,std::string> _reqHeadersKV;//请求报头属性集合(请求报头的各种属性存储在哈希表中)
std::string _path;//URL只是相对路径,要根据资源所在位置确定绝对路径
std::string _suffix;//资源后缀,用于确定请求的资源是什么类型,以便填充请求报头中的Content-Type属性
public:
//构造函数
HttpRequest()
:_blankLine(baseSep),
_path(webRootDir)
{}
//析构函数
~HttpRequest()
{}
private:
//解析请求协议,获取单行数据(目的是将请求行、请求报头、请求正文拆分开)
std::string GetLine(std::string& reqstr)
{
auto pos=reqstr.find(baseSep);//查找\r\n
if(pos!=std::string::npos)
{
std::string line=reqstr.substr(0,pos);
reqstr.erase(0,pos+baseSep.size());
if(line.empty()) return baseSep;//如果获取一行的内容是空,说明该行是空行,返回\r\n
else return line;//否则返回正常获取的一行数据
}
else
{
return std::string();
}
}
//解析请求行
void ParseReqLine()
{
//将请求行解析为请求方法、URL、http版本
std::stringstream ss(_reqLine);//按空格为分隔符
ss>>_method>>_url>>_version;
//--------------------------------------------------------------
//更详细地解析URL,以提取参数、初始化path、初始化资源后缀suffix
//1.提取参数
//解析URL中携带的参数(GET方法)
if(strcasecmp(_method.c_str(),"GET")==0)//请求方法是GET
{
auto pos=_url.find(argSep);
if(pos!=std::string::npos)//URL中携带参数
{
_reqContent=_url.substr(pos+argSep.size());//提取URL中的参数,存放到请求正文中
_url.resize(pos);//URL中去除参数部分
}
}
//2.初始化path
//初始化请求的资源路径path(绝对路径)
_path+=_url;
//解析请求的资源路径path,如果请求的是web根目录
if(_path[_path.size()-1]=='/')
{
_path+=homePage;
}
//3.初始化资源后缀suffix
auto pos=_path.rfind(suffixSep);
if(pos!=std::string::npos)
{
_suffix=_path.substr(pos);
}
else
{
_suffix=".default";
}
}
//解析请求报头
void ParseReqHeaders()
{
//将报头属性存储到哈希表中
for(auto& reqHeader:_reqHeaders)
{
auto pos=reqHeader.find(headerSep);
if(pos!=std::string::npos)
{
std::string k=reqHeader.substr(0,pos);
std::string v=reqHeader.substr(pos+headerSep.size());
if(k.empty()||v.empty()) continue;
_reqHeadersKV[k]=v;
}
else
{
continue;
}
}
}
public:
//反序列化:序列化字符串➡基本属性➡详细属性
void Deserialize(std::string& reqstr)
{
//基本的反序列化:拆分reqstr为请求行、请求报头、空行、请求正文
//请求行
_reqLine=GetLine(reqstr);
//请求报头 todo1
std::string reqHeader;
while((reqHeader=GetLine(reqstr))!=baseSep)
{
_reqHeaders.emplace_back(reqHeader);
}
//空行
_blankLine=baseSep;
//请求正文 todo2
_reqContent=reqstr;
//---------------------------------------------
//进一步反序列化:解析请求行、请求报头为更详细的属性
ParseReqLine();
ParseReqHeaders();
}
//获取路径
std::string GetPath()
{
return _path;
}
//获取正文内容
std::string GetReqContent()
{
return _reqContent;
}
//获取资源后缀
std::string GetSuffix()
{
return _suffix;
}
//输出反序列化结果
void Print()
{
std::cout<<"-----------------------------------------"<<std::endl;
std::cout<<"请求行:"<<_reqLine<<std::endl;
std::cout<<"请求方法:"<<_method<<std::endl;
std::cout<<"URL:"<<_url<<std::endl;
std::cout<<"Http版本:"<<_version<<std::endl;
for(auto& reqHeader: _reqHeaders)
{
std::cout<<"请求报头:"<<reqHeader<<std::endl;
}
std::cout<<"空行:"<<_blankLine;
std::cout<<"请求正文"<<_reqContent<<std::endl;
}
};
//Http应答
class HttpResponse
{
private:
//Http协议响应的基本属性
std::string _statusLine;//状态行
std::vector<std::string> _resHeaders;//响应报头
std::string _blankLine;//空行
std::string _resContent;//响应正文
//Http协议响应的详细属性
std::string _version;//http版本
int _statusCode;//状态码
std::string _statusDescribe;//状态码描述
std::unordered_map<std::string,std::string> _resHeadersKV;//响应报头属性集合
public:
//构造函数
HttpResponse()
:_blankLine(baseSep),
_version(httpVersion)
{}
//析构函数
~HttpResponse()
{}
public:
//序列化
std::string Serialize()
{
//构建状态行
_statusLine+=_version+lineSep+std::to_string(_statusCode)+lineSep+_statusDescribe+baseSep;
//构建应答报头
for(auto& resHeader: _resHeadersKV)
{
_resHeaders.emplace_back(resHeader.first+headerSep+resHeader.second+baseSep);
}
//正式序列化:详细属性➡基本属性➡序列化字符串
std::string responseStr=_statusLine;//加上状态行
for(auto& resHeader:_resHeaders)//加上响应报头
{
responseStr+=resHeader;
}
responseStr+=_blankLine;//加上空行
responseStr+=_resContent;//加上响应正文
return responseStr;
}
//设置属性
//设置状态码和状态码描述
void AddStatusCodeAndDescribe(int statusCode, const std::string& statusDescribe)
{
_statusCode=statusCode;
_statusDescribe=statusDescribe;
}
//添加报头属性
void AddHeader(const std::string& k, const std::string& v)
{
_resHeadersKV[k]=v;
}
//添加应答正文
void AddResContent(const std::string& resContent)
{
_resContent=resContent;
}
};
//Http处理(将请求处理为应答)
class HttpServer
{
private:
std::unordered_map<std::string,std::string> _resourceType;//资源类型(根据后缀,判定资源类型)
std::unordered_map<int,std::string> _codeToDescribe;//状态码-状态码描述
std::unordered_map<std::string,std::function<HttpResponse(HttpRequest)>> _serviceLists;//服务列表
public:
//构造函数
HttpServer()
{
//--------------------------------------------------------
//建立资源后缀和Http协议中的资源类型之间的映射关系
//--------------------------------------------------------
_resourceType.insert({".html", "text/html"});
_resourceType.insert({".htm", "text/html"});
_resourceType.insert({".css", "text/css"});
_resourceType.insert({".txt", "text/plain"});
_resourceType.insert({".md", "text/markdown"});
// 脚本类
_resourceType.insert({".js", "application/javascript"});
_resourceType.insert({".mjs", "application/javascript"});
// 图像类
_resourceType.insert({".jpg", "image/jpeg"});
_resourceType.insert({".jpeg", "image/jpeg"});
_resourceType.insert({".png", "image/png"});
_resourceType.insert({".gif", "image/gif"});
_resourceType.insert({".webp", "image/webp"});
_resourceType.insert({".svg", "image/svg+xml"});
_resourceType.insert({".ico", "image/x-icon"});
// 字体类
_resourceType.insert({".woff", "font/woff"});
_resourceType.insert({".woff2", "font/woff2"});
_resourceType.insert({".ttf", "font/ttf"});
// 多媒体类
_resourceType.insert({".mp3", "audio/mpeg"});
_resourceType.insert({".wav", "audio/wav"});
_resourceType.insert({".mp4", "video/mp4"});
_resourceType.insert({".webm", "video/webm"});
// 文档类
_resourceType.insert({".pdf", "application/pdf"});
_resourceType.insert({".doc", "application/msword"});
_resourceType.insert({".docx", "application/vnd.openxmlformats-officedocument.wordprocessingml.document"});
_resourceType.insert({".xls", "application/vnd.ms-excel"});
_resourceType.insert({".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"});
_resourceType.insert({".ppt", "application/vnd.ms-powerpoint"});
_resourceType.insert({".pptx", "application/vnd.openxmlformats-officedocument.presentationml.presentation"});
// 压缩包类
_resourceType.insert({".zip", "application/zip"});
_resourceType.insert({".tar", "application/x-tar"});
_resourceType.insert({".gz", "application/gzip"});
// 数据格式类
_resourceType.insert({".json", "application/json"});
_resourceType.insert({".xml", "application/xml"});
_resourceType.insert({".csv", "text/csv"});
// 二进制流默认类型
_resourceType.insert({".bin", "application/octet-stream"});
//--------------------------------------------------------
// 建立状态码和状态码描述之间的映射关系
//--------------------------------------------------------
// 1xx 信息响应
_codeToDescribe.insert(std::make_pair(100, "Continue"));
_codeToDescribe.insert(std::make_pair(101, "Switching Protocols"));
_codeToDescribe.insert(std::make_pair(102, "Processing"));
// 2xx 成功
_codeToDescribe.insert(std::make_pair(200, "OK"));
_codeToDescribe.insert(std::make_pair(201, "Created"));
_codeToDescribe.insert(std::make_pair(202, "Accepted"));
_codeToDescribe.insert(std::make_pair(204, "No Content"));
_codeToDescribe.insert(std::make_pair(206, "Partial Content"));
// 3xx 重定向
_codeToDescribe.insert(std::make_pair(300, "Multiple Choices"));
_codeToDescribe.insert(std::make_pair(301, "Moved Permanently"));
_codeToDescribe.insert(std::make_pair(302, "Found"));
_codeToDescribe.insert(std::make_pair(304, "Not Modified"));
_codeToDescribe.insert(std::make_pair(307, "Temporary Redirect"));
_codeToDescribe.insert(std::make_pair(308, "Permanent Redirect"));
// 4xx 客户端错误
_codeToDescribe.insert(std::make_pair(400, "Bad Request"));
_codeToDescribe.insert(std::make_pair(401, "Unauthorized"));
_codeToDescribe.insert(std::make_pair(403, "Forbidden"));
_codeToDescribe.insert(std::make_pair(404, "Not Found"));
_codeToDescribe.insert(std::make_pair(405, "Method Not Allowed"));
_codeToDescribe.insert(std::make_pair(408, "Request Timeout"));
_codeToDescribe.insert(std::make_pair(413, "Payload Too Large"));
_codeToDescribe.insert(std::make_pair(415, "Unsupported Media Type"));
_codeToDescribe.insert(std::make_pair(429, "Too Many Requests"));
// 5xx 服务端错误
_codeToDescribe.insert(std::make_pair(500, "Internal Server Error"));
_codeToDescribe.insert(std::make_pair(501, "Not Implemented"));
_codeToDescribe.insert(std::make_pair(502, "Bad Gateway"));
_codeToDescribe.insert(std::make_pair(503, "Service Unavailable"));
_codeToDescribe.insert(std::make_pair(504, "Gateway Timeout"));
_codeToDescribe.insert(std::make_pair(505, "HTTP Version Not Supported"));
}
private:
//获取文件资源
std::string GetResource(const std::string& path)
{
std::ifstream file(path, std::ios::binary);//二进制方式打开path路径下的文件
if(!file.is_open())//文件打开失败
{
return std::string();
}
file.seekg(0,file.end);//移动输入流指针到文件末尾
int fileSize=file.tellg();//获取当前输入流指针位置,即获取文件大小
file.seekg(0,file.beg);//回复输入流指针到文件开头
std::string resource;
resource.resize(fileSize);
file.read((char *)resource.c_str(),fileSize);//读取文件资源内容存放到resource中
file.close();//关闭文件
return resource;
}
public:
//处理服务端接收到的Http请求,最后返回Http应答序列化后的字符串
std::string Handle(std::string reqstr)
{
//参数说明:reqstr是客户端序列化过的Http请求
HttpRequest req;
req.Deserialize(reqstr);//反序列化
//处理Http请求,转为Http应答
HttpResponse res;
if(req.GetPath()=="wwwroot/redir")//处理重定向,重定向到qq官网
{
std::string redirPath="https://www.qq.com";
res.AddStatusCodeAndDescribe(301,_codeToDescribe[301]);
res.AddHeader("Location",redirPath);
}
else if(!req.GetReqContent().empty())//处理参数,如果请求正文不为空,说明传递了参数,请求的不是资源,而是某个服务,要进行服务处理
{
if(_serviceLists.find(req.GetPath())!=_serviceLists.end())
{
res=_serviceLists[req.GetPath()](req);//该服务是一个处理函数,参数是req,返回值是res
}
}
else//既没有重定向,也没有请求服务,那么直接进行序列化并返回请求的资源
{
std::string resource=GetResource(req.GetPath());//获取请求资源的内容
if(resource.empty())//说明没有该资源,返回404.html资源
{
resource=GetResource("wwwroot/404.html");
res.AddStatusCodeAndDescribe(404,_codeToDescribe[404]);
res.AddHeader("Content-Length",std::to_string(resource.size()));
res.AddHeader("Content-Type",_resourceType[".html"]);
res.AddResContent(resource);
}
else
{
res.AddStatusCodeAndDescribe(200,_codeToDescribe[200]);
res.AddHeader("Content-Length", std::to_string(resource.size()));
res.AddHeader("Content-Type", _resourceType[req.GetSuffix()]);
//res.AddHeader("Set-Cookie", "username=zhangsan"); //Cookie
res.AddResContent(resource);
}
}
return res.Serialize();//返回Http应答序列化后的字符串
}
//添加处理服务(当Http请求中携带参数时,说明其要请求的是某个服务,而不是资源)
void InsertService(const std::string& serviceName, std::function<HttpResponse(HttpRequest)> service)
{
_serviceLists.insert(std::make_pair(serviceName+webRootDir,service));
}
};