[转] C++实现轻量级极简httpserver和httpclient(提供http和websocket接口)

原文地址:https://blog.csdn.net/u012234115/article/details/79596826

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

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

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


 
 
  1. ├─common
  2. ├─mongoose.h
  3. └─mongoose.cpp
  4. ├─httpclient
  5. ├─http_client.h
  6. ├─http_client.cpp
  7. └─main.cpp
  8. └─httpserver
  9. └─web
  10. └─index.html
  11. ├─http_server.h
  12. ├─http_server.cpp
  13. └─main.cpp

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

http服务器

http_server.h


 
 
  1. #pragma once
  2. #include <string>
  3. #include <string.h>
  4. #include <unordered_map>
  5. #include <unordered_set>
  6. #include <functional>
  7. #include "../common/mongoose.h"
  8. // 定义http返回callback
  9. typedef void OnRspCallback(mg_connection *c, std::string);
  10. // 定义http请求handler
  11. using ReqHandler = std::function< bool ( std:: string, std:: string, mg_connection *c, OnRspCallback)>;
  12. class HttpServer
  13. {
  14. public:
  15. HttpServer() {}
  16. ~HttpServer() {}
  17. void Init(const std::string &port); // 初始化设置
  18. bool Start(); // 启动httpserver
  19. bool Close(); // 关闭
  20. void AddHandler(const std::string &url, ReqHandler req_handler); // 注册事件处理函数
  21. void RemoveHandler(const std::string &url); // 移除时间处理函数
  22. static std:: string s_web_dir; // 网页根目录
  23. static mg_serve_http_opts s_server_option; // web服务器选项
  24. static std:: unordered_map< std:: string, ReqHandler> s_handler_map; // 回调函数映射表
  25. private:
  26. // 静态事件响应函数
  27. static void OnHttpWebsocketEvent(mg_connection *connection, int event_type, void *event_data);
  28. static void HandleHttpEvent(mg_connection *connection, http_message *http_req);
  29. static void SendHttpRsp(mg_connection *connection, std::string rsp);
  30. static int isWebsocket(const mg_connection *connection); // 判断是否是websoket类型连接
  31. static void HandleWebsocketMessage(mg_connection *connection, int event_type, websocket_message *ws_msg);
  32. static void SendWebsocketMsg(mg_connection *connection, std::string msg); // 发送消息到指定连接
  33. static void BroadcastWebsocketMsg(std::string msg); // 给所有连接广播消息
  34. static std:: unordered_set<mg_connection *> s_websocket_session_set; // 缓存websocket连接
  35. std:: string m_port; // 端口
  36. mg_mgr m_mgr; // 连接管理器
  37. };

http_server.cpp


 
 
  1. #include <utility>
  2. #include "http_server.h"
  3. void HttpServer::Init( const std:: string &port)
  4. {
  5. m_port = port;
  6. s_server_option.enable_directory_listing = "yes";
  7. s_server_option.document_root = s_web_dir.c_str();
  8. // TODO:其他http设置
  9. }
  10. bool HttpServer::Start()
  11. {
  12. mg_mgr_init(&m_mgr, NULL);
  13. mg_connection *connection = mg_bind(&m_mgr, m_port.c_str(), HttpServer::OnHttpWebsocketEvent);
  14. if (connection == NULL)
  15. return false;
  16. // for both http and websocket
  17. mg_set_protocol_http_websocket(connection);
  18. printf( "starting http server at port: %s\n", m_port.c_str());
  19. // loop
  20. while ( true)
  21. mg_mgr_poll(&m_mgr, 500); // ms
  22. return true;
  23. }
  24. void HttpServer::OnHttpWebsocketEvent(mg_connection *connection, int event_type, void *event_data)
  25. {
  26. // 区分http和websocket
  27. if (event_type == MG_EV_HTTP_REQUEST)
  28. {
  29. http_message *http_req = (http_message *)event_data;
  30. HandleHttpEvent(connection, http_req);
  31. }
  32. else if (event_type == MG_EV_WEBSOCKET_HANDSHAKE_DONE ||
  33. event_type == MG_EV_WEBSOCKET_FRAME ||
  34. event_type == MG_EV_CLOSE)
  35. {
  36. websocket_message *ws_message = (struct websocket_message *)event_data;
  37. HandleWebsocketMessage(connection, event_type, ws_message);
  38. }
  39. }
  40. // ---- simple http ---- //
  41. static bool route_check(http_message *http_msg, char *route_prefix)
  42. {
  43. if (mg_vcmp(&http_msg->uri, route_prefix) == 0)
  44. return true;
  45. else
  46. return false;
  47. // TODO: 还可以判断 GET, POST, PUT, DELTE等方法
  48. //mg_vcmp(&http_msg->method, "GET");
  49. //mg_vcmp(&http_msg->method, "POST");
  50. //mg_vcmp(&http_msg->method, "PUT");
  51. //mg_vcmp(&http_msg->method, "DELETE");
  52. }
  53. void HttpServer::AddHandler( const std:: string &url, ReqHandler req_handler)
  54. {
  55. if (s_handler_map.find(url) != s_handler_map.end())
  56. return;
  57. s_handler_map.insert( std::make_pair(url, req_handler));
  58. }
  59. void HttpServer::RemoveHandler( const std:: string &url)
  60. {
  61. auto it = s_handler_map.find(url);
  62. if (it != s_handler_map.end())
  63. s_handler_map.erase(it);
  64. }
  65. void HttpServer::SendHttpRsp(mg_connection *connection, std:: string rsp)
  66. {
  67. // 必须先发送header
  68. mg_printf(connection, "%s", "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
  69. // 以json形式返回
  70. mg_printf_http_chunk(connection, "{ \"result\": %s }", rsp.c_str());
  71. // 发送空白字符快,结束当前响应
  72. mg_send_http_chunk(connection, "", 0);
  73. }
  74. void HttpServer::HandleHttpEvent(mg_connection *connection, http_message *http_req)
  75. {
  76. std:: string req_str = std:: string(http_req->message.p, http_req->message.len);
  77. printf( "got request: %s\n", req_str.c_str());
  78. // 先过滤是否已注册的函数回调
  79. std:: string url = std:: string(http_req->uri.p, http_req->uri.len);
  80. std:: string body = std:: string(http_req->body.p, http_req->body.len);
  81. auto it = s_handler_map.find(url);
  82. if (it != s_handler_map.end())
  83. {
  84. ReqHandler handle_func = it->second;
  85. handle_func(url, body, connection, &HttpServer::SendHttpRsp);
  86. }
  87. // 其他请求
  88. if (route_check(http_req, "/")) // index page
  89. mg_serve_http(connection, http_req, s_server_option);
  90. else if (route_check(http_req, "/api/hello"))
  91. {
  92. // 直接回传
  93. SendHttpRsp(connection, "welcome to httpserver");
  94. }
  95. else if (route_check(http_req, "/api/sum"))
  96. {
  97. // 简单post请求,加法运算测试
  98. char n1[ 100], n2[ 100];
  99. double result;
  100. /* Get form variables */
  101. mg_get_http_var(&http_req->body, "n1", n1, sizeof(n1));
  102. mg_get_http_var(&http_req->body, "n2", n2, sizeof(n2));
  103. /* Compute the result and send it back as a JSON object */
  104. result = strtod(n1, NULL) + strtod(n2, NULL);
  105. SendHttpRsp(connection, std::to_string(result));
  106. }
  107. else
  108. {
  109. mg_printf(
  110. connection,
  111. "%s",
  112. "HTTP/1.1 501 Not Implemented\r\n"
  113. "Content-Length: 0\r\n\r\n");
  114. }
  115. }
  116. // ---- websocket ---- //
  117. int HttpServer::isWebsocket( const mg_connection *connection)
  118. {
  119. return connection->flags & MG_F_IS_WEBSOCKET;
  120. }
  121. void HttpServer::HandleWebsocketMessage(mg_connection *connection, int event_type, websocket_message *ws_msg)
  122. {
  123. if (event_type == MG_EV_WEBSOCKET_HANDSHAKE_DONE)
  124. {
  125. printf( "client websocket connected\n");
  126. // 获取连接客户端的IP和端口
  127. char addr[ 32];
  128. mg_sock_addr_to_str(&connection->sa, addr, sizeof(addr), MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);
  129. printf( "client addr: %s\n", addr);
  130. // 添加 session
  131. s_websocket_session_set.insert(connection);
  132. SendWebsocketMsg(connection, "client websocket connected");
  133. }
  134. else if (event_type == MG_EV_WEBSOCKET_FRAME)
  135. {
  136. mg_str received_msg = {
  137. ( char *)ws_msg->data, ws_msg->size
  138. };
  139. char buff[ 1024] = { 0};
  140. strncpy(buff, received_msg.p, received_msg.len); // must use strncpy, specifiy memory pointer and length
  141. // do sth to process request
  142. printf( "received msg: %s\n", buff);
  143. SendWebsocketMsg(connection, "send your msg back: " + std:: string(buff));
  144. //BroadcastWebsocketMsg("broadcast msg: " + std::string(buff));
  145. }
  146. else if (event_type == MG_EV_CLOSE)
  147. {
  148. if (isWebsocket(connection))
  149. {
  150. printf( "client websocket closed\n");
  151. // 移除session
  152. if (s_websocket_session_set.find(connection) != s_websocket_session_set.end())
  153. s_websocket_session_set.erase(connection);
  154. }
  155. }
  156. }
  157. void HttpServer::SendWebsocketMsg(mg_connection *connection, std:: string msg)
  158. {
  159. mg_send_websocket_frame(connection, WEBSOCKET_OP_TEXT, msg.c_str(), strlen(msg.c_str()));
  160. }
  161. void HttpServer::BroadcastWebsocketMsg( std:: string msg)
  162. {
  163. for (mg_connection *connection : s_websocket_session_set)
  164. mg_send_websocket_frame(connection, WEBSOCKET_OP_TEXT, msg.c_str(), strlen(msg.c_str()));
  165. }
  166. bool HttpServer::Close()
  167. {
  168. mg_mgr_free(&m_mgr);
  169. return true;
  170. }

main.cpp


 
 
  1. #include <iostream>
  2. #include <memory>
  3. #include "http_server.h"
  4. // 初始化HttpServer静态类成员
  5. mg_serve_http_opts HttpServer::s_server_option;
  6. std:: string HttpServer::s_web_dir = "./web";
  7. std:: unordered_map< std:: string, ReqHandler> HttpServer::s_handler_map;
  8. std:: unordered_set<mg_connection *> HttpServer::s_websocket_session_set;
  9. bool handle_fun1(std::string url, std::string body, mg_connection *c, OnRspCallback rsp_callback)
  10. {
  11. // do sth
  12. std:: cout << "handle fun1" << std:: endl;
  13. std:: cout << "url: " << url << std:: endl;
  14. std:: cout << "body: " << body << std:: endl;
  15. rsp_callback(c, "rsp1");
  16. return true;
  17. }
  18. bool handle_fun2(std::string url, std::string body, mg_connection *c, OnRspCallback rsp_callback)
  19. {
  20. // do sth
  21. std:: cout << "handle fun2" << std:: endl;
  22. std:: cout << "url: " << url << std:: endl;
  23. std:: cout << "body: " << body << std:: endl;
  24. rsp_callback(c, "rsp2");
  25. return true;
  26. }
  27. int main(int argc, char *argv[])
  28. {
  29. std:: string port = "7999";
  30. auto http_server = std:: shared_ptr<HttpServer>( new HttpServer);
  31. http_server->Init(port);
  32. // add handler
  33. http_server->AddHandler( "/api/fun1", handle_fun1);
  34. http_server->AddHandler( "/api/fun2", handle_fun2);
  35. http_server->Start();
  36. return 0;
  37. }

index.html


 
 
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>RESTful API demo </title>
  5. <script src="//code.jquery.com/jquery-1.11.0.min.js"> </script>
  6. <script type="text/javascript">
  7. // simple http
  8. $( document).ready( function(){
  9. $( "button").click( function(){
  10. $.get( "/api/hello", function(data, status){
  11. console.log( "get rsp: ", data);
  12. $( '#result1').html(data);
  13. });
  14. });
  15. });
  16. $( document).on( 'keyup', '#n1, #n2', function() {
  17. $.ajax({
  18. url: '/api/sum',
  19. method: 'POST',
  20. dataType: 'json',
  21. data: { n1: $( '#n1').val(), n2: $( '#n2').val() },
  22. success: function(json) {
  23. console.log( "post rsp: ", json);
  24. $( '#result2').html(json.result);
  25. }
  26. });
  27. });
  28. // websocket
  29. var websocket = new WebSocket( 'ws://' + location.host + '/ws');
  30. websocket.onopen = function (ev) {
  31. console.log(ev.data);
  32. };
  33. websocket.onerror = function (ev) {
  34. console.log(ev.data);
  35. };
  36. websocket.onclose = function (ev) {
  37. console.log(ev.data);
  38. };
  39. websocket.onmessage = function (ev) {
  40. console.log(ev.data);
  41. document.getElementById( "ws_text").innerHTML = ev.data;
  42. };
  43. window.onload = function () {
  44. document.getElementById( 'send_button').onclick = function (ev) {
  45. var msg = document.getElementById( 'send_input').value;
  46. websocket.send(msg);
  47. };
  48. };
  49. </script>
  50. </head>
  51. <body>
  52. <h1>c++ httpserver demo </h1>
  53. <h2>simple http </h2>
  54. <h3>GET </h3>
  55. <div>
  56. <button id="btn">get request </button>
  57. </div>
  58. <div>
  59. <label>Result1: </label> <span id="result1">  </span>
  60. </div>
  61. <h3>POST </h3>
  62. <div>
  63. <label>Number 1: </label> <input type="text" id="n1" />
  64. </div>
  65. <div>
  66. <label>Number 2: </label> <input type="text" id="n2" />
  67. </div>
  68. <div>
  69. <label>Result2: </label> <span id="result2">  </span>
  70. </div>
  71. <h2>websocket </h2>
  72. <div>
  73. <span id="ws_text">  </span>
  74. <br />
  75. <input type="text" id="send_input" />
  76. <button id="send_button">Send </button>
  77. </div>
  78. </body>
  79. </html>
  • 服务器支持host静态页面资源
  • 服务器支持前端页面的热加载
  • 服务器支持http和websocket两种方式的接口
  • 服务器支持websocket单一连接发消息和广播消息
  • 服务器支持管理websocket的session
  • 需要手动设置loop polling的时间间隔
  • 可以自定义静态页面根路径,注册和解注册自定义api函数回调
  • 某些变量必须声明定义成全局或者静态变量
  • 如果需要回传json格式,可以序列化成字符串,在前端解析

http客户端

http_client.h


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

http_client.cpp


 
 
  1. #include "http_client.h"
  2. // 初始化client静态变量
  3. int HttpClient::s_exit_flag = 0;
  4. ReqCallback HttpClient::s_req_callback;
  5. // 客户端的网络请求响应
  6. void HttpClient::OnHttpEvent(mg_connection *connection, int event_type, void *event_data)
  7. {
  8. http_message *hm = (struct http_message *)event_data;
  9. int connect_status;
  10. switch (event_type)
  11. {
  12. case MG_EV_CONNECT:
  13. connect_status = *( int *)event_data;
  14. if (connect_status != 0)
  15. {
  16. printf( "Error connecting to server, error code: %d\n", connect_status);
  17. s_exit_flag = 1;
  18. }
  19. break;
  20. case MG_EV_HTTP_REPLY:
  21. {
  22. printf( "Got reply:\n%.*s\n", ( int)hm->body.len, hm->body.p);
  23. std:: string rsp = std:: string(hm->body.p, hm->body.len);
  24. connection->flags |= MG_F_SEND_AND_CLOSE;
  25. s_exit_flag = 1; // 每次收到请求后关闭本次连接,重置标记
  26. // 回调处理
  27. s_req_callback(rsp);
  28. }
  29. break;
  30. case MG_EV_CLOSE:
  31. if (s_exit_flag == 0)
  32. {
  33. printf( "Server closed connection\n");
  34. s_exit_flag = 1;
  35. };
  36. break;
  37. default:
  38. break;
  39. }
  40. }
  41. // 发送一次请求,并回调处理,然后关闭本次连接
  42. void HttpClient::SendReq( const std:: string &url, ReqCallback req_callback)
  43. {
  44. // 给回调函数赋值
  45. s_req_callback = req_callback;
  46. mg_mgr mgr;
  47. mg_mgr_init(&mgr, NULL);
  48. auto connection = mg_connect_http(&mgr, OnHttpEvent, url.c_str(), NULL, NULL);
  49. mg_set_protocol_http_websocket(connection);
  50. printf( "Send http request %s\n", url.c_str());
  51. // loop
  52. while (s_exit_flag == 0)
  53. mg_mgr_poll(&mgr, 500);
  54. mg_mgr_free(&mgr);
  55. }

main.cpp


 
 
  1. #include <iostream>
  2. #include "http_client.h"
  3. void handle_func(std::string rsp)
  4. {
  5. // do sth according to rsp
  6. std:: cout << "http rsp1: " << rsp << std:: endl;
  7. }
  8. int main()
  9. {
  10. // 拼完整url,带参数,暂时只写了GET请求
  11. std:: string url1 = "http://127.0.0.1:7999/api/hello";
  12. HttpClient::SendReq(url1, handle_func);
  13. std:: string url2 = "http://127.0.0.1:7999/api/fun2";
  14. HttpClient::SendReq(url2, []( std:: string rsp) {
  15. std:: cout << "http rsp2: " << rsp << std:: endl;
  16. });
  17. system( "pause");
  18. return 0;
  19. }
  • client每次请求都是一个独立的请求
  • 请求函数中加入回调用于处理网络返回

测试

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

GET

请求 

http://localhost:7999/api/hello
 
 

结果

{ "result": welcome to httpserver }
 
 

POST

请求

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

结果

{ "result": 38 }
 
 

websocket的测试可以用工具也可以用内嵌网页,查看连接状态以及双向发消息

网页截图

源码

csdn:demo

github: demo

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值