文章目录
- 前言
- `cpp-http` 库简介
- `cpp-http` 库使用介绍
- http 客户端搭建步骤
- http 服务端搭建步骤
- `cpp-http` 库示例
- 服务端实现
- 客户端实现
- 参考资料
前言
最近在做的一个项目需要使用到 HTTP 协议,在网上查了很久,也结合了之前学长做的项目,发现 cpp-http
库使用挺多的,所以就边学边做笔记,顺便分享出来(我觉得知识之所以叫知识,就是因为它能被记录并能被传播,为人所知,为人所识)。
我准备把这个学习记录做成一个系列文章,这个系列里面,我会介绍从 HTTP
协议诞生到目前的一些大概情况,然后会简单介绍一下目前 C/C++
下面用的比较广泛的 HTTP
库,之后就是讲解 cpp-http
库的简单使用、 cpp-http
库的实现,最后如果时间允许的话可以自己手码一个 C 语言的 http
用来作总结。
反正 flag 在这里先立下了,这篇文章是这个系列的第一篇,但是并不是按顺序来的第一篇,这里讲的是 cpp-http
库的简单使用,按顺序来看的话应该是系列里面的第三篇或者第二篇。如果大家目前对 HTTP 协议基础知识有需求的话还请移步其他博主的文章,之后我也会陆续把各个模块补上(下一篇可能是 cpp-http
库的实现部分),敬请大家期待。
另:本人目前是大学生,新人一枚,文章之中难免会有纰漏,还请大家多多包涵,不吝赐教。
cpp-http
库简介
-
cpp-http
库是什么?
cpp-http
由是一位国外程序员 yhirose 在 github 上面开源的一个 C++ 项目,同时这个项目得到了世界各地程序员的支持,目前包括原作者在内共有 104 位贡献者,拥有 5.7kStar
和 1.2kFork
,是目前使用较为广泛的一个 C++ http库之一。 -
cpp-http
项目地址
https://github.com/yhirose/cpp-httplib
如果网络不好不能访问 github 的话,这里是国内一位程序员搬运到 gitee 上面的项目,内容都是一样的:
https://gitee.com/zhangkt1995/cpp-httplib?_from=gitee_search -
关于它的简介,这里引用原作者的话:
A C++11 single-file header-only cross platform HTTP/HTTPS library.
It’s extremely easy to setup. Just include the httplib.h file in your code!
NOTE: This is a multi-threaded ‘blocking’ HTTP library. If you are looking for a ‘non-blocking’ library, this is not the one that you want.
翻译过来就是:
一个只有头文件的跨平台
HTTP/HTTPS
库。简单易用,只需要包含头文件
httplib,h
即可。注意:这个库是一个多线程阻塞式
Http
库,如果您需要的是一个非阻塞式的库,这个库并不适合您。
cpp-http
库使用介绍
网上大多数教程都只介绍了使用 cpp-http
库的 GET 请求使用,没有 POST 请求的(其实两者使用一模一样),在这里我也加上了 POST 请求的部分。
http 客户端搭建步骤
- 组织http协议格式的请求数据
- 搭建tcp客户端
- 发送组织好的http请求数据
- 等待服务端响应,接收响应数据
- 对响应数据的解析
http 服务端搭建步骤
- 搭建tcp服务端
- 等待接收客户端发送的数据
- 按照 http 协议格式,对数据进行解析(格式按照: 请求方法 URL 协议版本\r\n 头部\r\n 正文)
- 根据请求的资源路径以及查询字符串以及正文,进行业务处理
- 组织http协议格式的响应,返回给客户端(协议版本 状态码 描述\r\n 头部)
cpp-http
库示例
这里我们先用个 demo 来看看 cpp-http
库怎么使用:
首先先建立工程,目录如下:
▶ tree ../http -L 1
../http
├── client.cpp # 在这里编辑客户端的代码
├── httplib.h # 这是 cpp-http 库的头文件,server和client都需要包含它
└── server.cpp # 在这里编辑服务端的代码
0 directories, 5 files
其中 httplib.h
就是 cpp-http
库的所有内容了,就是这一个头文件;server.cpp
是我们自己的服务端程序, client.cpp
是我们自己的客户端程序。
服务端实现
- 代码。相关的解释都在注释里面
#include <iostream>
#include "httplib.h"
using Request = httplib::Request;
using Response = httplib::Response;
using Server = httplib::Server;
using Params = httplib::Params;
// get 请求中对 "/go" 的处理
void get_go(const Request& req, Response& res)
{
// 设置 "/go" 请求返回的内容
res.set_content("<html><h1>I'm the king of the world!</h1></html>", "text/html");
std::cout << "Received a request of get [go]." << std::endl;
}
int main(int argc, char const *argv[])
{
// 搭建服务端
Server srv;
// 这里注册用于处理 get 请求的函数,当收到对应的get请求时,程序会执行对应的函数
srv.Get("/go", get_go); // 可能相比 lambda 表达式,刚从 C 语言转过来的同学更熟悉这种形式,这个get调用方式也更简介易懂
// 这里注册的处理函数是 C++ 的 lambda 表达式,直接看成传入了一个指针就行了
srv.Get("/hi", [&](const Request& req, Response& res){
// 设置 get "hi" 请求返回的内容
res.set_content("<html><h1>Hello world!</h1></html>", "text/html");
std::cout << "Received a request of get [hi]." << std::endl;
});
srv.Get("/link", [&](const Request& req, Response& res){
res.set_content("<html><h1 href=https://baike.baidu.com/item/%E8%A5%BF%E5%8D%8E%E5%A4%A7%E5%AD%A6%E6%9C%BA%E5%99%A8%E4%BA%BA%E8%B6%B3%E7%90%83%E5%8D%8F%E4%BC%9A/22274030>soccer robot</h1></html>", "text/html");
std::cout << "Received a request of get [link]." << std::endl;
});
// POST 请求处理
srv.Post("/get_info", [&](const Request& req, Response& res) {
res.set_content("<html><h1>Go ahead!</h1></html>", "text/html");
std::cout << "Received a request of POST [get_info]." << std::endl;
});
// 绑定端口,启动监听
srv.listen("0.0.0.0", 12345);
return 0;
}
这里大家可能有三个疑问:
Server::Get()
、Server::Post()
注册函数的实现机制,收到请到 GET 请求后是怎么执行对应函数的- 使用
Server::listen()
启动端口的实现机制 - lambda 表达式
其中前两点我会在后面解释,关于lambda表达式的部分可能之后会出相应的文章来介绍,在此之前还请各位移步其他关于 lambda 的文章进行学习。
- 编译
cpp-http
库编译时需要使用线程库pthread
编译时使用g++
直接编译,使用gcc
编译会报错(毕竟 gcc 是为 C 设计的)
编译时使用命令:
g++ ./server.cpp -lpthread -o sever.out
- 执行
执行服务端程序:
./server.out
- 验证服务端:
- 可以通过 curl 来查看服务端返回的数据
# 示例如下 $ curl http://ip:port/source # 这里的 ip 是服务端运行所在电脑的ip,可以通过 `ip -a` 来查看 # port 就是我们在服务端里面指定监听的端口 # source 是通过 http 请求的资源,在这里就是 /hi /link /go 那三个。
最后一行 使用curl http://0.0.0.0:12345/get_info
时没有返回,并且服务器端也没有接收到数据,说明curl
直接发送/get_info
并不能用于提交 POST 请求,怎么使用 curl 提交 POST 请求等我之后有时间了再看看。
▶ curl http://0.0.0.0:12345/hi # 这里是请求 /link 资源需要的命令
<html><h1>Hello world!</h1></html>% # 这里是服务器回应的数据
▶ curl http://0.0.0.0:12345/link
<html><h1 href=https://baike.baidu.com/item/%E8%A5%BF%E5%8D%8E%E5%A4%A7%E5%AD%A6%E6%9C%BA%E5%99%A8%E4%BA%BA%E8%B6%B3%E7%90%83%E5%8D%8F%E4%BC%9A/22274030>soccer robot</h1></html>%
▶ curl http://0.0.0.0:12345/go
<html><h1>I'm the king of the world!</h1></html>%
▶ curl http://0.0.0.0:12345/get_info
- 也可以通过浏览器来查看数据:
通过在浏览器地址栏输入相应的地址来验证 http 服务端,直接在浏览器地址栏填入链接就好了:
例如我在浏览器输入如下地址http://http://0.0.0.0:12345/hi
结果如图:
- 如果一切正常的话,服务端的输出是这样:
▶ ./server.out
Received a request of get [link].
Received a request of get [link].
Received a request of get [hi].
Received a request of get [link].
Received a request of get [link].
Received a request of get [go].
Received a request of get [hi].
客户端实现
- 代码
#include <iostream>
#include <string>
#include "httplib.h"
using Client = httplib::Client;
using string = std::string;
int main(int argc, char const *argv[])
{
if (argc < 3) {
std::cout << "Please input server ip and port" << std::endl;
return 0;
}
string source; // 用于存储资源名称
// 创建客户端
// argv[1] 是从命令行传入的 ip
// argv[2] 是从命令行传入的 port
Client cli(argv[1], atoi(argv[2]));
// 这里是一个死循环,用于选择需要读取的资源
while (true) {
std::cout << "Please input the source you want"
<< "(Or use <CTRL + Z> to exit):"
<< std::endl;
// 从命令行获取下一步需要获取的资源名称
std::getline(std::cin, source, '\n');
// std::cout << source << std::endl;
// GET、POST 请求的返回值
std::shared_ptr<httplib::Response> res;
if (source == "/get_info") {
// POST 请求
res = cli.Post(source.c_str());
} else {
// GET 请求
res = cli.Get(source.c_str());
}
if (res) {
if (res->status == 200) { // 获取对应资源成功,输出获取到的信息
std::cout << "Recved response from server: \n\r"
<< res->body
<< "\n\r" << std::endl;
} else{ // 获取资源失败,输出 http 状态码
std::cout << "Request error: " << res->status
<< "\n\r" << std::endl;
}
} else { // 发送失败(多半是配置问题)
std::cout << "Request GET failed.\n\r" << std::endl;
}
sleep(1); // 线程休眠
}
return 0;
}
- 编译
- 编译命令和 编译 server.cpp 是一样的,不过参数要改成
client.cpp
:
g++ ./client.cpp -lpthread -o client.out
- 编译完成后的目录:
▶ tree -L 1 ../http
../http
├── client.cpp
├── client.out
├── httplib.h
├── server
├── server.cpp
└── server.out
0 directories, 6 files
- 执行
- 执行服务端和客户端程序:
./server.out
- 执行客户端程序:
▶ ./client.out 0.0.0.0 12345
Please input the source you want(Or use <CTRL + Z> to exit):
/go
Recved response from server:
<html><h1>I'm the king of the world!</h1></html>
Please input the source you want(Or use <CTRL + Z> to exit):
/link
Recved response from server:
<html><h1 href=https://baike.baidu.com/item/%E8%A5%BF%E5%8D%8E%E5%A4%A7%E5%AD%A6%E6%9C%BA%E5%99%A8%E4%BA%BA%E8%B6%B3%E7%90%83%E5%8D%8F%E4%BC%9A/22274030>soccer robot</h1></html>
Please input the source you want(Or use <CTRL + Z> to exit):
/hi
Recved response from server:
<html><h1>Hello world!</h1></html>
Please input the source you want(Or use <CTRL + Z> to exit):
- 程序执行结果:
参考资料
- 如何用chrome浏览器发送POST请求:https://www.hm1006.cn/archives/chromepost
- 【项目】cpp-httplib库的原理:https://blog.csdn.net/weixin_43939593/article/details/104263043
- 推荐一个比较好用的c++版本http协议库-cpp-httplib:https://blog.csdn.net/wuquan_1230/article/details/117690607