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

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

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

├─common├─mongoose.h└─mongoose.c
├─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服务器

http_server.h

#pragma once#include <string>
#include <string.h>
#include <unordered_map>
#include <unordered_set>
#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(); // 启动httpserverbool 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 OnHttpWebsocketEvent(mg_connection *connection, int event_type, void *event_data);static void HandleHttpEvent(mg_connection *connection, http_message *http_req);static void SendHttpRsp(mg_connection *connection, std::string rsp);static int isWebsocket(const mg_connection *connection); // 判断是否是websoket类型连接static void HandleWebsocketMessage(mg_connection *connection, int event_type, websocket_message *ws_msg); static void SendWebsocketMsg(mg_connection *connection, std::string msg); // 发送消息到指定连接static void BroadcastWebsocketMsg(std::string msg); // 给所有连接广播消息static std::unordered_set<mg_connection *> s_websocket_session_set; // 缓存websocket连接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();// 其他http设置// 开启 CORS,本项只针对主页加载有效// s_server_option.extra_headers = "Access-Control-Allow-Origin: *";
}bool HttpServer::Start()
{mg_mgr_init(&m_mgr, NULL);mg_connection *connection = mg_bind(&m_mgr, m_port.c_str(), HttpServer::OnHttpWebsocketEvent);if (connection == NULL)return false;// for both http and websocketmg_set_protocol_http_websocket(connection);printf("starting http server at port: %s\n", m_port.c_str());// loopwhile (true)mg_mgr_poll(&m_mgr, 500); // msreturn true;
}void HttpServer::OnHttpWebsocketEvent(mg_connection *connection, int event_type, void *event_data)
{// 区分http和websocketif (event_type == MG_EV_HTTP_REQUEST){http_message *http_req = (http_message *)event_data;HandleHttpEvent(connection, http_req);}else if (event_type == MG_EV_WEBSOCKET_HANDSHAKE_DONE ||event_type == MG_EV_WEBSOCKET_FRAME ||event_type == MG_EV_CLOSE){websocket_message *ws_message = (struct websocket_message *)event_data;HandleWebsocketMessage(connection, event_type, ws_message);}
}// ---- simple http ---- //
static bool route_check(http_message *http_msg, char *route_prefix)
{if (mg_vcmp(&http_msg->uri, route_prefix) == 0)return true;elsereturn 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::SendHttpRsp(mg_connection *connection, std::string rsp)
{// --- 未开启CORS// 必须先发送header, 暂时还不能用HTTP/2.0mg_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);// --- 开启CORS/*mg_printf(connection, "HTTP/1.1 200 OK\r\n""Content-Type: text/plain\n""Cache-Control: no-cache\n""Content-Length: %d\n""Access-Control-Allow-Origin: *\n\n""%s\n", rsp.length(), rsp.c_str()); */
}void HttpServer::HandleHttpEvent(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, &HttpServer::SendHttpRsp);}// 其他请求if (route_check(http_req, "/")) // index pagemg_serve_http(connection, http_req, s_server_option);else if (route_check(http_req, "/api/hello")) {// 直接回传SendHttpRsp(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);SendHttpRsp(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");}
}// ---- websocket ---- //
int HttpServer::isWebsocket(const mg_connection *connection)
{return connection->flags & MG_F_IS_WEBSOCKET;
}void HttpServer::HandleWebsocketMessage(mg_connection *connection, int event_type, websocket_message *ws_msg)
{if (event_type == MG_EV_WEBSOCKET_HANDSHAKE_DONE){printf("client websocket connected\n");// 获取连接客户端的IP和端口char addr[32];mg_sock_addr_to_str(&connection->sa, addr, sizeof(addr), MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);printf("client addr: %s\n", addr);// 添加 sessions_websocket_session_set.insert(connection);SendWebsocketMsg(connection, "client websocket connected");}else if (event_type == MG_EV_WEBSOCKET_FRAME){mg_str received_msg = {(char *)ws_msg->data, ws_msg->size};char buff[1024] = {0};strncpy(buff, received_msg.p, received_msg.len); // must use strncpy, specifiy memory pointer and length// do sth to process requestprintf("received msg: %s\n", buff);SendWebsocketMsg(connection, "send your msg back: " + std::string(buff));//BroadcastWebsocketMsg("broadcast msg: " + std::string(buff));}else if (event_type == MG_EV_CLOSE){if (isWebsocket(connection)){printf("client websocket closed\n");// 移除sessionif (s_websocket_session_set.find(connection) != s_websocket_session_set.end())s_websocket_session_set.erase(connection);}}
}void HttpServer::SendWebsocketMsg(mg_connection *connection, std::string msg)
{mg_send_websocket_frame(connection, WEBSOCKET_OP_TEXT, msg.c_str(), strlen(msg.c_str()));
}void HttpServer::BroadcastWebsocketMsg(std::string msg)
{for (mg_connection *connection : s_websocket_session_set)mg_send_websocket_frame(connection, WEBSOCKET_OP_TEXT, msg.c_str(), strlen(msg.c_str()));
}bool HttpServer::Close()
{mg_mgr_free(&m_mgr);return true;
}

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;
std::unordered_set<mg_connection *> HttpServer::s_websocket_session_set;bool handle_fun1(std::string url, std::string body, mg_connection *c, OnRspCallback rsp_callback)
{// do sthstd::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 sthstd::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 handlerhttp_server->AddHandler("/api/fun1", handle_fun1);http_server->AddHandler("/api/fun2", handle_fun2);http_server->Start();return 0;
}

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">// simple http$(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);}});});// websocketvar websocket = new WebSocket('ws://' + location.host + '/ws');websocket.onopen = function (ev) {console.log(ev.data);};websocket.onerror = function (ev) {console.log(ev.data);};websocket.onclose = function (ev) {console.log(ev.data);};websocket.onmessage = function (ev) {console.log(ev.data);document.getElementById("ws_text").innerHTML = ev.data;};window.onload = function () {document.getElementById('send_button').onclick = function (ev) {var msg = document.getElementById('send_input').value;websocket.send(msg);};};
</script>
</head>
<body><h1>c++ httpserver demo</h1><h2>simple http</h2><h3>GET</h3><div><button id="btn">get request</button></div><div><label>Result1:</label> <span id="result1">&nbsp;</span></div><h3>POST</h3><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">&nbsp;</span></div><h2>websocket</h2><div><span id="ws_text">&nbsp;</span><br /><input type="text" id="send_input" /><button id="send_button">Send</button></div></body>
</html>
  • 服务器支持host静态页面资源
  • 服务器支持前端页面的热加载
  • 服务器支持http和websocket两种方式的接口
  • 服务器可以添加头格式来开启跨域(CORS)加载支持
  • 服务器支持websocket单一连接发消息和广播消息
  • 服务器支持管理websocket的session
  • 需要手动设置loop polling的时间间隔
  • 可以自定义静态页面根路径,注册和解注册自定义api函数回调
  • 某些变量必须声明定义成全局或者静态变量
  • 如果需要回传json格式,可以序列化成字符串,在前端解析

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());// loopwhile (s_exit_flag == 0)mg_mgr_poll(&mgr, 500);mg_mgr_free(&mgr);
}

main.cpp

#include <iostream>
#include "http_client.h"void handle_func(std::string rsp)
{// do sth according to rspstd::cout << "http rsp1: " << rsp << std::endl;
}int main()
{// 拼完整url,带参数,暂时只写了GET请求std::string url1 = "http://127.0.0.1:7999/api/hello";HttpClient::SendReq(url1, handle_func);std::string url2 = "http://127.0.0.1:7999/api/fun2";HttpClient::SendReq(url2, [](std::string rsp) { std::cout << "http rsp2: " << rsp << std::endl; });system("pause");return 0;
}
  • 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

支持是知识分享的动力,有问题可扫码哦

C++实现轻量级极简httpserver和httpclient(提供http和websocket接口)相关推荐

  1. CentOS安装使用.netcore极简教程(免费提供学习服务器)

    本文目标是指引从未使用过Linux的.Neter,如何在CentOS7上安装.Net Core环境,以及部署.Net Core应用. 仅针对CentOS,其它Linux系统类似,命令环节稍加调整: 需 ...

  2. 美团推出极简版 为用户提供“米面粮油”等生活用品采购服务

    近日,有用户反馈,安卓应用商店显示,美团更新推出了极简版,对主应用的功能进行了删减,保留了美团主应用中涉及生活用品采购的相关业务.用户在打开极简版后,首页会呈现出采购蔬果.米面水油等生活用品的购买入口 ...

  3. 对Chrome进行扩展【极简插件】

    使用步骤 注意事项: 关于安装时出现"无法从该网站添加应用.扩展程序.和用户脚本"等问题,大概率是谷歌浏览器版本不是最新版,推荐在浏览器 - 帮助 - 关于 Google Chro ...

  4. 《Kotin 极简教程》第9章 轻量级线程:协程(2)

    <Kotlin极简教程>正式上架: 点击这里 > 去京东商城购买阅读 点击这里 > 去天猫商城购买阅读 非常感谢您亲爱的读者,大家请多支持!!!有任何问题,欢迎随时与我交流~ ...

  5. 轻量级Typecho极简主题——Ltns【好久不见】

    Hello,好久不见! 拖了两天,用了大约五六个小时时间,我完成了第一个Typecho主题. 有这个主题的想法,源于看见Typecho在长达近五年的断更时间下,Typecho更新了1.2.0版本. 对 ...

  6. 追求极简:Docker镜像构建演化史

    // Dockerfile.target.alpine 自从2013年dotCloud公司(现已改名为Docker Inc)发布Docker容器技术以来,到目前为止已经有五年多的时间了.这期间Dock ...

  7. 一个极简的Http请求client推荐,一行搞玩外部请求

    OKHttpUtil 在Java的世界中,Http客户端之前一直是Apache家的HttpClient占据主导,但是由于此包较为庞大,API又比较难用,因此并不使用很多场景.而新兴的OkHttp.Jo ...

  8. Kotlin极简教程

    目录 Kotlin极简教程 前言 视频教程 Kotlin 系统入门到进阶 Kotlin 从入门到放弃 Kotlin 从零基础到进阶 第1章 Kotlin简介 第2章 快速开始:HelloWorld 第 ...

  9. Docker极简入门

    原 Docker极简入门 2018年05月22日 20:25:12 阅读数:44 一.Docker概述 Docker通过一个包括应用程序运行时所需的一切的可执行镜像启动容器,包括配置有代码.运行时.库 ...

  10. ​上海AI Lab罗格斯大学港中文提出CLIP-Adapter,用极简方式微调CLIP中的最少参数!...

    关注公众号,发现CV技术之美 本文分享论文『CLIP-Adapter: Better Vision-Language Models with Feature Adapters』,由上海AI Lab&a ...

最新文章

  1. visual c++ for .net(新语法)
  2. JavaScript实现冒泡排序 可视化
  3. Google Map API v2 步步为营 (二)----- Location
  4. Win7系统转到Win10系统的装机方法
  5. Qt文档阅读笔记-windowOpacity官方解析及实例(两种方法使得程序渐变出现)
  6. Node.js_1.1
  7. PHP 程序员的技术成长规划
  8. 刺激!我31岁敲代码10年,明天退休!
  9. 从零基础入门Tensorflow2.0 ----一、3.2 实战深度神经网络(批归一化)
  10. 为静态Checkbox动态地添加checked属性
  11. 【FPGA+BP神经网络】基于FPGA的简易BP神经网络verilog设计
  12. 蚂蚁金服高要求的领域建模能力,对研发来说到底指什么?
  13. Java 如何获取线程状态呢?
  14. NVIDIA驱动安装
  15. intel945显卡linux驱动,下载:Intel 945G/G965/G3X显卡新版驱动
  16. 网站流量分析的整体思路(大数据)
  17. python,用pycharm写的评分系统
  18. 央国企的企业并购重组信息能在塔米狗上找到吗?
  19. [M1]Daily Scum 9.27
  20. Python脚本25:将两张图片拼在一起

热门文章

  1. 判断一个数是否为素数
  2. Oracle 12c 数据库基础教程
  3. 解决post请求跨域请求第三方服务器
  4. Linux网络的网络配置(超详细,百分百成功)
  5. pythonqq机器人酷q_基于python和酷Q的QQ机器人开发实践(1)
  6. RuntimeError: no valid convolution algorithms available in CuDNN
  7. CSR8675烧录工具-全系列CSR8675/86xx一拖多量产工具csr烧录软件(支持FLASH版本和ROM版本)
  8. android 微信输入法表情,分析Android 搜狗输入法在微信和QQ中发送图片和表情
  9. 阿里云移动推送 - 异常记录
  10. 闲聊人工智能产品经理(AIPM)—人工智能产品经理的行业理解