一般来说,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_server.h
#pragma once
#include
#include
#include
#include
#include
#include "../common/mongoose.h"
// 定义http返回callback
typedef void OnRspCallback(mg_connection *c, std::string);
// 定义http请求handler
using ReqHandler = std::function;
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 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 s_websocket_session_set; // 缓存websocket连接
std::string m_port; // 端口
mg_mgr m_mgr; // 连接管理器
};
http_server.cpp
#include
#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 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)
{
// --- 未开启CORS
// 必须先发送header, 暂时还不能用HTTP/2.0
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);
// --- 开启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 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
#include
#include "http_server.h"
// 初始化HttpServer静态类成员
mg_serve_http_opts HttpServer::s_server_option;
std::string HttpServer::s_web_dir = "./web";
std::unordered_map HttpServer::s_handler_map;
std::unordered_set 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(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
RESTful API demo
c++ httpserver demo
simple http
GET
POST
websocket
http_client.h
#pragma once
#include
#include
#include "../common/mongoose.h"
// 此处必须用function类,typedef再后面函数指针赋值无效
using ReqCallback = std::function;
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
#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;
}
可以用浏览器、或者其他工具提交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