目录
协议就是一种约定,http也并不例外,使用http也无非就是,定义一个http请求的结构体,将结构体序列化为字符串,发送给服务器,服务器接收字符串,将字符串反序列化为结构化的数据,处理这些数据,将结果返回给客户端。
1.认识http请求的字段
这就是一个完整的http请求,这请求是由浏览器发送。
两张图对照着看
请求方法:GET
URI:/ 是请求服务器的根目录(请求服务器的哪一个目录或者服务)。
HTTP版本:HTTP/1.1
Host,Connettion,User_Agent等等都是浏览器添加的请求报头。
因为有一个空行并且请求正文是空的,所以报头后面有两个空行。
2.HTTP请求类
因为请求是浏览器发送的,所以http请求类是不需要反序列化的,将http发送给我的请求序列化即可。
http请求发过来就是一个长字符串,我们要做的就是将字符串一个一个的解析出来。
1.第一步以"\r\n"为分隔符,将每一行拆分出来。
2.读到的第一行一定是状态行,将状态行的每个字段都拆出来。
如果请求方法是GET还有判断URI中是否有参数,因为GET方法提交表单的时候,参数是在URI中的,参数通常以?分割。
就像这样49.233.244.186:8888/login?user=41234&password=1234123
49.233.244.186:8888/login?user=41234&password=1234123将参数分离之后还需要判断URI请求的服务或者目录是否存在,如果URI访问的目录或者服务不存在返回404页面。
3.读到空行之前,读到的一定是请求报头,以": "为分隔符,将报头的key和value分离出来,并存储到unordered_map中。
4.读到空行之后说明,从此刻开始,剩下的只有正文部分了,直接保存起来即可。
3.认识HTTP应答字段
HTTP状态码:对请求的应答结果,可以去查一下http状态码表。
响应正文:例:可以返回一个网页,或图片,浏览器就会对你返回的内容做解析和渲染。
响应报头:这也是有一张表的,可以去查看一下,如果响应正文不是空的,响应报头就必须添加Content-Lenth,Content-Type,这样浏览器才知道你返回的内容到底是什么。
4.HTTP应答类
判断http请求是资源还是服务
1.如果http请求的是一个服务器的资源,要以二进制的方式将服务器的资源保存到字符串中,因为资源可能是图片,可能是视频。
判断读到的内容是否为空,如果为空说明,http请求了一个不存在的资源,这时候需要将状态码设置为404,构建应答,返回404页面。
不为空就添加对应的状态码,
添加响应头"Content-Type"和Content-Length,
添加响应正文
将应答返回
2.如果http请求是一个服务,需要将这个服务回调出去,将服务于http协议解耦。
5.源代码
cpp
#pragma once
#include <iostream>
#include "log.hpp"
#include <vector>
#include <string>
#include <unordered_map>
#include <fstream>
#include <sstream>
static const std::string sep = "\r\n";// http分隔符
static const std::string blank = " "; //空格
static const std::string headerSep = ": ";//请求头响应头分隔符
static const std::string webroot = "./webroot";//服务器的根目录
static const std::string httpVersion = "http/1.0";//http版本
static const std::string suffixSep = ".";//文件类型分隔符
static const std::string defaultpage = "index.html";//主页
static const std::string argsep = "?";//get提交表单分隔符
class httpReq;
class httpRep;
//using fun_t = std::function<std::shared_ptr<httpRep> (std::shared_ptr<httpReq>&)>;
using fun_t = std::function<std::shared_ptr<httpRep>(std::shared_ptr<httpReq>)>;
class httpReq
{
private:
std::string getOneLine(std::string &reqstr) // 获取请求的一行
{
if (reqstr.empty()) // 判断请求是否为空
{
Log(Error, "reqstr empty");
return std::string();
}
auto pos = reqstr.find(sep);
if (pos == std::string::npos) // 判断是否找到
{
Log(Error, "reqstr not found sep");
return std::string();
}
std::string line = reqstr.substr(0, pos);
reqstr.erase(0, pos + sep.size());
return line.empty() ? sep : line; // 如line为空说明读到空行
}
public:
httpReq(const std::string root = webroot, const std::string blanksep = sep)
: _root(root), _blankLine(blanksep)
{
}
void serialize()
{
}
bool parseLine() // 解析请求行
{
if (_requestLine.empty())
{
return false;
}
// 解析请求行
std::stringstream ss(_requestLine); // 1以空格为分隔符自动解析字符串
ss >> _reqMethod >> _url >> _httpVersion;
_path += webroot;
// 解析参数
if (strcasecmp(_reqMethod.c_str(), "get") == 0)
{
//Log(Debug, "before url %s", _url.c_str());
auto pos = _url.find(argsep);
if (pos != std::string::npos) // 有参数
{
_args = _url.substr(pos + argsep.size());
//Log(Info, "args: %s", _args);
_url.resize(pos);
}
//Log(Debug, "after url %s", _url.c_str());
}
_path += _url;
if (_path[_path.size() - 1] == '/')//如果请求的是根目录,跳转到主页
{
_path += defaultpage;
}
for (auto s : _requestHeader)
{
auto pos = s.find(headerSep);
std::string k = s.substr(0, pos);
std::string v = s.substr(pos + headerSep.size());
_kv.insert(std::make_pair(k, v));
}
return true;
}
bool Deserialize(std::string &reqstr) // 反序列化
{
_requestLine = getOneLine(reqstr);
while (true)
{
std::string line = getOneLine(reqstr);
if (line.empty()) // getOneline 失败
{
break;
}
else if (line == sep) // 读到空行
{
_text = reqstr;
break;
}
else // 读到请求头
{
_requestHeader.push_back(line);
}
}
return parseLine();
}
bool isServiceReq() //是否为路径服务请求
{
return !_text.empty() || !_args.empty();
}
const std::string &path()
{
return _path;
}
const std::string &method()
{
return _reqMethod;
}
const std::string &text()
{
return _text;
}
const std::string &args()
{
return _args;
}
void pprint()
{
std::cout << "###" << _reqMethod << std::endl;
std::cout << "###" << _url << std::endl;
std::cout << "###" << _path << std::endl;
std::cout << "###" << _httpVersion << std::endl;
for (auto it : _kv)
{
std::cout << "@@@" << it.first << ": " << it.second << endl;
}
std::cout << "$$$" << _text << std::endl;
}
std::string getSuffix()//获取返回资源的类型
{
if (_path.empty())
{
return std::string();
}
auto pos = _path.rfind(suffixSep);
if (pos == std::string::npos)
{
return ".html";
}
else
{
return _path.substr(pos);
}
}
private:
std::string _requestLine; // http请求第一行
std::vector<std::string> _requestHeader; // http请求头
std::string _blankLine; // 空行
std::string _text; // http正文
// 解析出每行具体内容
std::string _root; // web的根目录
std::string _reqMethod; // 请求方法
std::string _url; // url
std::string _httpVersion; // http版本
std::string _path; // 访问资源的路径
std::string _args; // 请求的参数
std::unordered_map<std::string, std::string> _kv; // 请求头的kv模型
};
class httpRep
{
public:
httpRep(const std::string version = httpVersion, const std::string b = sep)
: _httpVersion(version), _blankLine(b)
{
}
void addStatusLine(const int code, const std::string &descrip)//添加状态行
{
_statuCode = code;
_codeDescripetion = descrip;
}
void addHander(const std::string k, const std::string v)//添加响应头
{
_kv.insert(std::make_pair(k, v));
}
void addText(std::string &text)//添加响应文
{
_text = text;
}
std::string Serialize()
{
//序列化状态行
_statusLine = _httpVersion + blank + std::to_string(_statuCode) + blank + _codeDescripetion + sep;
//序列化响应头
for (auto it : _kv)
{
_respondHeader += it.first;
_respondHeader += headerSep;
_respondHeader += it.second;
_respondHeader += sep;
}
//构建应答
std::string respond;
respond += _statusLine;
respond += _respondHeader;
respond += sep;
respond += _text;
// std::cout << respond << std::endl;
// Log(Info, "%s", _statusLine.c_str());
// Log(Info, "%s", _respondHeader.c_str());
return respond;
}
void pprint()
{
std::cout << "###" << _httpVersion << std::endl;
std::cout << "###" << _statuCode << std::endl;
std::cout << "###" << _codeDescripetion << std::endl;
std::cout << "###" << _text << std::endl;
}
private:
// 构建响应必要的字段
std::string _httpVersion; // http版本
int _statuCode; // 状态码
std::string _codeDescripetion; // 状态码描述
std::unordered_map<std::string, std::string> _kv; // 响应报头的kv模型
// 构建响应的必要行
std::string _statusLine; // 状态行
std::string _respondHeader; // 请求头
std::string _blankLine; // 空行
std::string _text; // 正文
};
class Factor
{
public:
static std::shared_ptr<httpReq> BuildHttprequest() //使用智能指针构建应答
{
return std::make_shared<httpReq>();
}
static std::shared_ptr<httpRep> BuildHttprepond()
{
return std::make_shared<httpRep>();
}
};
class httpserver
{
public:
httpserver()
{
_mime.insert(std::make_pair(".html", "text/html"));
_mime.insert(std::make_pair(".jpg", "image/jpeg"));
_mime.insert(std::make_pair(".png", "application/x-plt"));
_Statuscode_Descripetion.insert(std::make_pair(100, "continue"));
_Statuscode_Descripetion.insert(std::make_pair(200, "ok"));
_Statuscode_Descripetion.insert(std::make_pair(301, "Moved Permanently")); // 永久重定向
_Statuscode_Descripetion.insert(std::make_pair(302, "Found")); // 临时重定向
_Statuscode_Descripetion.insert(std::make_pair(400, "Bad Request"));
_Statuscode_Descripetion.insert(std::make_pair(404, "Not Found"));
_Statuscode_Descripetion.insert(std::make_pair(404, "Not Found"));
}
std::string readFileContent(const std::string &path, int &filesize)
{
// std::cout<< path <<std::endl;
std::ifstream in(path, std::ios::binary);
if (!in.is_open())
{
return std::string();
}
in.seekg(0, in.end);
filesize = in.tellg();
in.seekg(0, in.beg);
// std::cout << filesize << std::endl;
if (filesize < 0)
{
filesize = 0;
}
std::string content;
content.resize(filesize);
in.read((char *)content.c_str(), filesize);
in.close();
return content;
}
void addhander(std::string path, fun_t handler)
{
std::string tmp = webroot + path;
_funcs.insert(std::make_pair(tmp, handler));
}
#define VERSION_1
std::string httpHandler(std::string req)
{
#ifdef VERSION_1
std::cout << req << std::endl;
auto request = Factor::BuildHttprequest();
request->Deserialize(req);
//Log(Info, "method %s", request->method().c_str());
// Log(Info,"s","redir ----------------------");
// if(request->path() == "./webroot/redir")//进行重定向
// {
// code = 302;
// respond->addHander("Location", "https://www.csdn.net/?spm=1011.2266.3001.4476");
// respond->addStatusLine(code, _Statuscode_Descripetion[code]);
// }
if (request->isServiceReq())//当前请求的是一个服务
{
Log(Info, "method %s", request->method().c_str());
Log(Info, "method %s", request->path().c_str());
auto response = _funcs[request->path()](request);
return response->Serialize();
}
else // 当前请求的是一个服务器资源
{
int code = 200;
int filesize = 0;
auto respond = Factor::BuildHttprepond();
std::string content = readFileContent(request->path(), filesize);
if (content.empty())//没有读到任何内容,说明请求不存在
{
code = 404;
respond->addStatusLine(code, _Statuscode_Descripetion[code]);
respond->addHander("Content-Type", ".html");
std::string content404 = readFileContent("./webroot/404.html", filesize);
respond->addText(content404);
}
else//请求存在
{
respond->addStatusLine(code, _Statuscode_Descripetion[code]);/
std::string suffix = request->getSuffix();//获取资源的后坠
respond->addHander("Content-Type", _mime[suffix]);
respond->addHander("Content-Length", std::to_string(filesize));
respond->addText(content);
}
return respond->Serialize();
}
#else
std::cout << "version control" << endl;
#endif
}
private:
std::unordered_map<std::string, std::string> _mime; // 文件后缀,对应的content type类型
std::unordered_map<int, std::string> _Statuscode_Descripetion; // 状态码 对应的描述
std::unordered_map<std::string, fun_t> _funcs; // 将请求回调出去
};