原文地址: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类,方便调用,目录结构如下:
├─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_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<
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
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();
-
-
// 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
-
#include
-
#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
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
-
-
<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>
http_client.h
-
#pragma once
-
#include
-
#include
-
#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
-
#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