cpp-httplib的下载和使用

news2024/11/9 4:57:03

cpp-httplib的下载和使用

  • 1.httplib 简介
  • 2. httplib 使用
    • 2.1 协议接口
    • 2.2 双端接口
    • 2.3 实际使用
  • 3. 对Server中的Handler回调函数进行分析
  • 4. 最后

1.httplib 简介

cpp-httplib(也称为 httplib)是一个基于 C++ 的轻量级 HTTP 框架,它提供了简单易用的 API,用于创建 HTTP 服务器和客户端。并且cpp-httplib是采用的select多路复用+线程池的方式响应请求,读取请求,放到业务线程池对于业务处理,同时还支持切换多路复用的方式。默认情况下,由于其广泛的支持,使用了select系统调用。如果你希望cpp-httplib使用poll,可以通过设置CPPHTTPLIB_USE_POLL实现。

2. httplib 使用

httplib 的安装也很简单,直接克隆库中的 httplib.h 到项目中即可(https://github.com/yhirose/cpp-httplib)。接口有点多,我先写出常用的接口声明和类声明(另外,该编译该库最好使用 g++7.3)。

2.1 协议接口

首先 http 报文内会包含:

  • 首部:请求方法(GET、POST)、URL(<protocol>://<username>:<password>@<host>:<port>/<path>?<params>#<fragment>)、协议版本
  • 报头:key-value\r\n
  • 空行:\r\n
  • 正文:请求的数据或者返回的数据

而 http 报文还会分为请求报文(Request)和响应报文(Response),因此在 httplib 中存在以下两个类(只是作为归纳的两个简化的类,实际声明可能有很大的不同)。

//Request 类
struct MultipartFormDataMap
{
    std::string name; //表单字段的名称, 用于标识表单数据
    std::string content; //文件内容
    std::string filename; //文件名称
    std::string content_type; //文件类型
};

struct Request
{
    //(1)请求行
    std::string method; //请求方法
    std::string path; //资源路径(不是所有的 URL, 通常是域名之后, 查询字符之前)
    Params params; //查询字符串
    std::string version; //协议版本
    
    //(2)请求头部
    Headers headers; //头部

    //(3)请求正文
    std::string body; //正文
    MultipartFormDataMap files; //保存客户端上传的文件信息
    Ranges ranges; //指定要获取资源范围, 会根据 Range 字段指定的范围来返回相应的资源内容, 一旦中断后重连, 就会从中断的位置继续下载文件, 而不需要重新下载整个文件
    
    bool has_header(const char *key) const;
   	//检查请求头部是否包含指定键名的头部字段, 参数 key 是要检查的头部字段的键名
    //如果请求头部中包含指定键名的头部字段, 则返回 true 否则返回 false
    
    std::string get_header_value(const char *key, size_t id = 0) const;
    //获取请求头部中指定键名的报头字段的值, 参数 key 是要获取的字段键名, 参数 id 是可选的
    //若头部字段有多个同名键名, 则可通过 id 来指定获取其中一个
    //如果请求头部中存在指定键名的头部字段, 则返回该字段的值, 否则返回空字符串
    
    void set_header(const char *key, const char *val);
    //用于设置请求头部中的一个新的头部字段或更新已存在的头部字段
    
    bool has_file(const char *key) const;
    //用于检查请求中是否包含指定键名的文件字段(表单 name)
    
    MultipartFormData get_file_value(const char *key) const;
    //用于获取请求中指定键名的文件字段的值(表单 name)
};

//Response 类
struct Response
{
    //(1)状态行
    std::string version;    //协议版本
    int status = -1;        //状态码
    std::string reason;     //状态描述
    
    //(2)响应头部
    Headers headers;        //响应头部
    
    //(3)响应正文
    std::string body;       //响应体
    std::string location;   //重定向地址

    //设置响应头部
    void set_header(const char *key, const char *val);

    //设置响应正文
    void set_content(const std::string &s, const char *content_type);
};

2.2 双端接口

//Server 类
class Server
{
public:
	//1.路由方法设置
    //请求路由:Handler 是函数类型, 无返回值, 传入请求, 带出响应
    using Handler = std::function<void(const Request&, Response&)>;
    //请求路由数组:众多 Handler 类型函数的列表 std::regex 是正则表达式, 用于填充和路由方法匹配 http 请求资源路径(实际上就是请求中的 path), 以后就可以根据用户端请求的路由选择对应的路由函数进行请求处理(若没有对端就会收到 404)
    using Handlers = std::vector<std::pair<std::regex, Handler>>;
    
    //(2)线程池设置
    //线程池成员, 其工作就是接受请求, 解析请求, 在映射表中查看是否有可以执行方法, 每接到链接请求, 就会把新的客户端连接抛入线程池中,
    std::function<TaskQueue* (void)> new_task_queue;

    //(3)请求方法设置, 通过下面接口, 针对不同的请求, 把路由方法添加到 Handlers 中
    //注册处理 GET 请求的处理程序
    Server &Get(const std::string &pattern, Handler handler);
    //注册处理 POST 请求的处理程序
    Server &Post(const std::string &pattern, Handler handler);
    //注册处理 PUT 请求的处理程序
    Server &Put(const std::string &pattern, Handler handler);
    //注册处理 PATCH 请求的处理程序
    Server &Patch(const std::string &pattern, Handler handler);
    //注册处理 DELETE 请求的处理程序
    Server &Delete(const std::string &pattern, Handler handler);
    //注册处理 OPTIONS 请求的处理程序
    Server &Options(const std::string &pattern, Handler handler);

    //4.启动 HTTP 服务器
    bool listen(const char* host, int port, int socket_flags = 0);
};
//Client 类
class Client
{
public:
    //传入对端服务器的 ip 和 port
    Client(const std::string &host, int port);
    
	//发送 GET 请求到指定路径
	Result Get(const char *path);
	
    //发送 GET 请求到指定路径, 可附带头部信息, 并返回结果
    Result Get(const char *path, const Headers &headers);

    //发送 POST 请求到指定路径, 包含纯文本主体、内容长度、内容类型
    Result Post(const char *path, const char *body, size_t content_length, const char *content_type);

    //发送 POST 请求到指定路径,包含多部分表单数据项
    Result Post(const char *path, const MultipartFormDataItems &items); //最后一个参数是一个文件数组
};

2.3 实际使用

接下来我们尝试使用一下上述的接口。

先写一个自动化脚本,方便多次代码编译。

# makefile

all:http_server http_client

http_server:http_server.cpp
	g++ -o $@ $^ -std=c++11

http_client:http_client.cpp

.PHONY:clean
clean:
	rm -fr http_server http_client

测试server端

#include <iostream>
#include "./1.Origin_source/Comm/httplib.h"
#include <string>
#include <cstdlib>

int main(int argc, char const* argv[])
{
    using namespace httplib;
    Server svr;

    svr.Get(R"(/my_get)", [](const Request& req, Response& res)
        {
            std::cout << "a get request" << std::endl;
            res.set_content("hello linux", "text/plain");
            res.status = 200;
        }
    );

	  // 匹配请求路径与正则表达式并提取其捕获值
	  svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
	    auto numbers = req.matches[1]; // 获取正则表达式中的内容
	    res.set_content(numbers, "text/plain");
	  });
  
    svr.Post(R"(my_post)", [](const Request& req, Response & res)
        {
            std::cout << "a post request" << std::endl;
            auto ret = req.has_file("file");
            if (!ret)
            {
                std::cout << "not file upload!" << std::endl;
                res.status = 404;
                return;
            }

            const auto& file = req.get_file_value("file");
            std::cout << "file.name:" << file.name << std::endl;
            std::cout << "file.filename:" << file.filename << std::endl; 
            std::cout << "file.content_type:" << file.content_type << std::endl; 
            std::cout << "file.content:" << file.content << std::endl; 

            std::string message = "This file is ";
            message += file.filename;
            message += "-";
            message += file.content_type;
            message += "\n";
            message += file.content;

            res.set_content(message, "text/plain");
            res.status = 200;
        }
    );

    svr.listen("0.0.0.0", 8080);
    return 0;
}

在这里插入图片描述
观察运行结果

#运行结果
$ ./http_server 选定的端口号
a get request.

测试POST接口,看看是否可以上传文件。

<!-- test.html -->

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>File Upload</title>
</head>
<body>
    <form action="http://这里填您的服务器公网ip:这里填您给服务端代码绑定的port/my_post" method="post" enctype="multipart/form-data">
        <input type="file" name="file" id="file">
        <input type="submit" value="提交文件">
    </form>
</body>
</html>

先将这个网页写到text.txt文本中,再通过修改后缀的方式进行生成网页,并打开,将提前写好的后缀并为.txt的文本中写入一些数据进行打开。

在这里插入图片描述

运行结果
a post request
file.name:file
file.filename:my_post.txt
file.content_type:text/plain
file.content:hello,this is a post request

对客户端进行编写测试


//较完整的 http_client.cpp
#include <iostream>
#include <cstdlib>
#include <fstream>
#include "./1.Origin_source/Comm/httplib.h"

int main(int argc, char const* argv[]) {
    using namespace httplib;
    Client cli(argv[1], atoi(argv[2]));

    //1.发送 GET 请求
    auto res_get = cli.Get("/my_get");
    if (res_get->status == 200) {
        std::cout << "get success" << std::endl;
        std::cout << "返回响应的状态码: " << res_get->status << std::endl;
        std::cout << "返回响应具体内容: " << res_get->body << std::endl;
    } else {
        std::cout << "get error" << std::endl;
    }

    //2.发送 POST 请求
    //读取文件内容
    std::ifstream file("./example.txt", std::ios::binary);
    if (!file.is_open()) {
        std::cout << "open error" << std::endl;
        return 1;
    }

    //设置content内容
    std::string file_content = "hello linux";

    MultipartFormData item;
    item.name = "file"; //表单字段名
    item.filename = "example.txt"; //作为文件名
    item.content = file_content.c_str(); //文件内容
    item.content_type = "text/plain"; //文件格式
    MultipartFormDataItems items;
    items.push_back(item);

    //发送 POST 请求上传文件, 并且返回 res 响应
    auto res = cli.Post("/my_post", items);

    if (res && res->status == 200) { //注意 res 其实就是一个指向响应的智能指针
        std::cout << "port success" << std::endl;
        std::cout << "返回响应的状态码: " << res_get->status << std::endl;
        std::cout << "返回响应具体内容: " << res->body << std::endl;
    } else {
        std::cout << "port error" << std::endl;
    }

    return 0;
}

在这里插入图片描述

3. 对Server中的Handler回调函数进行分析

这里我们再写测试Server的时候是把svr.listen()写到了最后一行,我们要知道编译器执行发方式是顺序执行,也就是说编译器执行代码的顺序是从上往下指向的(函数跳转这里我们不考虑),而我们的get和post方法是写在了listen监听套接字的起那面,那么也就是说注定get和post方法会在创建listen套接字之前就已经执行完毕了。那么大家再第一次接触httplib的时候会不会有这样的困惑:就是不应该是创建好监听套接字后才开始执行get和post方法吗?这样才符合只要有客户端来了请求就调用get或者post方法。

所以我们就要看看server到底是怎么实现get和post方法的。

inline Server &Server::Get(const char *pattern, Handler handler) {
  get_handlers_.push_back(
      std::make_pair(std::regex(pattern), std::move(handler)));
  return *this;
}

inline Server &Server::Post(const char *pattern, Handler handler) {
  post_handlers_.push_back(
      std::make_pair(std::regex(pattern), std::move(handler)));
  return *this;
}

从上面看,post和get都是都是调用的post_handlers_.push_back()插入到一个容器里面的。

Handlers get_handlers_;
using Handlers = std::vector<std::pair<std::regex, Handler>>;

从这里我们可以看出来post_handlers_其实就是一个vector容器。于是这里我们就恍然大悟了,原来是httplib再指向到get和post方法的时候是将pattern和handler回调函数进行了一种映射存储起来了。也就是说在没有创建好listen监听套接字的时候,httplib已经将路径和需要执行的函数功能建立了映射关系。当创建好套接字后,一旦客户来了请求,那么就会去这样映射表中进行查询回调函数进行回调,然后返回执行结果。

所以综上所述,上面的执行一个个的get和post方法其实就是再创建一个个的映射关系。客户端进行请求其实就是再这张映射表中查询是否有相关的映射值。

4. 最后

本篇章是在原作者的基础上稍加上了我自己的对源码的理解,具体细节请看原作者内容源地址https://blog.csdn.net/m0_73168361/article/details/137931807
或者前往cpp-httplib开源仓库中详细查看httplib的使用https://gitcode.com/gh_mirrors/cp/cpp-httplib/overview?utm_source=artical_gitcode&index=bottom&type=card&webUrl

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2129713.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

统一建模语言UML之类图(Class Diagram)(表示|关系|举例)

文章目录 1.UML2.Class Diagram2.1 类图的表示2.2 类间的关系2.2.1 关联2.2.2 聚合2.2.3 组合2.2.4 泛化&#xff08;继承&#xff09;2.2.5 实现&#xff08;接口实现&#xff09;2.2.6 依赖 2.3 类图的作用 参考&#xff1a;Class Diagram | Unified Modeling Language (UML)…

2024/9/12 数学“回头看”之R(a)与R(a※)、分布函数、概率密度的特点

注意&#xff01;这是充分必要条件。 分布函数性质 概率密度性质&#xff1a;

如何使用Jmeter关联influxDB?

一、添加"添加后端监听器" 二、后端监听器实现选择&#xff0c;"org. apache. jmeter. visualizers. backend. influxdb.InfluxdbBackendlistenerClient" 三、修改"influxdbUrl&#xff1a;自己的主机、application:取一个项目名" 四、influxDB&…

SAP B1 学习笔记 - 易混淆字段名(持续更新中)

背景 在 SAP B1 的单据中&#xff0c;由于同一单据时常对应着多个后台表单&#xff0c;且后台表单内包含的字段信息往往远大于单据显示出来的&#xff0c;在配置时经常出现多个字段混淆、无系统信息提示字段名模糊的情况&#xff0c;这里总结常见的易混淆难查找的后台字段名。…

【MySQL】MySQL表的增删改查(进阶篇)——之查询操作(超级详解)

前言&#xff1a; &#x1f31f;&#x1f31f;本期讲解关于MySQL表增删查改进阶篇&#xff0c;希望能帮到屏幕前的你。 &#x1f308;上期博客在这里&#xff1a;http://t.csdnimg.cn/8SiWF &#x1f308;感兴趣的小伙伴看一看小编主页&#xff1a;http://t.csdnimg.cn/8SiWF ​…

SpringBoot父子工程搭建

SpringBoot父子工程搭建 1、父工程 1.1、创建父工程 1.2、移除无用文件 1.3、修改pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XML…

循环节,CF 314B - Sereja and Periods

目录 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 二、解题报告 1、思路分析 2、复杂度 3、代码详解 一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 314B - Sereja and Periods 二、解题报告 1、思路分析 如果 b 个 a 中出…

【Python报错已解决】AttributeError: ‘str‘ object has no attribute ‘read‘

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 前言一、问题描述1.1 报错示例1.2 报错分析1.3 解决思路 二、解决方法2.1 方法一&#xff1a;直接使用字符串2.2 步骤…

DWI扩散磁共振成像和结构连接组学指南

扩散磁共振成像和结构连接组学指南 引言流程概述扩散磁共振成像(dMRI)dMRI基础ADC&#xff08; apparent diffusion coefficient, 表观扩散系数&#xff09;MD&#xff08;mean diffusivity, 平均扩散率&#xff09;FA&#xff08; fractional anisotropy, 分数各向异性&#x…

安装FTP服务器教程

一。安装vsftpd yum install vsftpd 二。修改配置文件&#xff0c;匿名账户具有访问&#xff0c;上传和创建目录的权限 vim /etc/vsftpd/vsftpd.conf &#xff08;红色进行设置放开YES&#xff09; local_enable&#xff1a;本地登陆控制&#xff0c;no表示禁止&#xff0c;ye…

llama网络结构及源码

模型初始化 首先模型初始化&#xff0c;确定模型属性 class LLaMA(nn.Module):def __init__(self, config: LLaMAConfig) -> None:super().__init__()assert config.padded_vocab_size is not Noneself.config configself.lm_head nn.Linear(config.n_embd, config.pad…

5 模拟——59. 螺旋矩阵II ★★

5 模拟 59. 螺旋矩阵II 给你一个正整数n,生成一个包含 1 到 n2所有元素,且元素按顺时针顺序螺旋排列的nn正方形矩阵 matrix 。 示例1: 输入:n = 3 输出:[[1,2,3],[8,9,4],[7,6,5]] 示例2: 输入:n = 1 输出:[[1]] 算法设计 本题与上一题【54. 螺旋矩阵】不同,上一…

1、https的全过程

目录 一、概述二、SSL过程如何获取会话秘钥1、首先认识几个概念&#xff1a;2、没有CA机构的SSL过程&#xff1a;3、没有CA机构下的安全问题4、有CA机构下的SSL过程 一、概述 https是非对称加密和对称加密的过程&#xff0c;首先建立https链接需要经过两轮握手&#xff1a; T…

redis基本数据结构-hash

这里写自定义目录标题 1. redis的数据结构hash1.1 Hash 数据结构的特点1.2 常见命令1.3 适用示例 2. 常见业务场景2.1 用户信息存储2.1.1 场景2.1.2 优势2.1.3 解决方案2.1.4 代码实现 2.2 购物车管理2.2.1 背景2.2.2 优势2.2.3 解决方案2.2.4 代码实现 3. 注意事项&#xff1a…

使用虚拟信用卡WildCard轻松订阅POE:全面解析平台功能与订阅方式

POE&#xff08;Platform of Engagement&#xff09;是一个由Quora推出的人工智能聊天平台&#xff0c;汇集了多个强大的AI聊天机器人&#xff0c;如GPT-4、Claude、Sage等。POE提供了一个简洁、统一的界面&#xff0c;让用户能够便捷地与不同的AI聊天模型进行互动。这种平台的…

Shadertoy和desmos用来快速图像化辅助计算的好工具

Desmos适用场景解直线方程例子 Shadertoy是一个专门通过shader片段利用gpu像素着色的工具。每一帧都会执行显示区域每个像素点的着色。默认片段坐标是左下角(0,0)到右上角(像素分辨率大小)。有网页版&#xff0c;也有vscode插件版。插件版更方便.如果要验证一些图像化的计算。…

MyBatis-Plus分页查询、分组查询

目录 准备工作1. 实体类2. Mapper类3. 分页插件4. 数据 分页查询1. 使用条件构造器2. 使用自定义sql 分组查询1. 分组结果类2. 自定义sql3. 测试类 准备工作 1. 实体类 对地址字段address使用字段类型转换器&#xff0c;将List转为字符串数组保存在数据库中 package com.exa…

(web自动化测试+python)1

一.UI自动化测试介绍 1.测试化理论 UI就是指的是用户接口&#xff0c;指的是用户与电脑的接口&#xff0c;是用户界面 UI不仅仅指的是web&#xff0c;还可以指代app 我们为什么要进行自动化&#xff1f; 大量版本的回归 当新的功能出现&#xff0c;复测之间的--我们叫做回归&am…

《Diffusion Models Without Attention》CVPR2024

摘要 这篇论文探讨了在高保真图像生成领域&#xff0c;去噪扩散概率模型&#xff08;Denoising Diffusion Probabilistic Models, DDPMs&#xff09;的重要性。尽管DDPMs在捕捉复杂视觉分布方面表现出色&#xff0c;但在高分辨率图像生成上面临显著的计算挑战。现有的方法&…

动物目标检测——基于YOLOv5和树莓派4B平台

目标检测在计算机视觉领域中具有重要意义。YOLOv5&#xff08;You Only Look One-level&#xff09;是目标检测算法中的一种代表性方法&#xff0c;以其高效性和准确性备受关注&#xff0c;并且在各种目标检测任务中都表现出卓越的性能。本文将详细介绍如何在性能更强的计算机上…