asio实现http服务器

asio实现简易的http服务器

client

1
2
3
4
5
6
7
8
9
10
/**
1. 发送肯定要用到io_context上下文
2. server:服务器地址
3. path:要请求的路径

tcp::resolver resolver_; // 解析域名
tcp::socket socket_; // socket连接
*/
client(boost::asio::io_context& io_context,const std::string& server, const std::string& path)
: resolver_(io_context),socket_(io_context)

初始化的客户端:发送消息,先发送头部。

  • 异步解析ip和端口:封装请求头,调用async_resolve,回调handle_resolve解析ip和端口。
  • handle_resolve:如果解析失败报错,解析成功则调用异步连接async_connect,回调handle_connect。
  • handle_connect:失败打印,成功调用异步写函数async_write,发送请求头,回调handle_write_request。
  • handle_write_request:看请求头发送成功还是失败。成功的话调用async_read_until,异步读,直到读取到”\r\n”,证明读取到了服务器发送回的响应头,回调handle_read_status_line。
  • handle_read_status_line 请求头中的状态码如果是200,则说明响应成功,接下来把所有的头部信息都读出来handle_read_headers。
  • handle_read_headers:代码逐行读出头部信息,然后读出响应的内容handle_read_content。
  • handle_read_content:继续监听读事件读取相应的内容,直到接收到EOF信息,也就是对方关闭,继续监听读事件是因为有可能是长连接的方式,当然如果是短链接,则服务器关闭连接后,客户端也是通过异步函数读取EOF进而结束请求。

Server

  • http::server::server s(“127.0.0.1”, “8080”, path.string()); 初始化服务器相关信息。包括地址和端口,以及初始化的文件路径。
1
2
3
4
5
6
7
8
9
10

/**
signals_ 是优雅退出的时候绑定的信号。
acceptor_ 用来监听的对端的连接。
connection_manager_ 接收到对端的连接后,放到connection_manager_中进行处理,读写都交给manager处理。
socket_:为每个连接到来分配一个socket
request_handler_:处理请求。
*/
server::server(const std::string& address, const std::string& port, const std::string& doc_root): io_service_(),signals_(io_service_),acceptor_(io_service_),connection_manager_(),socket_(io_service_),request_handler_(doc_root){
}
  • do_await_stop: 里面调用signals_.async_wait,异步等待退出信号,优雅退出 。会将acceptor_.close();并且connection_manager_.stop_all();都关闭之后再进行退出。
  • boost::asio::ip::tcp::resolver resolver(io_service_); 初始化一个解析器,绑定到io_service_里,会返回一个端点endpoint。
  • 根据endpoint,打开设置,设置端口复用,设置bind,listen,do_accept();
  • do_accept():异步监听连接:acceptor_.async_accept。连接请求成功后把连接加到connection_manager_里面。
1
connection_manager_.start(std::make_shared<connection>(std::move(socket_),connection_manager_, request_handler_));
  • connection_manager_.start开始一个连接管理类,make_shared智能指针的方式创建一个新的连接connection对象,用std::move(socket_),把socket_move到connection的构造函数里,以及manager自身和处理器request_handler_。
  • start函数就会do_read();do_read()会调用异步读请求async_read_some。
  • async_read_some:构造buffer去读,bytes_transferred是读到了多少数据,ec是成功还是失败。如果成功了,就要继续进行解析:调用request_parser::result_type result的方法。
  • std::tie把返回值绑定为一个元组。返回值有三种类型request_parser::good、request_parser::bad、还有继续接收数据。中间需要继续解析各种类型的数据。
  • do_write:调用的还是异步发送操作。
  • 处理请求的时候,调用handle_request,需要将结果异步返回给对端。
  • handle_request:首先进行路径解码,
    • 判断路径是否为空或者路径格式不正确,如果不正确,直接返回bad_request,异常
    • 判断发送一个斜杠,返回index.html,返回的时候,还是要先把头添上,然后再返回给对端。
    • 如果是正常的目录或者文件,读取后发送。用substr,截取文件后缀名,然后发送文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void connection::do_read()
{
auto self(shared_from_this());
socket_.async_read_some(boost::asio::buffer(buffer_),
[this, self](boost::system::error_code ec, std::size_t bytes_transferred)
{
if (!ec)
{
request_parser::result_type result;
std::tie(result, std::ignore) = request_parser_.parse(
request_, buffer_.data(), buffer_.data() + bytes_transferred);

if (result == request_parser::good)
{
request_handler_.handle_request(request_, reply_);
do_write();
}
else if (result == request_parser::bad)
{
reply_ = reply::stock_reply(reply::bad_request);
do_write();
}
else
{
do_read();
}
}
else if (ec != boost::asio::error::operation_aborted)
{
connection_manager_.stop(shared_from_this());
}
});
}

Beast 实现http server

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
// BeastHttp.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。

#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio.hpp>
#include <chrono> //时钟相关
#include <ctime>
#include<cstdlib>
#include<memory>
#include<string>
#include<json/json.h> // post请求的时候,post一个json数据的时候,处理数据
#include<json/value.h>
#include<json/reader.h>
#include <iostream>

using namespace Json;
using namespace std;

namespace beast = boost::beast; //空间命名
namespace http = beast::http;
namespace net = boost::asio;
using tcp = boost::asio::ip::tcp;

namespace my_program_state {
// 获取别人连接我们服务器了多少次
std::size_t request_count() {
static std::size_t count = 0;
return count++;
}

// 获取当前时间戳
std::time_t now() {
return std::time(0);
}
}

// http的连接类,为了能以智能指针的方式使用,并且能够在内部分享出自己的智能指针,
// 继承public std::enable_shared_from_this<http_connection>,允许分享智能指针
class http_connection :public std::enable_shared_from_this<http_connection> {
public:
/*
socket_(socket) :这种构造方式是不行的,socket是不允许做拷贝赋值的
拷贝赋值会调用拷贝构造,socket不实现拷贝构造,那这里就会报错
要么传一个引用,要么用移动赋值,move语义来做。
1. 如果传入引用,那么管理类中的socket就不能独立的管理这个socket
2. 用move语义,移动构造来做,就可以实现独立管理功能,这也是C++11的妙处
移动构造,就把类的使用时间,都交给了类的成员来管理,可用有效性就失效了
*/
http_connection(tcp::socket socket):socket_(std::move(socket)) {

}

void start() {
read_request();
check_deadline(); // 超时检测:tcp是一个长连接,http是一个短连接,时间长了以后,就把这个Http断掉,保证连接处理不要太超时


}
private:
tcp::socket socket_;
beast::flat_buffer buffer_{ 8192 }; // beast的buffer类型
http::request<http::dynamic_body> request_; // dynamic_body动态包体,可以接收各种请求
http::response<http::dynamic_body> response_;

// asio设计的定时器,设置时间超过1分钟,就认为是超时了。
net::steady_timer deadline_{socket_.get_executor(),std::chrono::seconds(60) };

void read_request() {
auto self = shared_from_this();
// 异步去读
http::async_read(socket_, buffer_, request_, [self](beast::error_code ec,std::size_t bytes_transferred) {
boost::ignore_unused(bytes_transferred);
if (!ec) {
self->progress_request();
}
});
}

// 检查超时
void check_deadline() {
auto self = shared_from_this();

// async_wait 异步表达式,接收一个异步的回调函数,或者一个lambda表达式
// 捕获一个this,参数传递一个错误码,这里会有一个异常,这个函数只会在超时之后调用,60s的时间里,http_connection可能都已经回收了
// 变成捕获self
deadline_.async_wait([self] (boost::system::error_code ec) {
if (!ec) {
self->socket_.close();
}
});
}

// 处理
void progress_request() {
response_.version(request_.version());
response_.keep_alive(false); // 短连接
switch (request_.method())
{
case http::verb::get:
response_.result(http::status::ok);
response_.set(http::field::server, "Beast");
create_response();
break;
case http::verb::post:
create_post_response();
break;
default:
response_.result(http::status::bad_request);
response_.set(http::field::content_type, "text/plain"); // 设置回应头
//beast::ostream,回应对象里面写数据了
beast::ostream(response_.body()) << "Invalid request-method ' " << std::string(request_.method_string()) << "'";
break;
}

// 将数据发送回去
write_response();
}

void create_response() {
if (request_.target() == "/count") {
// 回复一个html
response_.set(http::field::content_type,"text/html");
beast::ostream(response_.body()) << "<html>\n"
<< "<head><title>Request count</title></head>\n"
<< "<body>\n"
<< "<h1>Request count</h1>\n"
<< "<p>There have been "
<< my_program_state::request_count() //返回count
<< " requests so far.</p>\n"
<< "</body>\n"
<< "</html>\n";
}
else if (request_.target() == "/time")
{
response_.set(http::field::content_type, "text/html");
beast::ostream(response_.body())
<< "<html>\n"
<< "<head><title>Current time</title></head>\n"
<< "<body>\n"
<< "<h1>Current time</h1>\n"
<< "<p>The current time is "
<< my_program_state::now()
<< " seconds since the epoch.</p>\n"
<< "</body>\n"
<< "</html>\n";
}
else
{
response_.result(http::status::not_found);
response_.set(http::field::content_type, "text/plain");
beast::ostream(response_.body()) << "File not found\r\n";
}


}

void create_post_response() {
if (request_.target() == "/email")
{
auto& body = this->request_.body();
auto body_str = boost::beast::buffers_to_string(body.data());
std::cout << "receive body is " << body_str << std::endl;
this->response_.set(http::field::content_type, "text/json");
Json::Value root;
Json::Reader reader;
Json::Value src_root;
bool parse_success = reader.parse(body_str, src_root);
if (!parse_success) {
std::cout << "Failed to parse JSON data!" << std::endl;
root["error"] = 1001;
std::string jsonstr = root.toStyledString();
beast::ostream(this->response_.body()) << jsonstr;
return;
}

auto email = src_root["email"].asString();
std::cout << "email is " << email << std::endl;

root["error"] = 0;
root["email"] = src_root["email"];
root["msg"] = "recevie email post success";
std::string jsonstr = root.toStyledString();
beast::ostream(this->response_.body()) << jsonstr;
}
else
{
response_.result(http::status::not_found);
response_.set(http::field::content_type, "text/plain");
beast::ostream(response_.body()) << "File not found\r\n";
}
}

// 异步的将数据写回去
void write_response() {
auto self = shared_from_this();

response_.content_length(response_.body().size()); // 写回数据的长度

http::async_write(
socket_,
response_,
[self](beast::error_code ec, std::size_t)
{
// 发送完之后,服务器主动去关闭发送端,shutdown短连接
self->socket_.shutdown(tcp::socket::shutdown_send, ec);
self->deadline_.cancel(); //定时器关闭
});
}
};

void http_server(tcp::acceptor& acceptor, tcp::socket& socket)
{
acceptor.async_accept(socket,[&](beast::error_code ec){
if (!ec)
std::make_shared<http_connection>(std::move(socket))->start();
http_server(acceptor, socket);// 继续监听请求
});
}


int main()
{
try
{
// Check command line arguments.
/*if (argc != 3)
{
std::cerr << "Usage: " << argv[0] << " <address> <port>\n";
std::cerr << " For IPv4, try:\n";
std::cerr << " receiver 0.0.0.0 80\n";
std::cerr << " For IPv6, try:\n";
std::cerr << " receiver 0::0 80\n";
return EXIT_FAILURE;
}*/

auto const address = net::ip::make_address("127.0.0.1");
unsigned short port = static_cast<unsigned short>(8080); //静态转换类型

net::io_context ioc{ 1 };

tcp::acceptor acceptor{ ioc, {address, port} };
tcp::socket socket{ ioc };
http_server(acceptor, socket);

std::cout << "server" << endl;

ioc.run();
}
catch (std::exception const& e)
{
std::cerr << "Error: " << e.what() << std::endl;
return EXIT_FAILURE;
}
}




asio实现http服务器
https://cauccliu.github.io/2024/03/25/asio实现http服务器/
Author
Liuchang
Posted on
March 25, 2024
Licensed under