cpp-httplib的下载和使用

cpp-httplib的下载和使用

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.youkuaiyun.com/m0_73168361/article/details/137931807
                      或者前往cpp-httplib开源仓库中详细查看httplib的使用GitCode - 全球开发者的开源社区,开源代码托管平台

                      cpp-httplib的下载和使用-优快云博客

                      ### 如何在Visual Studio中下载安装cpp-httplib库 #### 安装依赖项 为了顺利使用`cpp-httplib`,建议通过vcpkg来管理第三方库。这可以简化依赖管理跨平台支持。 ```bash vcpkg install cpp-httplib ``` 此命令会自动处理所有必要的依赖关系并安装最新版本的`cpp-httplib`[^2]。 #### 配置项目设置 确保项目的附加包含目录已正确配置以便能够找到`httplib.h`头文件。具体操作是在Visual Studio中打开项目属性页,在C/C++ -> 常规 -> 附加包含目录里加入vcpkg提供的路径[^3]: ``` $(VCPKG_ROOT)\installed\x64-windows\include ``` 这里假设目标架构为x64;如果是其他架构,则需相应调整路径。 #### 正确导入头文件顺序 当同时使用Windows API其他外部库时,注意头文件的加载顺序非常重要。对于`cpp-httplib`而言,应该先于`<Windows.h>`之前引入`httplib.h`以防止潜在冲突或编译错误[^1]: ```cpp #include <httplib/httplib.h> #include <windows.h> int main() { httplib::Server svr; // Your server setup code here return 0; } ``` #### 测试服务器实例化 创建简单的HTTP服务器可以帮助验证安装是否成功以及环境配置无误: ```cpp #include <iostream> #include <httplib/httplib.h> // Ensure this is before Windows.h using namespace std; void simple_http_server_example() { httplib::Server svr; svr.Get("/hello", [](const httplib::Request& req, httplib::Response& res) { res.set_content("Hello World!", "text/plain"); }); cout << "Starting HTTP Server on port 8080..." << endl; svr.listen("localhost", 8080); } int main(){ try{ simple_http_server_example(); }catch(const exception &ex){ cerr<<"Error starting http server:"<< ex.what()<<endl; return EXIT_FAILURE; } return EXIT_SUCCESS; } ``` 上述代码展示了如何建立一个监听本地地址8080端口的基础Web服务,并响应来自根路径下的GET请求返回字符串"Hello World!"作为回应体内容。
                      评论
                      添加红包

                      请填写红包祝福语或标题

                      红包个数最小为10个

                      红包金额最低5元

                      当前余额3.43前往充值 >
                      需支付:10.00
                      成就一亿技术人!
                      领取后你会自动成为博主和红包主的粉丝 规则
                      hope_wisdom
                      发出的红包
                      实付
                      使用余额支付
                      点击重新获取
                      扫码支付
                      钱包余额 0

                      抵扣说明:

                      1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
                      2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

                      余额充值