对于网络应用,Tcp,Http,websocket是常用的几个协议,今天我们来讲讲HttpServer方面的应用。我们直接上代码,来看看我平时的应用。
头文件
#pragma once
#include <string>
#include <string.h>
#include <unordered_map>
#include <unordered_set>
#include <functional>
#include <utility>
#include <memory>
#include "mongoose.h"
#include <mutex>
#include <map>
// 定义http返回callback
typedef void OnRspCallback(mg_connection *c, std::string);
// 定义http请求handler
using ReqHandler = std::function<bool (unsigned int iSubHttpConID, std::string, std::string, mg_connection *c, OnRspCallback)>;
class GrockHttpServer
{
public:
GrockHttpServer() {}
~GrockHttpServer() {}
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; // 回调函数映射表
void SendRspMsg(mg_connection *connection, std::string rsp);
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; // 连接管理器
};
class CGrockHttpSsMg
{
public:
CGrockHttpSsMg() { m_HttpServer = std::shared_ptr<GrockHttpServer>(new GrockHttpServer); }
static CGrockHttpSsMg& Single() { static CGrockHttpSsMg single; return single; }
void Start(const std::string& port);
unsigned CreateHttpConID(mg_connection* conn);
void SendRspMsg(unsigned int iSubConID, std::string rsp);
void AddHandler(const std::string &url, ReqHandler req_handler)
{
m_HttpServer->AddHandler(url, req_handler);
}
private:
~CGrockHttpSsMg() {}
private:
std::mutex m_mutex;
std::map<unsigned, mg_connection*> m_HttpMg;
std::shared_ptr<GrockHttpServer> m_HttpServer;
};
这里我用到了mongoose.h
mongoose的代码着实轻量,先看看它的特点:
-
在整个的实现是使用C语言编写
-
整个代码也只有一个mongoose.c和mongoose.h两个文件, 从引入第三方的考虑上也着实不多。
-
实现的功能还是非常多的,从使用的层面上来说功能还是比较全面。只不过不知道是否是为了第三方使用的方便还是怎么地,它的代码只用了两个源文件罢了。诸多的功能也大以宏的开始与结束来区分。
源文件:
#include "./GrockHttpServer.h"
#include <thread>
mg_serve_http_opts GrockHttpServer::s_server_option;
std::string GrockHttpServer::s_web_dir = "./web";
std::unordered_map<std::string, ReqHandler> GrockHttpServer::s_handler_map;
std::unordered_set<mg_connection *> GrockHttpServer::s_websocket_session_set;
void GrockHttpServer::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();
}
bool GrockHttpServer::Start()
{
mg_mgr_init(&m_mgr, NULL);
mg_connection *connection = mg_bind(&m_mgr, m_port.c_str(), GrockHttpServer::OnHttpWebsocketEvent);
if (connection == NULL)
return false;
mg_set_protocol_http_websocket(connection);
printf("starting http server at port: %s\n", m_port.c_str());
while (true)
mg_mgr_poll(&m_mgr, 500);
return true;
}
void GrockHttpServer::OnHttpWebsocketEvent(mg_connection *connection, int event_type, void *event_data)
{
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);
}
}
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;
}
void GrockHttpServer::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 GrockHttpServer::RemoveHandler(const std::string &url)
{
auto it = s_handler_map.find(url);
if (it != s_handler_map.end())
s_handler_map.erase(it);
}
void GrockHttpServer::SendHttpRsp(mg_connection *connection, std::string rsp)
{
mg_printf(connection, "%s", "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
mg_printf_http_chunk(connection, "%s", rsp.c_str());
mg_send_http_chunk(connection, "", 0);
}
void GrockHttpServer::SendRspMsg(mg_connection *connection, std::string rsp)
{
mg_printf(connection, "%s", "HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n");
mg_printf_http_chunk(connection, "%s", rsp.c_str());
mg_send_http_chunk(connection, "", 0);
}
void GrockHttpServer::HandleHttpEvent(mg_connection *connection, http_message *http_req)
{
unsigned iSubConID = CGrockHttpSsMg::Single().CreateHttpConID(connection);
if (iSubConID == 0)
return;
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(iSubConID, url, body, connection, &GrockHttpServer::SendHttpRsp);
}
else
{
if (route_check(http_req, "/api/hello"))
{
SendHttpRsp(connection, "welcome to GrockHttpServer");
}
else
{
mg_printf(
connection,
"%s",
"HTTP/1.1 501 Not Implemented\r\n"
"Content-Length: 0\r\n\r\n");
}
}
}
// ---- websocket ---- //
int GrockHttpServer::isWebsocket(const mg_connection *connection)
{
return connection->flags & MG_F_IS_WEBSOCKET;
}
void GrockHttpServer::HandleWebsocketMessage(mg_connection *connection, int event_type, websocket_message *ws_msg)
{
if (event_type == MG_EV_WEBSOCKET_HANDSHAKE_DONE)
{
printf("client websocket connected\n");
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);
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
printf("received msg: %s\n", buff);
SendWebsocketMsg(connection, "send your msg back: " + std::string(buff));
}
else if (event_type == MG_EV_CLOSE)
{
if (isWebsocket(connection))
{
printf("client websocket closed\n");
if (s_websocket_session_set.find(connection) != s_websocket_session_set.end())
s_websocket_session_set.erase(connection);
}
}
}
void GrockHttpServer::SendWebsocketMsg(mg_connection *connection, std::string msg)
{
mg_send_websocket_frame(connection, WEBSOCKET_OP_TEXT, msg.c_str(), strlen(msg.c_str()));
}
void GrockHttpServer::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 GrockHttpServer::Close()
{
mg_mgr_free(&m_mgr);
return true;
}
///////////////////////////////////////////////////////////////////
void CGrockHttpSsMg::Start(const std::string& port)
{
auto fun = [](std::shared_ptr<GrockHttpServer> pHttpServer,
const std::string& strPort) {
pHttpServer->Init(strPort);
pHttpServer->Start();
};
std::thread t(fun, m_HttpServer, port);
t.detach();
}
unsigned CGrockHttpSsMg::CreateHttpConID(mg_connection* conn)
{
static unsigned int iHttpConID = 20000;
unsigned int iTempID = iHttpConID;
m_mutex.lock();
typename std::map<unsigned, mg_connection*>::iterator iter = m_HttpMg.find(iTempID);
if (iter == m_HttpMg.end())
{
m_HttpMg.insert(std::map<unsigned, mg_connection*>::value_type(iTempID, conn));
m_mutex.unlock();
iHttpConID += 1;
return iTempID;
}
m_mutex.unlock();
return 0;
}
void CGrockHttpSsMg::SendRspMsg(unsigned int iSubConID, std::string rsp)
{
m_mutex.lock();
typename std::map<unsigned, mg_connection*>::iterator iter = m_HttpMg.find(iSubConID);
if (iter != m_HttpMg.end())
{
mg_connection* connection = iter->second;
m_HttpMg.erase(iter);
m_mutex.unlock();
m_HttpServer->SendRspMsg(connection, rsp);
}
else
{
m_mutex.unlock();
printf("不存在这个Http连接,应该已经回应\n");
}
}