目录
协议就是一种约定,http也并不例外,使用http也无非就是,定义一个http请求的结构体,将结构体序列化为字符串,发送给服务器,服务器接收字符串,将字符串反序列化为结构化的数据,处理这些数据,将结果返回给客户端。
这就是一个完整的http请求,这请求是由浏览器发送。
两张图对照着看
请求方法:GET
URI:/ 是请求服务器的根目录(请求服务器的哪一个目录或者服务)。
HTTP版本:HTTP/1.1
Host,Connettion,User_Agent等等都是浏览器添加的请求报头。
因为有一个空行并且请求正文是空的,所以报头后面有两个空行。
因为请求是浏览器发送的,所以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.读到空行之后说明,从此刻开始,剩下的只有正文部分了,直接保存起来即可。
HTTP状态码:对请求的应答结果,可以去查一下http状态码表。
响应正文:例:可以返回一个网页,或图片,浏览器就会对你返回的内容做解析和渲染。
响应报头:这也是有一张表的,可以去查看一下,如果响应正文不是空的,响应报头就必须添加Content-Lenth,Content-Type,这样浏览器才知道你返回的内容到底是什么。
判断http请求是资源还是服务
1.如果http请求的是一个服务器的资源,要以二进制的方式将服务器的资源保存到字符串中,因为资源可能是图片,可能是视频。
判断读到的内容是否为空,如果为空说明,http请求了一个不存在的资源,这时候需要将状态码设置为404,构建应答,返回404页面。
不为空就添加对应的状态码,
添加响应头"Content-Type"和Content-Length,
添加响应正文
将应答返回
2.如果http请求是一个服务,需要将这个服务回调出去,将服务于http协议解耦。
- #pragma once
- #include
- #include "log.hpp"
- #include
- #include
- #include
- #include
- #include
- 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&)>; -
- using fun_t = std::function
shared_ptr(std::shared_ptr)>; - 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
_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
_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
_kv; // 响应报头的kv模型 -
- // 构建响应的必要行
- std::string _statusLine; // 状态行
- std::string _respondHeader; // 请求头
- std::string _blankLine; // 空行
- std::string _text; // 正文
- };
-
- class Factor
- {
- public:
- static std::shared_ptr
BuildHttprequest() //使用智能指针构建应答 - {
- return std::make_shared
(); - }
-
- static std::shared_ptr
BuildHttprepond() - {
- return std::make_shared
(); - }
- };
-
- 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::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
_mime; // 文件后缀,对应的content type类型 - std::unordered_map<int, std::string> _Statuscode_Descripetion; // 状态码 对应的描述
- std::unordered_map
fun_t> _funcs; // 将请求回调出去 - };