基于Asio2实现http服务器的实践总结(二)

目录

1.基于asio2实现的http示例代码

2.修改服务线程数

3.在绑定的路由请求中获取请求的远程的IP和端口

4.静态资源绑定

[5.在 url 中传递参数的原理](#5.在 url 中传递参数的原理)

5.1.简介

[5.2.浏览器本地预处理(输入 URL 后,发请求前)](#5.2.浏览器本地预处理(输入 URL 后,发请求前))

[5.3.建立 TCP 连接](#5.3.建立 TCP 连接)

[5.4.浏览器发送 HTTP 请求报文](#5.4.浏览器发送 HTTP 请求报文)

5.5.服务端(asio2)接收、解析、处理请求

[5.6.服务端返回 HTTP 响应报文](#5.6.服务端返回 HTTP 响应报文)

5.7.浏览器接收响应,解析渲染页面

5.8.连接维持与关闭

5.9.服务端把参数传给前端的两种方式

6.唯一标识用户还是需要token

7.跨域处理

[8.QtWebEngine 拼接 URL 携带 Token](#8.QtWebEngine 拼接 URL 携带 Token)

[9.asio2 内置 URL 工具(http 命名空间)](#9.asio2 内置 URL 工具(http 命名空间))

Asio2: 一个基于 Boost.Asio 封装的高性能网络编程库(一)

1.基于asio2实现的http示例代码

源码如下:

cpp 复制代码
#include <iostream>
#include <asio2/asio2.hpp>

struct aop_log
{
	bool before(http::web_request& req, http::web_response& rep)
	{
		asio2::ignore_unused(rep);
		printf("aop_log before %s\n", req.method_string().data());
		return true;
	}
	bool after(std::shared_ptr<asio2::http_session>& session_ptr, http::web_request& req, http::web_response& rep)
	{
		ASIO2_ASSERT(asio2::get_current_caller<std::shared_ptr<asio2::http_session>>().get() == session_ptr.get());
		asio2::ignore_unused(session_ptr, req, rep);
		printf("aop_log after\n");
		return true;
	}
};

struct aop_check
{
	bool before(std::shared_ptr<asio2::http_session>& session_ptr, http::web_request& req, http::web_response& rep)
	{
		ASIO2_ASSERT(asio2::get_current_caller<std::shared_ptr<asio2::http_session>>().get() == session_ptr.get());
		asio2::ignore_unused(session_ptr, req, rep);
		printf("aop_check before\n");
		return true;
	}
	bool after(http::web_request& req, http::web_response& rep)
	{
		asio2::ignore_unused(req, rep);
		printf("aop_check after\n");
		return true;
	}
};

int main()
{
	// try open http://localhost:8080 in your browser
	std::string_view host = "0.0.0.0";
	std::string_view port = "8080";

	asio2::http_server server;

	server.support_websocket(true);

	// root = /asio2/bin/x64
	std::filesystem::path root = std::filesystem::current_path();

	// root = /asio2/example/wwwroot
	root = root.parent_path().parent_path().append("example/wwwroot");

	// set the http server root directory
	server.set_root_directory(std::move(root));

	server.bind_recv([&](http::web_request& req, http::web_response& rep)
	{
		asio2::ignore_unused(req, rep);
		// all http and websocket request will goto here first.
		std::cout << req.path() << req.query() << std::endl;
	}).bind_connect([](auto & session_ptr)
	{
		printf("client enter : %s %u %s %u\n",
			session_ptr->remote_address().c_str(), session_ptr->remote_port(),
			session_ptr->local_address().c_str(), session_ptr->local_port());
		//session_ptr->set_response_mode(asio2::response_mode::manual);
		session_ptr->set_disconnect_timeout(std::chrono::seconds(2));
	}).bind_disconnect([](auto & session_ptr)
	{
		printf("client leave : %s %u %s\n",
			session_ptr->remote_address().c_str(), session_ptr->remote_port(),
			asio2::last_error_msg().c_str());
	}).bind_start([&]()
	{
		printf("start http server : %s %u %d %s\n",
			server.listen_address().c_str(), server.listen_port(),
			asio2::last_error_val(), asio2::last_error_msg().c_str());
	}).bind_stop([&]()
	{
		printf("stop http server : %d %s\n",
			asio2::last_error_val(), asio2::last_error_msg().c_str());
	});

	server.bind<http::verb::get, http::verb::post>("/", [](http::web_request& req, http::web_response& rep)
	{
		asio2::ignore_unused(req, rep);

		rep.fill_file("/index.html");
	}, aop_log{}, http::enable_cache);

	// If no method is specified, GET and POST are both enabled by default.
	server.bind("*", [](http::web_request& req, http::web_response& rep)
	{
		rep.fill_file(http::url_decode(req.target()));
		rep.chunked(true);
	}, aop_check{}, http::enable_cache);

	// Test http multipart
	server.bind<http::verb::get, http::verb::post>("/multipart_test", [](http::web_request& req, http::web_response& rep)
	{
		auto& body = req.body();
		auto target = req.target();

		std::stringstream ss;
		ss << req;
		auto str_req = ss.str();

		asio2::ignore_unused(body, target, str_req);

		for (auto it = req.begin(); it != req.end(); ++it)
		{
			std::cout << it->name_string() << ": " << it->value() << std::endl;
		}

		auto mpbody = req.multipart();

		for (auto it = mpbody.begin(); it != mpbody.end(); ++it)
		{
			std::cout
				<< "filename:" << it->filename() << " name:" << it->name()
				<< " content_type:" << it->content_type() << std::endl;
		}

		if (req.has_multipart())
		{
			http::multipart_fields multipart_body = req.multipart();

			auto& field_username = multipart_body["username"];
			auto username = field_username.value();

			auto& field_password = multipart_body["password"];
			auto password = field_password.value();

			std::string str = http::to_string(multipart_body);

			std::string type = "multipart/form-data; boundary="; type += multipart_body.boundary();

			rep.fill_text(str, http::status::ok, type);

			http::web_request req_copy;
			req_copy.method(http::verb::post);
			req_copy.set(http::field::content_type, type);
			req_copy.keep_alive(true);
			req_copy.target("/api/user/");
			req_copy.body() = str;
			req_copy.prepare_payload();

			std::stringstream ress;
			ress << req_copy;
			auto restr = ress.str();

			asio2::ignore_unused(username, password, restr);
		}
		else
		{
			rep.fill_page(http::status::ok);
		}
	}, aop_log{});

	server.bind<http::verb::get>("/del_user",
		[](std::shared_ptr<asio2::http_session>& session_ptr, http::web_request& req, http::web_response& rep)
	{
		asio2::ignore_unused(req, rep);

		printf("del_user ip : %s\n", session_ptr->remote_address().data());

		rep.fill_page(http::status::ok, "del_user successed.");

	}, aop_check{});

	server.bind<http::verb::get, http::verb::post>("/api/user/*", [](http::web_request& req, http::web_response& rep)
	{
		asio2::ignore_unused(req, rep);

		// if the url is :
		// /api/user/add_user?name=hanmeimei&age=20&height=168

		[[maybe_unused]] std::string_view t = req.target(); // t == /api/user/add_user?name=hanmeimei&age=20&height=168
		[[maybe_unused]] std::string_view p = req.get_path(); // p == /api/user/add_user
		[[maybe_unused]] std::string_view q = req.get_query(); // q == name=hanmeimei&age=20&height=168

		std::vector<std::string_view> kvs = asio2::split(q, '&');

		for (std::string_view kv : kvs)
		{
			[[maybe_unused]] std::string_view k = kv.substr(0, kv.find('='));
			[[maybe_unused]] std::string_view v = kv.substr(kv.find('=') + 1);
		}

		//rep.fill_text("the user name is hanmeimei, .....");

	}, aop_log{}, aop_check{});

	server.bind<http::verb::get>("/defer", [](http::web_request& req, http::web_response& rep)
	{
		asio2::ignore_unused(req, rep);

		// use defer to make the reponse not send immediately, util the derfer shared_ptr
		// is destroyed, then the response will be sent.
		std::shared_ptr<http::response_defer> rep_defer = rep.defer();

		std::thread([rep_defer, &rep]() mutable
		{
			std::this_thread::sleep_for(std::chrono::milliseconds(1000));

			rep = asio2::http_client::execute("http://www.baidu.com");
		}).detach();

	}, aop_log{}, aop_check{});

	// the /ws is the websocket upgraged target
	server.bind("/ws", websocket::listener<asio2::http_session>{}.
		on("message", [](std::shared_ptr<asio2::http_session>& session_ptr, std::string_view data)
	{
		printf("ws msg : %zu %.*s\n", data.size(), (int)data.size(), data.data());

		session_ptr->async_send(data);

	}).on("open", [](std::shared_ptr<asio2::http_session>& session_ptr)
	{
		printf("ws open\n");

		// print the websocket request header.
		std::cout << session_ptr->request() << std::endl;
		
		// same as above
		auto& req = session_ptr->get_request();
		std::cout << req.get_path() << std::endl;

		// Set the binary message write option.
		session_ptr->ws_stream().binary(true);

		// Set the text message write option.
		//session_ptr->ws_stream().text(true);

		// how to set custom websocket response data : 
		session_ptr->ws_stream().set_option(websocket::stream_base::decorator(
			[](websocket::response_type& rep)
		{
			rep.set(http::field::authorization, " http-server-coro");
		}));

		// how to send a message to client when the websocket connection is connected.
		// can't use session_ptr->async_send(...) directly, because the websocket connection is not ready.
		session_ptr->post_queued_event([session_ptr]()
		{
			session_ptr->async_send("eg: hello websocket");
		});

	}).on("close", [](std::shared_ptr<asio2::http_session>& session_ptr)
	{
		asio2::ignore_unused(session_ptr);

		printf("ws close\n");

	}).on_ping([](std::shared_ptr<asio2::http_session>& session_ptr)
	{
		asio2::ignore_unused(session_ptr);

		printf("ws ping\n");

	}).on_pong([](std::shared_ptr<asio2::http_session>& session_ptr)
	{
		asio2::ignore_unused(session_ptr);

		printf("ws pong\n");

	}));

	server.bind_not_found([](http::web_request& req, http::web_response& rep)
	{
		asio2::ignore_unused(req);

		rep.fill_page(http::status::not_found);
	});

	server.start(host, port);

	while (std::getchar() != '\n'); // press enter to exit this program

	return 0;
}

2.修改服务线程数

如果不人为控制,asio2内部会自动获取系统的CPU核数,启动对于核数的线程,如果控制启动的线程数,可以手动传入参数,如:

cpp 复制代码
asio2::http_server server(1); //1个线程

http客户端(如浏览器)发送请求是并发的,服务端多线程处理,就会涉及到并发的去访问文件、数据库、网络等资源,设计的时候需要考虑到。

3.在绑定的路由请求中获取请求的远程的IP和端口

cpp 复制代码
server.bind<http::verb::get, http::verb::post>("/", [](http::web_request& req, http::web_response& rep)
{
	asio2::ignore_unused(req, rep);

	rep.fill_file("/index.html");
}, aop_log{}, http::enable_cache);

在 asio2 库中,不能 直接从 http::web_request 对象获取远端的 IP 和端口。http::web_request 只封装了 HTTP 请求本身的内容(如方法、路径、头部等),不包含底层连接信息。

asio2 在绑定 HTTP 路由回调时,允许将会话对象指针 (std::shared_ptr<asio2::http_session>)作为第一个参数传入。你需要通过这个会话对象来获取远端地址和端口。

cpp 复制代码
server.bind<http::verb::get>("/",
    [](std::shared_ptr<asio2::http_session>& session_ptr,
       http::web_request& req, http::web_response& rep) {
        
        // 获取远端 IP 和端口
        std::string remote_ip = session_ptr->remote_address();
        uint16_t remote_port = session_ptr->remote_port();
        
        printf("client ip: %s, port: %u\n", remote_ip.c_str(), remote_port);
        
        rep.fill_page(http::status::ok, "success");
    }, aop_check{});

关键点说明:

  • session_ptr 是回调的第一个参数 :asio2 允许你在 HTTP 回调签名中选择是否添加这个参数。如果不需要远端信息,可以省略它;如果需要,就把它加上。

  • remote_address() 返回 std::string_view :可以直接 .data() 或转换为 std::string 使用。

  • remote_port() 返回端口号 :类型通常为 uint16_t。

4.静态资源绑定

cpp 复制代码
// If no method is specified, GET and POST are both enabled by default.
server.bind("*", [](http::web_request& req, http::web_response& rep)
{
	rep.fill_file(http::url_decode(req.target()));
	rep.chunked(true);
}, aop_check{}, http::enable_cache);
req的路径函数 对应含义
req.path() 服务器切开 ? 后得到的路径部分
req.query() 服务器切开 ? 后得到的原始参数字符串
req.target() 完整原始请求目标(path + ? + query)
req.body() body内容
parse_query(raw_q) 按 & 分割、按 = 拆键值、执行 URL 解码
rep.fill_file(req.path()) 只用 path 拼接 root_dir 找文件,query 完全不参与
cpp 复制代码
// 完整原始target
std::string full_target = req.target();//如:/index.html?a=10&b=10
// 拆分好的路径
std::string path = req.path(); //如:/index.html
// 拆分好的查询串
std::string query_raw = req.query();//如:a=10&b=10

如果在主目录下有个index.html,那么就可以通过下面的地址访问:

http://192.168.3.62:8080/index.html

5.在 url 中传递参数的原理

5.1.简介

基于 RFC 3986 URI 标准 和 HTTP 协议规范,URL 参数(Query String)本质是在请求行中附加的键值对信息,和资源路径严格分离,全程不影响服务端文件定位。

一个完整 HTTP URL 的结构如下,? 是路径和参数的法定分隔符:

复制代码
http://127.0.0.1:8080/index.html?uid=1001&name=张三#section
└──┬───┘   └────┬────┘ └──┬───┘ └────────┬────────┘ └──┬───┘
 协议         主机:端口    路径         查询参数        锚点(fragment)
部分 作用 是否发给服务端
路径 /index.html 定位服务器上的资源 / 路由 是
查询参数 uid=1001&name=张三 传递业务参数 是
锚点 #section 浏览器内页面定位 否,完全不发给服务器

核心结论:服务器解析 URL 时,会在第一个 ? 处直接切开,前面的部分作为资源路径,后面的部分作为纯参数字符串。 这就是你之前观察到的「asio2 自动分开 path 和 query」的底层原因 ------ 所有标准 HTTP 服务器都是这么实现的。

整个过程是**「浏览器封装编码 → 网络传输 → 服务端解析处理 → 网络返回 → 浏览器解析渲染」**的完整闭环,底层基于 TCP/IP 协议栈和 HTTP 应用层协议。

5.2.浏览器本地预处理(输入 URL 后,发请求前)

用户在地址栏输入 URL 并回车,浏览器先做 4 件事:

1. 拆分 URL 结构

按标准把 URL 拆成独立字段,? 是路径和参数的法定分界符

复制代码
协议: http
主机: 127.0.0.1
端口: 8080
路径: /index.html
查询参数: uid=1001&name=张三&token=abc123

2.URL 编码(百分号编码)

URL 只允许 ASCII 字符,中文、空格、特殊符号会自动编码:

  • 张三 → %E5%BC%A0%E4%B8%89
  • 空格 → %20
  • 目的:避免中文乱码、避免特殊字符(如&、=)破坏参数结构

对应你代码里的 url_decode,就是做这个的逆过程。

3. 缓存检查

浏览器先查本地缓存(强缓存 / 协商缓存),如果页面之前访问过且没过期,直接从本地加载,不发请求到服务器。

4. DNS 解析(域名转 IP)

如果是域名(如www.test.com),浏览器会通过 DNS 服务器把域名解析成 IP 地址; 你用127.0.0.1是本地回环地址,直接跳过 DNS 解析。

5.3.建立 TCP 连接

HTTP 基于 TCP 传输,浏览器和服务器通过TCP 三次握手建立可靠连接:

  1. 浏览器 → 服务器:SYN 同步包
  2. 服务器 → 浏览器:SYN+ACK 确认包
  3. 浏览器 → 服务器:ACK 确认包

连接建立后,才能开始传输 HTTP 报文。 如果是 HTTPS,这一步还会额外做 TLS 加密握手。

5.4.浏览器发送 HTTP 请求报文

连接建立后,浏览器把请求内容封装成标准 HTTP 报文,通过 TCP 发出去。

HTTP 请求报文结构

复制代码
【请求行】GET /index.html?uid=1001&name=%E5%BC%A0%E4%B8%89&token=abc123 HTTP/1.1
【请求头】
Host: 127.0.0.1:8080
User-Agent: Chrome/120.0.0.0
Accept: text/html,application/xhtml+xml
Connection: keep-alive
...
【空行】
【请求体】(GET 请求体为空,POST 才会放数据)

核心要点

  1. 参数在请求行的 URL 里,不在请求体里:GET 请求的参数永远跟在路径后面,不会放进 body。
  2. 参数已经编码完成:name=张三 变成了 name=%E5%BC%A0%E4%B8%89。
  3. 整个报文是纯文本格式,按行分隔。

5.5.服务端(asio2)接收、解析、处理请求

这是你写的 C++ 代码真正执行的阶段,asio2 内部按以下步骤执行:

步骤 1:接收并解析 HTTP 报文

asio2 从 TCP 连接读取数据,按 HTTP 协议解析出:

  • 请求方法:GET
  • 完整 URL:/index.html?uid=1001&name=%E5%BC%A0%E4%B8%89&token=abc123
  • 所有请求头

步骤 2:拆分路径和查询参数

在第一个 ? 处直接切开,分成两部分存储:

  • req.path() → /index.html(只用来匹配路由、找文件)
  • req.query() → uid=1001&name=%E5%BC%A0%E4%B8%89&token=abc123(纯参数字符串)

✅ 这就是「带参数也能正常访问静态文件」的根本原因:查询参数根本不参与文件路径拼接,找文件只用 path 部分。

步骤 3:路由匹配

asio2 按你写 bind 的从上到下顺序 ,用 req.path() 去匹配每个路由的正则:

  1. 先匹配 /api/echo 等业务接口 → 不匹配
  2. 再匹配最后的 .* 静态兜底路由 → 匹配成功,执行对应的回调函数

步骤 4:执行业务回调(你写的代码逻辑)

  1. 调用 parse_query(req.query()):按&分割、按=拆键值、做 URL 解码,得到参数 map
  2. Token 校验:取出token判断是否合法,不合法直接返回 401,终止流程
  3. 静态资源处理:
    • 如果是index.html:调用 rep.load_file() 读取 HTML 文件,把{``{uid}}、{``{name}}替换成参数值(服务端模板注入)
    • 如果是 css/js:调用 rep.fill_file() 直接读取文件内容
  4. 设置 CORS 跨域响应头、Content-Type 等

步骤 5:构建 HTTP 响应报文

asio2 把处理结果封装成标准 HTTP 响应报文,包含:

  • 状态行:HTTP/1.1 200 OK
  • 响应头:Content-Type、Access-Control-Allow-Origin 等
  • 响应体:处理后的 HTML 内容 / 文件二进制数据

5.6.服务端返回 HTTP 响应报文

asio2 把封装好的响应报文,通过 TCP 连接发回给浏览器。

HTTP 响应报文示例

复制代码
【状态行】HTTP/1.1 200 OK
【响应头】
Content-Type: text/html;charset=utf-8
Content-Length: 1256
Access-Control-Allow-Origin: *
Connection: keep-alive
【空行】
【响应体】
<!DOCTYPE html>
<html>...(完整的HTML页面内容,占位符已经替换成实际参数值)

5.7.浏览器接收响应,解析渲染页面

浏览器收到响应后,按以下步骤处理,最终显示出页面:

1. 解析响应

  • 读取状态码:200 表示成功,404 表示找不到,401 表示未授权
  • 读取响应头:根据Content-Type判断内容类型
  • 接收完整响应体

2. HTML 解析 → DOM 树

浏览器把 HTML 文本从上到下解析,构建 DOM(文档对象模型)树,页面上的每个标签都是树上的一个节点。

如果是服务端模板注入的参数({``{uid}}),解析后直接就是实际值,页面上立刻能看到。

3. CSS 解析 → 渲染树

解析 CSS 样式,把 DOM 树和样式结合,生成渲染树,计算每个元素的位置、大小、颜色。

4. JS 执行

遇到<script>标签就暂停解析,执行 JS 代码:

  • 如果是前端 JS 读取 URL 参数:JS 通过window.location.search拿到地址栏里的 query 字符串,用URLSearchParams解析出 uid、name 等参数
  • 如果有 fetch/axios 请求:JS 再发起新的 HTTP 请求去调用后端 API

5. 绘制与显示

浏览器根据渲染树,把页面像素绘制到屏幕上,用户最终看到完整的网页。


5.8.连接维持与关闭

  • 默认开启 Connection: keep-alive 长连接,同一个域名的后续请求复用这个 TCP 连接,不用每次都三次握手
  • 连接空闲一段时间后,通过TCP 四次挥手关闭连接

5.9.服务端把参数传给前端的两种方式

方式 环节 原理
服务端模板注入 阶段 4 步骤 4 asio2 读取 HTML 文件,把占位符替换成参数值,再返回给浏览器
前端 JS 读取 URL 阶段 6 步骤 4 浏览器地址栏本身就带参数,JS 直接从window.location里解析

6.唯一标识用户还是需要token

token的主要作用是用来验证这个请求是「我的合法客户端」,拦截陌生 / 非法访问 。还有一个重要作用是关联上下文。因为http客户端的请求是多线程多连接并发的,用远程的ip和端口来表示唯一用户是不科学的。

asio2中一般是在登陆请求处理后返回的http头添加token,后面访问其它接口,都必须加上token,解析出的token的方法:

cpp 复制代码
http::web_request& req
std::string token;
for (auto it = req.begin(); it != req.end(); ++it)
{
    std::cout << it->name_string() << ": " << it->value() << std::endl;
    if (it->name_string() == "Authorize"){
        token = it->value();
    }
}

7.跨域处理

cpp 复制代码
asio2::http_server server;
    server.bind<asio2::http::verb::get>(".*", [](asio2::http_request &req, asio2::http_response &rep)
    {
        // 获取TCP远端IP和端口
        std::string client_ip = req.remote_address();
        uint16_t client_port = req.remote_port();
        std::cout << "[访问] " << client_ip << ":" << client_port << " | " << req.path() << "?" << req.query() << "\n";

        auto params = parse_query(req.query());
        // ========== Token校验 ==========
        auto token_it = params.find("token");
        if(token_it == params.end() || token_it->second != g_server_token)
        {
            rep.status(401);
            rep.set_header("Content-Type", "text/html;charset=utf-8");
            rep.body() = "<h1>401 未授权,Token无效</h1>";
            return;
        }
        // ===============================

        // CORS跨域头
        rep.set("Access-Control-Allow-Origin", "*");
        rep.set("Access-Control-Allow-Methods", "GET,POST,OPTIONS");
        rep.set_hseteader("Access-Control-Allow-Headers", "*");

        if(req.path() == "/index.html")
        {
            std::string html;
            if(rep.load_file(req.path(), html))
            {
                std::string uid = params["uid"];
                replace_all(html, "{{uid}}", uid);
                rep.body() = html;
                rep.set_header("Content-Type", "text/html;charset=utf-8");
            }
            else
            {
                rep.status(404);
                rep.body() = "<h1>404 Not Found</h1>";
            }
        }
        else
        {
            bool ok = rep.fill_file(req.path());
            if(!ok)
            {
                rep.status(404);
                rep.body() = "<h1>404</h1>";
            }
        }
    });

8.QtWebEngine 拼接 URL 携带 Token

cpp 复制代码
QUrl url("http://127.0.0.1:8080/index.html");
QUrlQuery query;
query.addQueryItem("token", g_server_token);
query.addQueryItem("uid", "1001");
url.setQuery(query);
ui->webView->setUrl(url);

9.asio2 内置 URL 工具(http 命名空间)

asio2 自带 http url 解析、编解码工具,直接分离 scheme/host/path/query,path、query 中文必须 url_encode,不支持裸中文直接请求:

cpp 复制代码
#include "asio2/http.hpp"
using namespace asio2;

// 1. URL编解码(核心本地化:中文、特殊字符百分号编码 UTF-8)
std::string raw_url = "http://127.0.0.1:8080/api/文件?name=测试";
std::string url_enc = http::url_encode(raw_url);
std::string url_dec = http::url_decode(url_enc);

// 2. 拆分path / query
std::string path = http::url_to_path("/api/文件?name=测试");
std::string query = http::url_to_query("/api/文件?name=测试");

// 3. 直接http_client执行url
asio2::http_client client;
std::error_code ec;
auto rep = client.execute("http://localhost:8080/hello", ec);

接口说明

  1. http::url_encode:RFC3986 百分号编码,路径、查询参数中文、空格、#& 等特殊字符用这个
  2. http::url_decode:解码
  3. url_to_path:提取路径(/xxx)
  4. url_to_query:提取?后的 query 串

注意:asio2没有内置 IDNA (Punycode) 域名编码 。如果是中文域名http://中文域名.com,需要自己实现 punycode 转码后再传给 asio2。

完整的示例如下:

cpp 复制代码
#include <asio2/http/http_client.hpp>
#include <iostream>

int main()
{
	std::string_view host = "127.0.0.1";
	std::string_view port = "8080";

	asio2::socks5::option<asio2::socks5::method::anonymous>
		sock5_option{ "127.0.0.1",10808 };
	//asio2::socks5::option<asio2::socks5::method::anonymous, asio2::socks5::method::password>
	//	sock5_option{ "s5.doudouip.cn",1088,"zjww-1","aaa123" };

	// download and save the file directly. // see ssl_http_client.cpp
	// The file is in this directory: /asio2/example/bin/x64/100mb.test
	asio2::http_client::download("http://cachefly.cachefly.net/100mb.test", "100mb.test");

	std::string_view url = R"(http://www.baidu.com/cond?json={"qeury":"name like '%abc%'","id":1})";

	std::string en = http::url_encode(url);
	std::cout << en << std::endl;
	std::string de = http::url_decode(en);
	std::cout << de << std::endl;

	auto path = asio2::http::url_to_path(url);
	std::cout << path << std::endl;

	auto query = asio2::http::url_to_query(url);
	std::cout << query << std::endl;


	auto rep21 = asio2::http_client::execute(url);
	std::cout << rep21 << std::endl;

	http::web_request req24 = http::make_request(url);
	req24.set(http::field::user_agent, "Chrome");
	auto rep26 = asio2::http_client::execute(req24);
	std::cout << rep26 << std::endl;


	asio2::http_client::execute("www.baidu.com", "80", "/", std::chrono::seconds(5));
	asio2::http_client::execute("www.baidu.com", "80", "/", sock5_option);


	// GET
	auto req2 = http::make_request("GET / HTTP/1.1\r\nHost: 192.168.0.1\r\n\r\n");
	auto rep2 = asio2::http_client::execute("www.baidu.com", "80", req2, std::chrono::seconds(3));
	if (asio2::get_last_error())
		std::cout << asio2::last_error_msg() << std::endl;
	else
		std::cout << rep2 << std::endl;

	// POST
	auto req4 = http::make_request("POST / HTTP/1.1\r\nHost: 192.168.0.1\r\n\r\n");
	auto rep4 = asio2::http_client::execute("www.baidu.com", "80", req4);
	if (asio2::get_last_error())
		std::cout << asio2::last_error_msg() << std::endl;
	else
		std::cout << rep4 << std::endl;

	// GET
	http::web_request req6(http::verb::get, "/", 11);
	req6.set(http::field::user_agent, "Chrome");
	auto rep6 = asio2::http_client::execute("www.baidu.com", "80", req6, sock5_option);
	if (asio2::get_last_error())
		std::cout << asio2::last_error_msg() << std::endl;
	else
		std::cout << rep6 << std::endl;

	// POST
	http::web_request req7;
	req7.method(http::verb::post);
	req7.target("/");
	req7.set(http::field::user_agent, "Chrome");
	req7.set(http::field::content_type, "text/html");
	req7.body() = "Hello World.";
	req7.prepare_payload();
	auto rep7 = asio2::http_client::execute("www.baidu.com", "80", req7);
	if (asio2::get_last_error())
		std::cout << asio2::last_error_msg() << std::endl;
	else
		std::cout << rep7 << std::endl;

	// convert the response body to string
	std::stringstream ss1;
	ss1 << rep7.body();
	std::cout << ss1.str() << std::endl;

	// convert the whole response to string
	std::stringstream ss2;
	ss2 << rep7;
	std::cout << ss2.str() << std::endl;


	asio2::http_client client;

	client.bind_recv([&](http::web_request& req, http::web_response& rep)
	{
		// print the whole response
		std::cout << rep << std::endl;

		// print the response body
		std::cout << rep.body() << std::endl;

		// convert the response body to string
		std::stringstream ss;
		ss << rep.body();
		std::cout << ss.str() << std::endl;

		// Remove all fields
		req.clear();

		req.set(http::field::user_agent, "Chrome");
		req.set(http::field::content_type, "text/html");

		req.method(http::verb::get);
		req.keep_alive(true);
		req.target("/get_user?name=abc");
		req.body() = "Hello World.";
		req.prepare_payload();

		client.async_send(std::move(req));

	}).bind_connect([&]()
	{
		if (asio2::get_last_error())
			printf("connect failure : %d %s\n",
				asio2::last_error_val(), asio2::last_error_msg().c_str());
		else
			printf("connect success : %s %u\n",
				client.local_address().c_str(), client.local_port());

		// connect success, send a request.
		if (!asio2::get_last_error())
		{
			const char * msg = "GET / HTTP/1.1\r\n\r\n";
			client.async_send(msg);
		}
	});

	client.start(host, port/*, sock5_option*/);

	while (std::getchar() != '\n');

	return 0;
}
相关推荐
技术不好的崎鸣同学1 小时前
第七章:模块踩踏以覆写导出函数绕过CFG
c++·windows·安全·系统安全
miofly1 小时前
GitHub 今日推荐|ps2-web-recomp:浏览器里跑 PS2 游戏的静态重编译方案
c++·开源·github
开开心心就好1 小时前
Windows视频播放器推荐,精简版体积更小
java·服务器·开发语言·eclipse·pdf·ocr·音视频
开开心心就好1 小时前
A3的PDF怎么切成A4?拖一下分割线就行
服务器·开发语言·python·算法·pdf·ocr·tornado
Ruiery1 小时前
Linux 6.6内核内存管理深度解析(四):大页(HugeTLB / THP)— 为什么一个页表项要管 2MB
linux·运维·服务器
晚枫歌F1 小时前
dpdk实现arp协议
服务器·网络
惊讶的猫1 小时前
F12 Network:把“发 HTTP 请求”这件事变成看得见的东西
网络·网络协议·http
沫璃染墨1 小时前
从零入门计算机网络系列(五):UDP套接字编程实战——从核心API到客户端与服务器通信
linux·网络·c++·vscode·网络协议·udp
此生决int1 小时前
Linux系统(07)——进程概念(下)
linux·运维·服务器