目录
一、HTTP协议概述
二、HTTP应用层服务器实现
Util.hpp
Protocal.hpp
Http_Server.hpp
http_server.cc
indext.html
请求和响应怎么保证应用层完整读取完毕了?
HTTP的请求方法
方法 |
说明 |
支持的HTTP协议版本 |
GET |
获取资源 |
1.0/1.1 |
POST |
传输实体主题 |
1.0/1.1 |
PUT |
传输文件 |
1.0/1.1 |
HEAD |
获得报文首部 |
1.0/1.1 |
DELETE |
删除文件 |
1.0/1.1 |
OPTIONS |
询问支持的方法 |
1.1 |
TRACE |
追踪路径 |
1.1 |
CONNECT |
要求用隧道协议连接代理 |
1.1 |
LINK |
建立和资源之间的联系 |
1.0 |
UNLINE |
断开连接关系 |
1.0 |
最常用的是GET和POST方法:
HTTP的状态码
类型 |
原因短语 |
|
1XX |
Informational(信息性状态码) |
接收的请求正在处理 |
2XX |
Success(成功状态码) |
请求正常处理完毕 |
3XX |
Redirection(重定向状态码) |
需要进行附加操作以完成请求 |
4XX |
Client Error(客户端错误状态码) |
处理器无法处理请求 |
5XX |
Server Error(服务端错误状态码) |
服务器处理请求出错 |
互联网公司并不会严格遵守HTTP协议的状态码规定,即使是服务器出错,绝大多数情况也不会返回5XX状态码,一是返回5XX的错误码会“掉价”,二是避免入侵。
HTTP的请求头Header
服务器获取网页连接请求,返回响应体给客户端(html、图片等资源),展现网页信息
服务器获取网页的请求体之后,分析请求体的字符串数据,生成请求体结构体,根据请求体结构体生成响应体结构体
生成请求体req:①从inbuffer缓冲区读取第一行,里面包含method、url、httpversion信息;②根据url设置资源路径path;③根据path获取资源后缀;④获取资源的大小st
生成响应体resp:①根据req.path获取资源位置;②根据资源后缀返回对应的资源类型;③添加响应体的报头和空行信息;④将资源的二进制信息通过响应正文传输
编写对应的html文件,展示网页,html的根目录设置为wwwroot,index.html是默认主页,404.html是访问错误的默认页面,其他的一些网页资源也可以放在wwwroot目录下,一些二级网页可以放入wwwroot目录下的子目录中
#pragma once
#include
#include
#include
#include
using namespace std;
class Util
{
public:
static string Get_Line(string& buffer, const string& sep)
{
auto pos = buffer.find(sep);
if (pos == string::npos)
return "";
string sub = buffer.substr(0, pos);
buffer.erase(0, sub.size() + sep.size());
return sub;
}
static bool Read_File(const string resource, char* buffer, int size)
{
ifstream in(resource, ios_base::binary);
if (!in.is_open())
return false;
in.read(buffer, size);
in.close();
return true;
}
};
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include "Util.hpp"
using namespace std;
static const string g_sep = "\r\n";
static const string g_default_root = "./wwwroot";
static const string g_homepage = "index.html";
static const string g_html404 = "./wwwroot/404.html";
class HttpRequest
{
public:
HttpRequest()
{}
void Parse()
{
// 1. 从inbuffer里拿到第一行,分隔符 "\r\n"
string line = Util::Get_Line(inbuffer, g_sep);
if (line.empty())
return;
// 2. 提取三个字段
cout << line << endl;
stringstream ss(line);
ss >> method >> url >> httpversion;
// 3. 添加web默认路径
path = g_default_root;
path += url;
if (path[path.size() - 1] == '/')
path += g_homepage;
// 4. 获取path对应的资源后缀
auto pos = path.rfind(".");
if (pos == string::npos)
suffix = ".html";
else
suffix = path.substr(pos);
// 5. 获取资源的大小
struct stat st;
int n = stat(path.c_str(), &st);
if (n == 0)
size = st.st_size;
else
size = -1;
}
public:
string inbuffer;
// std::string reqline;
// std::vector reqheader;
// std::string body;
string method;
string url;
string httpversion;
string path;
string suffix;
int size;
};
class HttpResponse
{
public:
string outbuffer;
};
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// #include
#include "Protocal.hpp"
static const uint16_t g_port = 8080;
static const int g_backlog = 5;
using namespace std;
using func_t = function;
class HttpServer
{
public:
HttpServer(func_t func, const uint16_t& port = g_port)
: _listenfd(-1), _func(func), _port(port)
{}
void Init()
{
_listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (_listenfd < 0)
{
std::cerr << "socket create error" << std::endl;
exit(1);
}
struct sockaddr_in local;
memset(&local, 0, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(_port);
local.sin_addr.s_addr = INADDR_ANY;
if (bind(_listenfd, (struct sockaddr*)&local, sizeof(local)) < 0)
{
cerr << "bind error" << endl;
exit(1);
}
if (listen(_listenfd, g_backlog) < 0)
{
cerr << "listen error" << endl;
exit(1);
}
}
void Start()
{
while (true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sock = accept(_listenfd, (struct sockaddr*)&peer, &len);
if (sock < 0)
continue;
pid_t id = fork();
if (id == 0)
{
close(_listenfd);
if (fork() > 0)
exit(0);
Handler_Http(sock);
close(sock);
exit(0);
}
waitpid(id, nullptr, 0);
}
}
void Handler_Http(int sock)
{
// 1. 读取完整http请求
// 2. 反序列化请求
// 3. 回调
// 4. 序列化响应
// 5. send
char buffer[1024];
HttpRequest req;
HttpResponse resp;
ssize_t n = recv(sock, buffer, sizeof(buffer) - 1, 0);
if (n > 0)
{
buffer[n] = 0;
req.inbuffer = buffer;
req.Parse();
_func(req, resp);
// funcs[req.path](req, resp);
send(sock, resp.outbuffer.c_str(), resp.outbuffer.size(), 0);
}
}
// void register_cb(std::string serverceName, func_t cb)
// {
// funcs.insert(std::make_pair(serverceName, cb));
// }
private:
int _listenfd;
uint16_t _port;
func_t _func;
// std::unordered_map funcs;
};
#include "Http_Server.hpp"
#include
using namespace std;
string Suffix_To_Desc(const string suffix)
{
string ct = "Content-Type: ";
if (suffix == ".html")
ct += "text/html";
else if (suffix == ".jpg")
ct += "application/x-jpg";
ct += "\r\n";
return ct;
}
// 1. 服务器和网页分离
// 2. 根据url获取资源位置
// 3. 根据访问资源的后缀返回对应的资源类型
bool Get_Resp(const HttpRequest& req, HttpResponse& resp)
{
// if (req.path == "./seach")
// {
// // 根据path提供特定的服务
// // 建立进程间通信,进程替换
// // 父进程将 req.parm 通过管道
// }
cout << "------------ http start ----------------" << endl;
cout << req.inbuffer << endl;
cout << "mothod: " << req.method << endl;
cout << "url: " << req.url << endl;
cout << "httpversion: " << req.httpversion << endl;
cout << "path: " << req.path << endl;
cout << "suffix: " << req.suffix << endl;
cout << "size: " << req.size << " 字节" << endl;
cout << "------------- http end ----------------" << endl << endl;
string respline = "HTTP/1.1 200 OK\r\n";
string respheader = Suffix_To_Desc(req.suffix);
if (req.size > 0)
{
respheader += "Content-Length: ";
respheader += to_string(req.size);
respheader += "\r\n";
}
respheader += "Set-Cookie: name=12345678qwer; Max-Age=120\r\n"; // Max-Age是到期时间,单位为秒
// 往后每次http请求,都会自动携带cookie,帮浏览器进行鉴权功能
string respblank = "\r\n";
string body;
body.resize(req.size + 1);
if (!Util::Read_File(req.path, (char*)body.c_str(), req.size))
{
Util::Read_File(g_html404, (char*)body.c_str(), req.size);
}
resp.outbuffer += respline;
resp.outbuffer += respheader;
resp.outbuffer += respblank;
cout << "------------ resp start ----------------" << endl;
cout<< resp.outbuffer << endl;
cout << "------------ resp end ----------------" << endl;
resp.outbuffer += body;
return true;
}
// ./http_server port
int main(int argc, char* argv[])
{
if (argc != 2)
{
cout << "usage error" << endl;
exit(1);
}
uint16_t port = atoi(argv[1]);
unique_ptr httpsvr(new HttpServer(Get_Resp, port));
// httpsvr->register_cb("/", get_resp);
// httpsvr->register_cb("/a/b/test.py", py_mothod);
httpsvr->Init();
httpsvr->Start();
return 0;
}
我的首页