基于http协议的服务器代码编写(可以访问指定路径的资源版+添加跳转网页功能
目次可以访问指定路径资源的服务端
编写
引入
介绍
代码
Serialization.hpp
404_err.html
示例
增长跳转网页功能
href
介绍
代码
root.html
page1.html
page2.html
示例
添加重定向功能
引入
介绍
代码
示例
添加显示图片功能
引入
介绍
代码
http_server.hpp
Serialization.hpp
root_page.html
示例
修改为多线程版(短毗连)
介绍
代码
可以访问指定路径资源的服务端
编写
引入
如果我们要访问客户端指定路径的资源,就得把请求拆分出来
[*]url里记录了要访问资源的路径
[*]拆分的过程着实就是反序列化
介绍
那么题目就酿成了 -- 怎样序列化/反序列化
[*]因为我们只用编写服务端(因为客户端有浏览器主动帮我们进行处理惩罚),所以只涉及请求的反序列化+响应的序列化
[*]着实响应的序列化我们早就在上一个版本里做过了,我们把它再做个处理惩罚并且拆分出来
请求怎样序列化(格式化数据 -> 字符串)呢?
[*]把响应报头放在vector<string>里
[*]响应正文单独存放在string里
[*]在拼接的时候,利用某个分隔符分开他们(比如http协议里规定的\r\n,固然这个分隔符只适用于报头部分)
响应怎样反序列化(字符串-> 格式化数据)呢?
[*]就是把上述过程反过来
[*]找到分隔符,将得到的数据赋值给结构体对应的字段
[*]详细来说就是:
[*]以\r\n为分隔符,找出报头的每一行,将每一行push到容器里,同时删除掉源串里的内容+"/r/n"
[*]如果读到空行(内容为空)时,就已经将报头读完了
[*] 然后探求报头里的content-length字段,找到后拿到正文长度,就可以继承把正文也拆出来了
怎样得到我们的目标url呢?
[*]也就是要继承细化
[*]url在请求行里,也就是报头的第一行=vector里的第一个元素
[*]把它以空格为分隔符,拆出三大部分,即可得到纯净的url
[*]我们可以手动分割
[*]也可以利用ss流,它默认以空格为分隔符主动分割,只必要用变量给他接着就行(就像水倒出来,用杯子接着它哈哈哈)
得到url后,我们必要将它和我们自定义的web根目次的位置拼接起来
[*]也就是在上一个版本那边介绍的那样,拼接起来得到[要访问的资源在linux中的现实路径]:
[*]https://img-blog.csdnimg.cn/direct/1295b749424540e5983d060ceaf5f370.png
但是这里我们要增长一个特殊处理惩罚:
[*]当访问根目次时,必要手动让它访问我们设定的首页文件
[*]并且当访问不存在的页面时,必要手动让它访问我们设定好的错误页面
代码
#pragma once
#include <signal.h>
#include <unistd.h>
#include <cstring>
#include <functional>
#include "socket.hpp"
#include "Serialization.hpp"
static MY_SOCKET my_socket;
#define buff_size 1024 * 30
#define root_path "./root_page"
#define def_file "root.html"
class http_server
{
public:
http_server(const uint16_t port, const std::string &ip = "0.0.0.0")
: port_(port), ip_(ip) {}
~http_server() {}
void run()
{
init();
while (true)
{
uint16_t client_port;
std::string client_ip;
lg(DEBUG, "accepting ...");
int sockfd = my_socket.Accept(client_ip, client_port);
if (sockfd == -1)
{
continue;
}
lg(INFO, "get a new link..., sockfd: %d, client ip: %s, client port: %d", sockfd, client_ip.c_str(), client_port);
int ret = fork();
if (ret == 0)
{
my_socket.Close();
char buffer;
std::string in_buffer;
while (true)
{
memset(buffer, 0, sizeof(buffer));
int n = read(sockfd, buffer, sizeof(buffer)); //"size"\n"a op b"\n
if (n > 0)
{
buffer = 0;
lg(INFO, "get request");
in_buffer += buffer; // 连续读取
lg(INFO, "%s", in_buffer.c_str());
request req;
req.deserialize(in_buffer);
// 构建访问资源的路径
std::string def_page = root_path;
if (req.url_ == "/")
{
def_page += "/";
def_page += def_file;
}
else
{
def_page += req.url_;
}
// 构建响应
response res;
res.version_ = "HTTP/1.1";
std::string text = get_page(def_page);
if (text.empty())
{
res.code_ = 404;
res.desc_ = "Not Found";
def_page = root_path;
def_page += "/";
def_page += "404_err.html";
res.text_ = get_page(def_page);
}
else
{
res.code_ = 200;
res.desc_ = "OK";
res.text_ = text;
}
std::string cl = "Content-Length: ";
cl += std::to_string((res.text_).size());
cl += protocol_sep;
(res.title_).push_back(cl);
std::string content;
res.serialize(content);
write(sockfd, content.c_str(), content.size());
}
else if (n == 0)
{
lg(INFO, "%s quit", client_ip.c_str());
break;
}
else // 读出错误
{
break;
}
}
// lg(INFO, "fork quit");
exit(0);
close(sockfd);
}
}
}
private:
void init()
{
signal(SIGPIPE, SIG_IGN);
signal(SIGCHLD, SIG_IGN);
my_socket.Socket();
my_socket.Bind(port_);
my_socket.Listen();
lg(INFO, "server init done");
}
std::string get_page(std::string path)
{
std::ifstream in(path.c_str());
if (!in.is_open())
{
return "";
}
std::string content, tmp;
while (std::getline(in, tmp))
{
content += tmp;
}
return content;
}
private:
uint16_t port_;
std::string ip_;
};
Serialization.hpp
#pragma once
#include <string>
#include <vector>
#include <sstream>
#include <fstream>
#include <iostream>
#define protocol_sep "\r\n"
#define blank_sep ' '
class request
{
public:
request()
: type_(""), url_(""), version_(""), text_("") {}
bool deserialize(std::string &content)
{
std::string tmp;
size_t left = 0;
while (true)
{
size_t pos = content.find(protocol_sep, left);
if (pos == std::string::npos)
{
return false;
}
tmp = content.substr(left, pos - left); // 左闭右开
left = pos + 2;
if (tmp.empty()) // 读到空行
{
break;
}
else
{
title_.push_back(tmp);
}
}
// 细分请求行
std::string request_line = title_;
std::stringstream ss(request_line);
ss >> type_ >> url_ >> version_;
// 如果有正文的话
std::string comp = "Content-Length: ";
bool is_find = false;
int size = 0;
for (auto &it : title_)
{
size_t pos = it.find(comp);
if (pos != std::string::npos)
{
ssize_t right = it.find(protocol_sep);
std::string s_size = it.substr(pos + comp.size(), right - pos - comp.size());
size = stoi(s_size);
is_find = true;
break;
}
}
if (!is_find)
{ // 没有Content-Length字段
content.erase(0, left + 2);
}
else
{
text_ = content.substr(left + 2, size);
content.erase(0, left + 2 + size);
}
return true;
}
public:
std::string type_;
std::string url_;
std::string version_;
std::vector<std::string> title_;
std::string text_;
};
class response
{
public:
response()
: version_(""), code_(0), desc_(""), text_("") {}
void serialize(std::string &content)
{
// 响应行
std::string header = version_;
header += blank_sep;
header += std::to_string(code_);
header += blank_sep;
header += desc_;
header += protocol_sep;
// 响应报头
std::string attribute;
for (auto &it : title_)
{
attribute += it;
attribute += protocol_sep;
}
content = header + attribute + protocol_sep + text_;
}
public:
std::string version_;
int code_;
std::string desc_;
std::vector<std::string> title_;
std::string text_;
};404_err.html
这是我上网任意搜的一个(毕竟咱也不是做前端的,没必要本身写)
<!DOCTYPE html>
<html>
<head>
<title>404-对不起!您访问的页面不存在</title>
<meta charset="UTF-8" http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style>
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
color: #0e356c;
display: table;
font-weight: 100;
font-family: 'Lato';
}
.container {
text-align: center;
display: table-cell;
vertical-align: middle
}
.content {
text-align: center;
display: inline-block;
}
.title {
font-size: 42px;
margin-bottom: 40px;
}
</style>
</head>
<body>
<div class="container">
<div class="content">
<div class="title">404-对不起!您访问的页面不存在</div>
</div>
</div>
</body>
</html>
示例
这是我们创建的多个html文件结构:
[*]https://img-blog.csdnimg.cn/direct/83d44fa96849458598e26662ca7ae71a.png
访问指定资源:
[*]https://img-blog.csdnimg.cn/direct/47ff1529b40b436eb0ea333e8dd52fcc.png
[*]https://img-blog.csdnimg.cn/direct/6c2c676cc20a42a99d9b759514736e0d.png
如果访问的资源不存在:
[*]https://img-blog.csdnimg.cn/direct/4326158a1af547409d7c3e23a8808460.png
增长跳转网页功能
href
是 HTML 元素中常用的属性,用于指定链接的目标地址
[*]https://img-blog.csdnimg.cn/direct/d7ab9dade42d4e41a2a8fd531e42d28c.png
跳转网页时不一定都发送了请求,因为浏览器会缓存一些网页
介绍
路径可以是绝对路径,也可以是相对路径
[*]所以可以实现跳转外部/内部网页
代码
root.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>my title</title>
</head>
<body>
<h1>hello.</h1>
<h2>hello..</h2>
<h3>hello...</h3>
<p>hello world!</p>
<h2>跳转至第一个页面</h2>
<p><a href="a/page1.html">page1</a></p>
<h2>跳转至第二个页面</h2>
<p><a href="a/b/page2.html">page2</a></p>
</body>
</html>page1.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>my title</title>
</head>
<body>
<h1>第一个页面</h1>
<p>hello first</p>
<h2>跳转至第二个页面</h2>
<p><a href="b/page2.html">page2</a></p>
<h2>跳转至首页</h2>
<p><a href="../root.html">root</a></p>
</body>
</html>page2.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>my title</title>
</head>
<body>
<h1>第二个页面</h1>
<p>hello second</p>
<h2>跳转至第一个页面</h2>
<p><a href="../page1.html">page1</a></p>
<h2>跳转至首页</h2>
<p><a href="../../root.html">root</a></p>
</body>
</html> 示例
当我们起首访问page2:
[*]https://img-blog.csdnimg.cn/direct/8c96d74ebb1e46ef9666ebfe2e882c4a.png
点击跳转首页后:
[*]https://img-blog.csdnimg.cn/direct/b7c57e457d5144c1ba007e8971830a74.png
点击跳转page1后:
[*]https://img-blog.csdnimg.cn/direct/d9e0e73e7c1a46c480ed65626f1d402d.png
添加重定向功能
引入
我们在上一篇博客中介绍过重定向的概念 -- 域名介绍,url的介绍+原理+特殊字符的处理惩罚,网络行为,http协议请求/响应的格式+结构,状态码介绍,临时/永世重定向,http报头常见字段,fiddler-CSDN博客
介绍
我们这里假定当客户端访问/302时,就触发重定向功能
[*]也就是让响应的状态码为302
[*]浏览器会帮我们根据http协议 -- 当状态码为302时,主动跳转到location字段定义的资源(这里我们设置为百度主页,都行的啦)
[*]因为只是一个测试嘛,起到一个看看重定向结果的作用
代码
只必要修改动态构建响应部分即可:
void run()
{
init();
while (true)
{
uint16_t client_port;
std::string client_ip;
lg(DEBUG, "accepting ...");
int sockfd = my_socket.Accept(client_ip, client_port);
if (sockfd == -1)
{
continue;
}
lg(INFO, "get a new link..., sockfd: %d, client ip: %s, client port: %d", sockfd, client_ip.c_str(), client_port);
int ret = fork();
if (ret == 0)
{
my_socket.Close();
char buffer;
std::string in_buffer;
while (true)
{
memset(buffer, 0, sizeof(buffer));
int n = read(sockfd, buffer, sizeof(buffer)); //"size"\n"a op b"\n
if (n > 0)
{
bool code_302 = false;
buffer = 0;
lg(INFO, "get request");
in_buffer += buffer; // 连续读取
lg(INFO, "%s", in_buffer.c_str());
request req;
req.deserialize(in_buffer);
// 构建访问资源的路径
std::string def_page = root_path;
if (req.url_ == "/")
{
def_page += "/";
def_page += def_file;
}
else if (req.url_ == "/302")
{
code_302 = true;
}
else
{
def_page += req.url_;
}
// 构建响应
response res;
res.version_ = "HTTP/1.1";
if (code_302)
{
res.code_ = 302;
res.desc_ = "Found";
std::string cl = "Location: ";
cl += "https://www.baidu.com";
cl += protocol_sep;
(res.title_).push_back(cl);
}
else
{
std::string text = get_page(def_page);
if (text.empty())
{
res.code_ = 404;
res.desc_ = "Not Found";
def_page = root_path;
def_page += "/";
def_page += "404_err.html";
res.text_ = get_page(def_page);
}
else
{
res.code_ = 200;
res.desc_ = "OK";
res.text_ = text;
}
std::string cl = "Content-Length: ";
cl += std::to_string((res.text_).size());
cl += protocol_sep;
(res.title_).push_back(cl);
}
std::string content;
res.serialize(content);
write(sockfd, content.c_str(), content.size());
}
else if (n == 0)
{
lg(INFO, "%s quit", client_ip.c_str());
break;
}
else // 读出错误
{
break;
}
}
// lg(INFO, "fork quit");
exit(0);
close(sockfd);
}
}
} 示例
服务端收到的请求:
[*]https://img-blog.csdnimg.cn/direct/edb649850efe40e2a106c7031b37e0f8.png
成功跳转到我们设定好的百度主页
[*]偶然候大概会一直转圈圈,多切换下网络
添加显示图片功能
引入
网页一样平常都会有图片
[*]图片也属于网络资源,也必要浏览器向服务器请求这个资源
[*]所以图片资源也会放在web根目次下
这也就意味着:
[*]当我们请求一个页面后,浏览器不仅必要请求网页,还必要根据页面标签定义的图片资源挨个请求
这也就是为什么新版本的http要支持长毗连:
[*]如果还采用短毗连,一个毗连=一个资源,那万一网页有几百张图片资源,那不得毗连死
介绍
插入图片资源的html语法: -- HTML 图像
[*]https://img-blog.csdnimg.cn/direct/3f3f895ca9fd4e2393e7abd7ca9c232d.png
图片资源着实就是二进制文件,它必要被上层以某种格式解释,才气呈现出我们想要看到的样子
[*]所以就必要添加conten-type字段,来指定该资源的类型
[*]我们之前都没有利用过这个字段,所以他默认以html格式解释
[*]如果是图片的话,就得是image/jpeg / image/png
但是,那么多资源,怎样识别哪个资源是那个类型呢?
[*]我们就必要继承细分url了
[*]url不是记录了客户端要访问的资源路径吗,就肯定会带上资源的文件名
[*]如果文件名带有.jpg / .png后缀,则为图片资源
[*]如果带有.html后缀,则是我们平常的网页资源
[*]如果是其他的,就默认以html格式解释(这里就简朴一点处理惩罚啦)
那么,有了后缀,我们还必要将后缀与对应的conten-type的添补值相关联
[*]所以我们必要定义一个键值对容器
既然图片是二进制,那如果还利用[读取文本文件的一行一行读]的方式就不可了
[*]必要增长一个读取二进制的函数,用来区分两者
代码
除了增长了读取图片资源的功能,我还把代码结构修改了很多
http_server.hpp
#pragma once
#include <signal.h>
#include <unistd.h>
#include <cstring>
#include <functional>
#include <unordered_map>
#include "socket.hpp"
#include "Serialization.hpp"
static MY_SOCKET my_socket;
#define buff_size 1024 * 30
class http_server
{
public:
http_server(const uint16_t port, const std::string &ip = "0.0.0.0")
: port_(port), ip_(ip)
{
content_type_[".html"] = "text/html";
content_type_[".png"] = "image/png";
content_type_[".jpg"] = "image/jpeg";
content_type_[".jpeg"] = "image/jpeg";
}
~http_server() {}
void run()
{
init();
while (true)
{
uint16_t client_port;
std::string client_ip;
lg(DEBUG, "accepting ...");
int sockfd = my_socket.Accept(client_ip, client_port);
if (sockfd == -1)
{
continue;
}
lg(INFO, "get a new link..., sockfd: %d, client ip: %s, client port: %d", sockfd, client_ip.c_str(), client_port);
int ret = fork();
if (ret == 0)
{
my_socket.Close();
char buffer;
std::string in_buffer;
while (true)
{
memset(buffer, 0, sizeof(buffer));
int n = read(sockfd, buffer, sizeof(buffer)); //"size"\n"a op b"\n
if (n > 0)
{
buffer = 0;
in_buffer += buffer; // 连续读取
lg(INFO, "get request: \n%s", in_buffer.c_str());
// 构建请求
request req;
req.deserialize(in_buffer);
//lg(DEBUG, "path: %s ,url: %s ", (req.path_).c_str(), (req.url_).c_str());
// 构建响应
response res;
handle_response(res, req);
// 响应序列化
std::string content;
res.serialize(content);
write(sockfd, content.c_str(), content.size());
}
else if (n == 0)
{
lg(INFO, "%s quit", client_ip.c_str());
break;
}
else // 读出错误
{
break;
}
}
exit(0);
close(sockfd);
}
}
}
private:
void init()
{
signal(SIGPIPE, SIG_IGN);
signal(SIGCHLD, SIG_IGN);
my_socket.Socket();
my_socket.Bind(port_);
my_socket.Listen();
lg(INFO, "server init done");
}
void handle_response(response &res, request &req)
{
int code = req.code_;
std::string path = req.path_;
std::string content_type_data = content_type_;
//lg(DEBUG, "content_type_data: %s", content_type_data.c_str());
res.version_ = "HTTP/1.1";
if (code == 302)
{
res.code_ = 302;
res.desc_ = "Found";
std::string cl = "Location: ";
cl += "https://www.qq.com";
(res.title_).push_back(cl);
return ;
}
if (code == 404)
{
res.code_ = 404;
res.desc_ = "Not Found";
}
else
{
res.code_ = 200;
res.desc_ = "OK";
}
// 将读取网页和图片资源的方式分开
if (req.suffix_ == ".html")
{
res.text_ = get_page(path);
//lg(DEBUG, "text: %s", (res.text_).c_str());
}
else
{
res.text_ = b_get_page(path);
}
//构建响应报头
std::string cl = "Content-Length: ";
cl += std::to_string((res.text_).size());
//lg(DEBUG, "text_size: %d", (res.text_).size());
(res.title_).push_back(cl);
cl = "Content-Type: ";
cl += content_type_data;
(res.title_).push_back(cl);
}
private:
uint16_t port_;
std::string ip_;
std::unordered_map<std::string, std::string> content_type_;
};
Serialization.hpp
#pragma once
#include <string>
#include <vector>
#include <sstream>
#include <fstream>
#include <iostream>
#define protocol_sep "\r\n"
#define blank_sep ' '
#define root_path "./root_page"
#define def_file "root.html"
std::string get_page(std::string path)
{
std::string content, tmp;
std::ifstream in(path.c_str());
if (!in.is_open())
{
return "";
}
while (std::getline(in, tmp))
{
content += tmp;
}
in.close();
// printf("content : %s", content.c_str());
return content;
}
std::string b_get_page(std::string path)
{
std::ifstream in(path.c_str(), std::ios_base::binary);
if (!in.is_open())
{
return "";
}
in.seekg(0, std::ios_base::end);
auto len = in.tellg();
in.seekg(0, std::ios_base::beg);
std::string content;
content.resize(len);
in.read((char *)content.c_str(), content.size());
in.close();
return content;
}
class request
{
public:
request()
: type_(""), url_(""), version_(""), text_(""), path_(""), code_(0), suffix_("") {}
bool deserialize(std::string &content)
{
std::string tmp;
size_t left = 0;
while (true)
{
size_t pos = content.find(protocol_sep, left);
if (pos == std::string::npos)
{
return false;
}
tmp = content.substr(left, pos - left); // 左闭右开
left = pos + 2;
if (tmp.empty()) // 读到空行
{
break;
}
else
{
title_.push_back(tmp);
}
}
// 判断是否有正文
std::string comp = "Content-Length: ";
bool is_find = false;
int size = 0;
for (auto &it : title_)
{
size_t pos = it.find(comp);
if (pos != std::string::npos)
{
ssize_t right = it.find(protocol_sep);
std::string s_size = it.substr(pos + comp.size(), right - pos - comp.size());
size = stoi(s_size);
is_find = true;
break;
}
}
if (!is_find)
{ // 没有Content-Length字段
content.erase(0, left + 2);
}
else
{
text_ = content.substr(left + 2, size);
content.erase(0, left + 2 + size);
}
handle_path();
return true;
}
private:
void handle_path() // 构建访问资源的路径
{
// 细分请求行
std::string request_line = title_;
std::stringstream ss(request_line);
ss >> type_ >> url_ >> version_;
// 构建路径
path_ = root_path;
if (url_ == "/")
{
path_ += "/";
path_ += def_file;
}
else if (url_ == "/302")
{
code_ = 302; // 这里设置为重定向到百度页面,所以不能走本地读取
return;
}
else
{
path_ += url_;
}
// 判断该资源是否存在
if (get_page(path_).empty())
{
code_ = 404; // 需要告诉响应,这里发生了404错误
path_ = root_path;
path_ += "/";
path_ += "404_err.html";
}
// 拿到资源后缀
size_t pos = path_.rfind(".");
if (pos == std::string::npos)
{
suffix_ = ".html";
}
else
{
suffix_ = path_.substr(pos);
}
}
public:
std::string type_;
std::string url_;
std::string path_;
int code_;
std::string suffix_;
std::string version_;
std::vector<std::string> title_;
std::string text_;
};
class response
{
public:
response()
: version_(""), code_(0), desc_(""), text_("") {}
void serialize(std::string &content)
{
// 响应行
std::string header = version_;
header += blank_sep;
header += std::to_string(code_);
header += blank_sep;
header += desc_;
header += protocol_sep;
// 响应报头
std::string attribute;
for (auto &it : title_)
{
attribute += it;
attribute += protocol_sep;
}
content = header + attribute + protocol_sep + text_;
}
public:
std::string version_;
int code_;
std::string desc_;
std::vector<std::string> title_;
std::string text_;
};root_page.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>my title</title>
</head>
<body>
<h1>hello.</h1>
<h2>hello..</h2>
<h3>hello...</h3>
<p>hello world!</p>
<form action="a/page1.html" method="get">
name:<input type="text" name="name"><br>
password:<input type="password" name="password"><br>
<input type="submit" name="submit">
</form>
<img src="1.jpeg" alt="猫猫" width="500" height="400">
<h2>跳转至第一个页面</h2>
<p><a href="a/page1.html">page1</a></p>
<h2>跳转至第二个页面</h2>
<p><a href="a/b/page2.html">page2</a></p>
</body>
</html> 示例
当我们在首页放上我们的图片资源时
[*]访问首页后,浏览器就会根据页面上的html标签继承申请其他资源
[*]https://img-blog.csdnimg.cn/direct/58942a01b1854a558a5e51761041ae7d.png
[*]这是网页的样式(可以看到图片成功被加载出来了):
[*]https://img-blog.csdnimg.cn/direct/dbf60c35db7c493c880574f0f382ee42.png
修改为多线程版(短毗连)
介绍
服务器收到一个毗连后,就创建一个线程去处理惩罚请求
[*]并且添加了重连功能
留意:
[*]我们这里是短毗连版本,一次毗连=处理惩罚一次请求
[*]但纵然是短毗连,也得保证读取到的数据是一份完备的请求
[*]所以我在线程外部定义了一个缓冲区,用于保存处理惩罚过程中剩下的数据(因为它有大概是其他毗连的一部分)
[*]而线程内部在反序列化时,将完备请求从缓冲区中剥离
代码
#pragma once
#include <signal.h>
#include <unistd.h>
#include <cstring>
#include <functional>
#include <pthread.h>
#include <unordered_map>
#include "socket.hpp"
#include "Serialization.hpp"
static MY_SOCKET my_socket;
#define buff_size 1024 * 30
class http_server;
struct thread_data
{
int sockfd_;
std::string ip_;
std::string &in_buffer_;
http_server *this_;
};
class http_server
{
public:
http_server(const uint16_t port, const std::string &ip = "0.0.0.0")
: port_(port), ip_(ip)
{
content_type_[".html"] = "text/html";
content_type_[".png"] = "image/png";
content_type_[".jpg"] = "image/jpeg";
content_type_[".jpeg"] = "image/jpeg";
}
~http_server() {}
void run()
{
init();
while (true)
{
uint16_t client_port;
std::string client_ip;
// 一个线程处理一次请求(短连接)
pthread_t pid;
std::string in_buffer;
int sockfd = 0;
int count = 5;
do
{
lg(DEBUG, "accepting ...");
sockfd = my_socket.Accept(client_ip, client_port);
if (sockfd != -1 || --count == 0)
{
break;
}
} while (true);
if (sockfd == -1)
{
lg(ERROR, "accepting error");
}
lg(INFO, "get a new link..., sockfd: %d, client ip: %s, client port: %d", sockfd, client_ip.c_str(), client_port);
thread_data *td = new thread_data{sockfd, client_ip, in_buffer, this};
lg(DEBUG, "create pthread");
pthread_create(&pid, nullptr, entry, reinterpret_cast<void *>(td));
// 一个进程服务一个客户端
// lg(DEBUG, "accepting ...");
// int sockfd = my_socket.Accept(client_ip, client_port);
// if (sockfd == -1)
// {
// continue;
// }
// lg(INFO, "get a new link..., sockfd: %d, client ip: %s, client port: %d", sockfd, client_ip.c_str(), client_port);
//int ret = fork();
//if (ret == 0)
//{
// my_socket.Close();
//char buffer;
//std::string in_buffer;
// while (true)
// {
// memset(buffer, 0, sizeof(buffer));
// int n = read(sockfd, buffer, sizeof(buffer)); //"size"\n"a op b"\n
// if (n > 0)
// {
// buffer = 0;
// in_buffer += buffer; // 连续读取
// lg(INFO, "get request: \n%s", in_buffer.c_str());
// // 构建请求
// request req;
// req.deserialize(in_buffer);
// // lg(DEBUG, "path: %s ,url: %s ", (req.path_).c_str(), (req.url_).c_str());
// // 构建响应
// response res;
// handle_response(res, req);
// // 响应序列化
// std::string content;
// res.serialize(content);
// write(sockfd, content.c_str(), content.size());
// }
// else if (n == 0)
// {
// lg(INFO, "%s quit", client_ip.c_str());
// break;
// }
// else // 读出错误
// {
// break;
// }
// }
// exit(0);
// close(sockfd);
// }
}
}
private:
void init()
{
signal(SIGPIPE, SIG_IGN);
signal(SIGCHLD, SIG_IGN);
my_socket.Socket();
my_socket.Bind(port_);
my_socket.Listen();
lg(INFO, "server init done");
}
void handle_response(response &res, request &req)
{
int code = req.code_;
std::string path = req.path_;
std::string content_type_data = content_type_;
// lg(DEBUG, "content_type_data: %s", content_type_data.c_str());
res.version_ = "HTTP/1.1";
if (code == 302)
{
res.code_ = 302;
res.desc_ = "Found";
std::string cl = "Location: ";
cl += "https://www.qq.com";
(res.title_).push_back(cl);
return;
}
if (code == 404)
{
res.code_ = 404;
res.desc_ = "Not Found";
}
else
{
res.code_ = 200;
res.desc_ = "OK";
}
// 将读取网页和图片资源的方式分开
if (req.suffix_ == ".html")
{
res.text_ = get_page(path);
// lg(DEBUG, "text: %s", (res.text_).c_str());
}
else
{
res.text_ = b_get_page(path);
}
//构建响应报头
std::string cl = "Content-Length: ";
cl += std::to_string((res.text_).size());
// lg(DEBUG, "text_size: %d", (res.text_).size());
(res.title_).push_back(cl);
cl = "Content-Type: ";
cl += content_type_data;
(res.title_).push_back(cl);
}
static void *entry(void *args)
{
pthread_detach(pthread_self());
thread_data *td = reinterpret_cast<thread_data *>(args);
int sockfd = td->sockfd_;
std::string ip = td->ip_;
std::string in_buffer = td->in_buffer_;
http_server *it = td->this_;
// 读取请求
char buffer;
bool flag = true;
request req;
while (true) // 虽说是短连接,但也得确保读出来的内容是一个完整的请求
{
memset(buffer, 0, sizeof(buffer));
int n = read(sockfd, buffer, sizeof(buffer));
if (n > 0)
{
buffer = 0;
in_buffer += buffer; // 连续读取
lg(INFO, "get request: \n%s", in_buffer.c_str());
// 构建请求
flag = req.deserialize(in_buffer);
if (flag == false)
{
continue;
}
else
{
break;
}
}
else if (n == 0)
{
lg(INFO, "%s quit", ip.c_str());
return nullptr;
}
else
{
lg(ERROR, "%s read error", ip.c_str());
return nullptr;
}
}
// lg(DEBUG, "path: %s ,url: %s ", (req.path_).c_str(), (req.url_).c_str());
// 构建响应
response res;
it->handle_response(res, req);
// 响应序列化
std::string content;
res.serialize(content);
write(sockfd, content.c_str(), content.size());
//销毁资源
delete td;
close(sockfd);
return nullptr;
}
private:
uint16_t port_;
std::string ip_;
std::unordered_map<std::string, std::string> content_type_;
};
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]