一般来说,C++的项目多是偏底层,不怎么需要跟http打交道,但有时候又需要在C++后端项目中加入一些简单 http接口,比如游戏运营服务器,金融交易监控服务等。

但是传统的实现方法比如采用libcurl,asio等较为重型的框架来做有没有必要,因此,这里采用​​mongoose​​这个库来实现基本的httpserver和httpclient功能,非常简单,包含一个h文件,一个cpp文件到工程中就行了,无需编译,无需链接库。

本文实现了一个project,将mongoose中提供的http相关api封装成了httpserver类和httpclient类,方便调用,目录结构如下:


├─common     ├─mongoose.h     └─mongoose.cpp ├─httpclient     ├─http_client.h     ├─http_client.cpp     └─main.cpp └─httpserver     └─web        └─index.html     ├─http_server.h     ├─http_server.cpp     └─main.cpp


编译环境:win10,vs2015, C++11 (其实是跨平台的)


http_server.h


#pragma once  #include <string> #include <unordered_map> #include <functional> #include "../common/mongoose.h"  // 定义http返回callback typedef void OnRspCallback(mg_connection *c, std::string); // 定义http请求handler using ReqHandler = std::function<bool (std::string, std::string, mg_connection *c, OnRspCallback)>;  class HttpServer { public:     HttpServer() {}     ~HttpServer() {}     void Init(const std::string &port); // 初始化设置     bool Start(); // 启动httpserver     bool Close(); // 关闭     void AddHandler(const std::string &url, ReqHandler req_handler); // 注册事件处理函数     void RemoveHandler(const std::string &url); // 移除时间处理函数     static std::string s_web_dir; // 网页根目录      static mg_serve_http_opts s_server_option; // web服务器选项     static std::unordered_map<std::string, ReqHandler> s_handler_map; // 回调函数映射表  private:     // 静态事件响应函数     static void OnHttpEvent(mg_connection *connection, int event_type, void *event_data);     static void HandleEvent(mg_connection *connection, http_message *http_req);     static void SendRsp(mg_connection *connection, std::string rsp);      std::string m_port;    // 端口     mg_mgr m_mgr;          // 连接管理器 };



http_server.cpp


#include <utility> #include "http_server.h"  void HttpServer::Init(const std::string &port) {     m_port = port;     s_server_option.enable_directory_listing = "yes";     s_server_option.document_root = s_web_dir.c_str();     // TODO:其他设置 }  bool HttpServer::Start() {     mg_mgr_init(&m_mgr, NULL);     mg_connection *connection = mg_bind(&m_mgr, m_port.c_str(), OnHttpEvent);     if (connection == NULL)         return false;     mg_set_protocol_http_websocket(connection);      printf("starting http server at port: %s\n", m_port.c_str());     // loop     while (true)         mg_mgr_poll(&m_mgr, 500); // ms      return true; }  void HttpServer::OnHttpEvent(mg_connection *connection, int event_type, void *event_data) {     http_message *http_req = (http_message *)event_data;     switch (event_type)     {     case MG_EV_HTTP_REQUEST:         HandleEvent(connection, http_req);         break;     default:         break;     } }  static bool route_check(http_message *http_msg, char *route_prefix) {     if (mg_vcmp(&http_msg->uri, route_prefix) == 0)         return true;     else         return false;      // TODO: 还可以判断 GET, POST, PUT, DELTE等方法     //mg_vcmp(&http_msg->method, "GET");     //mg_vcmp(&http_msg->method, "POST");     //mg_vcmp(&http_msg->method, "PUT");     //mg_vcmp(&http_msg->method, "DELETE"); }  void HttpServer::AddHandler(const std::string &url, ReqHandler req_handler) {     if (s_handler_map.find(url) != s_handler_map.end())         return;      s_handler_map.insert(std::make_pair(url, req_handler)); }  void HttpServer::RemoveHandler(const std::string &url) {     auto it = s_handler_map.find(url);     if (it != s_handler_map.end())         s_handler_map.erase(it); }  void HttpServer::SendRsp(mg_connection *connection, std::string rsp) {     // 必须先发送header     mg_printf(connection, "%s", "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");     // 以json形式返回     mg_printf_http_chunk(connection, "{ \"result\": %s }", rsp.c_str());     // 发送空白字符快,结束当前响应     mg_send_http_chunk(connection, "", 0); }  void HttpServer::HandleEvent(mg_connection *connection, http_message *http_req) {     std::string req_str = std::string(http_req->message.p, http_req->message.len);     printf("got request: %s\n", req_str.c_str());      // 先过滤是否已注册的函数回调     std::string url = std::string(http_req->uri.p, http_req->uri.len);     std::string body = std::string(http_req->body.p, http_req->body.len);     auto it = s_handler_map.find(url);     if (it != s_handler_map.end())     {         ReqHandler handle_func = it->second;         handle_func(url, body, connection, SendRsp);     }      // 其他请求     if (route_check(http_req, "/")) // index page         mg_serve_http(connection, http_req, s_server_option);     else if (route_check(http_req, "/api/hello"))      {         // 直接回传         SendRsp(connection, "welcome to httpserver");     }     else if (route_check(http_req, "/api/sum"))     {         // 简单post请求,加法运算测试         char n1[100], n2[100];         double result;          /* Get form variables */         mg_get_http_var(&http_req->body, "n1", n1, sizeof(n1));         mg_get_http_var(&http_req->body, "n2", n2, sizeof(n2));          /* Compute the result and send it back as a JSON object */         result = strtod(n1, NULL) + strtod(n2, NULL);         SendRsp(connection, std::to_string(result));     }     else     {         mg_printf(             connection,             "%s",             "HTTP/1.1 501 Not Implemented\r\n"             "Content-Length: 0\r\n\r\n");     } }  bool HttpServer::Close() {     mg_mgr_free(&m_mgr);     return true; }



index.html


<!DOCTYPE html> <html> <head>   <title>RESTful API demo</title>  <script src="//code.jquery.com/jquery-1.11.0.min.js"></script> <script type="text/javascript">      $(document).ready(function(){      $("button").click(function(){         $.get("/api/hello",function(data, status){             console.log("get rsp: ", data);             $('#result1').html(data);         });        });     });      $(document).on('keyup', '#n1, #n2', function() {       $.ajax({         url: '/api/sum',         method: 'POST',         dataType: 'json',         data: { n1: $('#n1').val(), n2: $('#n2').val() },         success: function(json) {           console.log("post rsp: ", json);           $('#result2').html(json.result);         }       });     });  </script> </head> <body>     <h1>c++ httpserver demo</h1>      <p>       front end request     </p>      <h2>GET</h2>     <div>         <button id="btn">get request</button>     </div>     <div>      <label>Result1:</label> <span id="result1"> </span>     </div>      <h2>POST</h2>     <div>       <label>Number 1:</label> <input type="text" id="n1" />     </div>     <div>       <label>Number 2:</label> <input type="text" id="n2" />     </div>     <div>      <label>Result2:</label> <span id="result2"> </span>     </div>  </body> </html>



main.cpp


#include <iostream> #include <memory> #include "http_server.h"  // 初始化HttpServer静态类成员 mg_serve_http_opts HttpServer::s_server_option; std::string HttpServer::s_web_dir = "./web"; std::unordered_map<std::string, ReqHandler> HttpServer::s_handler_map;  bool handle_fun1(std::string url, std::string body, mg_connection *c, OnRspCallback rsp_callback) {     // do sth     std::cout << "handle fun1" << std::endl;     std::cout << "url: " << url << std::endl;     std::cout << "body: " << body << std::endl;      rsp_callback(c, "rsp1");      return true; }  bool handle_fun2(std::string url, std::string body, mg_connection *c, OnRspCallback rsp_callback) {     // do sth     std::cout << "handle fun2" << std::endl;     std::cout << "url: " << url << std::endl;     std::cout << "body: " << body << std::endl;      rsp_callback(c, "rsp2");      return true; }  int main(int argc, char *argv[])  {     std::string port = "7999";     auto http_server = std::shared_ptr<HttpServer>(new HttpServer);     http_server->Init(port);     // add handler     http_server->AddHandler("/api/fun1", handle_fun1);     http_server->AddHandler("/api/fun2", handle_fun2);     http_server->RemoveHandler("/api/fun3");     // http_server->RemoveHandler("/api/fun3");     http_server->Start();          return 0; }




  • 服务器支持host静态页面资源,也支持rest api调用
  • 需要手动设置loop polling的时间间隔
  • 可以自定义静态页面根路径,注册和解注册自定义api函数回调
  • 某些变量必须声明定义成全局或者静态变量

http客户端

http_client.h


#pragma once #include <string> #include <functional> #include "../common/mongoose.h"  // 此处必须用function类,typedef再后面函数指针赋值无效 using ReqCallback = std::function<void (std::string)>;  class HttpClient { public:     HttpClient() {}     ~HttpClient() {}      static void SendReq(const std::string &url, ReqCallback req_callback);     static void OnHttpEvent(mg_connection *connection, int event_type, void *event_data);     static int s_exit_flag;     static ReqCallback s_req_callback; };



http_client.cpp


#include "http_client.h"  // 初始化client静态变量 int HttpClient::s_exit_flag = 0; ReqCallback HttpClient::s_req_callback;  // 客户端的网络请求响应 void HttpClient::OnHttpEvent(mg_connection *connection, int event_type, void *event_data) {     http_message *hm = (struct http_message *)event_data;     int connect_status;      switch (event_type)      {     case MG_EV_CONNECT:         connect_status = *(int *)event_data;         if (connect_status != 0)          {             printf("Error connecting to server, error code: %d\n", connect_status);             s_exit_flag = 1;         }         break;     case MG_EV_HTTP_REPLY:     {         printf("Got reply:\n%.*s\n", (int)hm->body.len, hm->body.p);         std::string rsp = std::string(hm->body.p, hm->body.len);         connection->flags |= MG_F_SEND_AND_CLOSE;         s_exit_flag = 1; // 每次收到请求后关闭本次连接,重置标记                  // 回调处理         s_req_callback(rsp);     }         break;     case MG_EV_CLOSE:         if (s_exit_flag == 0)          {             printf("Server closed connection\n");             s_exit_flag = 1;         };         break;     default:         break;     } }   // 发送一次请求,并回调处理,然后关闭本次连接 void HttpClient::SendReq(const std::string &url, ReqCallback req_callback) {     // 给回调函数赋值     s_req_callback = req_callback;     mg_mgr mgr;     mg_mgr_init(&mgr, NULL);     auto connection = mg_connect_http(&mgr, OnHttpEvent, url.c_str(), NULL, NULL);     mg_set_protocol_http_websocket(connection);      printf("Send http request %s\n", url.c_str());      // loop     while (s_exit_flag == 0)         mg_mgr_poll(&mgr, 500);      mg_mgr_free(&mgr); }



  • client每次请求都是一个独立的请求
  • 请求函数中加入回调用于处理网络返回

测试

可以用浏览器、或者其他工具提交url,查看网络请求返回

GET

请求

http://localhost:7999/api/hello

结果

{ "result": welcome to httpserver }

POST

请求

http://localhost:7999/api/sum?n1=20&n2=18

结果

{ "result": 38 }

源码

csdn:​​demo​

github: ​​demo​