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

但是传统的实现方法比如采用libcurl,asio等较为重型的框架来做有没有必要,因此,这里采用mongoose
<https://github.com/cesanta/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服务器

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(); // 启动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 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(); // TODO:其他http设置 }
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 websocket
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::OnHttpWebsocketEvent(mg_connection
*connection, int event_type, void *event_data) { // 区分http和websocket if
(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; 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::SendHttpRsp(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::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 page mg_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); // 添加 session
s_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 request
printf("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"); // 移除session
if (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 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->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); } }); }); // websocket var 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"> </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"> </span> </div> <h2>websocket</h2> <div> <span
id="ws_text"> </span> <br /> <input type="text" id="send_input" /> <button
id="send_button">Send</button> </div> </body> </html>
* 服务器支持host静态页面资源
* 服务器支持前端页面的热加载
* 服务器支持http和websocket两种方式的接口
* 服务器支持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()); // loop while (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 rsp std::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 <https://download.csdn.net/download/u012234115/10292941>

github: demo <https://github.com/tashaxing/CppHttpDemo>