基于Mongoose的HTTP通信库
Mongoose可以支持的网络服务类型
-
TCP服务端和客户端
-
UDP服务端和客户端
-
HTTP服务端和客户端
-
Websocket服务端和客户端
-
SNTP 客户端
-
DNS服务端和客户端(不常用)
-
MQTT服务端和客户端(不常用)
-
COAP服务端和客户端(不常用)
HTTP相关接口介绍
struct mg_http_endpoint {
struct mg_http_endpoint *next;
struct mg_str uri_pattern; /* owned */
char *auth_domain; /* owned */
char *auth_file; /* owned */
mg_event_handler_t handler;
#if MG_ENABLE_CALLBACK_USERDATA
void *user_data;
#endif
};
/* Looking for the longest suitable handler of nc and the given uri_path*/
struct mg_http_endpoint *mg_http_get_endpoint_handler(struct mg_connection *nc, struct mg_str *uri_path);
/*Attaches a built-in HTTP event handler to the given connection*/
void mg_set_protocol_http_websocket(struct mg_connection *nc);
/*回调函数入口*/
void mg_http_handler(struct mg_connection *nc, int ev,void *ev_data MG_UD_ARG(void *user_data));
/*返回状态码对应的状态*/
const char *mg_status_message(int status_code);
/*HTTP数据发送相关接口*/
void mg_http_send_error(struct mg_connection *nc, int code,const char *reason);
void mg_http_send_redirect(struct mg_connection *nc, int status_code, const struct mg_str location,
const struct mg_str extra_headers);
void mg_send_head(struct mg_connection *c, int status_code,int64_t content_length, const char *extra_headers);
void mg_send_response_line(struct mg_connection *nc, int status_code,const char *extra_headers);
void mg_send_response_line_s(struct mg_connection *nc, int status_code,const struct mg_str extra_headers);
int mg_printf(struct mg_connection *, const char *fmt, ...);
int mg_vprintf(struct mg_connection *, const char *fmt, va_list ap);
void mg_send(struct mg_connection *, const void *buf, int len);
/*
* Sends buffer `buf` of size `len` to the client using chunked HTTP encoding.
* This function sends the buffer size as hex number + newline first, then
* the buffer itself, then the newline.
*
* NOTE: The HTTP header "Transfer-Encoding: chunked" should be sent prior to
* using this function.
*
* NOTE: do not forget to send an empty chunk at the end of the response,
* to tell the client that everything was sent. Example:
*/
void mg_send_http_chunk(struct mg_connection *nc, const char *buf, size_t len);
void mg_printf_http_chunk(struct mg_connection *nc, const char *fmt, ...);
/*Serves a specific file with a given MIME type and optional extra headers.*/
void mg_http_serve_file(struct mg_connection *nc, struct http_message *hm,const char *path,
const struct mg_str mime_type,const struct mg_str extra_headers);
/*example:
*static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
* switch (ev) {
* case MG_EV_HTTP_REQUEST: {
* struct http_message *hm = (struct http_message *) ev_data;
* mg_http_serve_file(nc, hm, "file.txt",mg_mk_str("text/plain"), mg_mk_str(""));
* break;
* }
* ...
* }
* }
*/
/* Serves given HTTP request according to the options */
void mg_serve_http(struct mg_connection *nc, struct http_message *hm,struct mg_serve_http_opts opts);
/*example:
*static void ev_handler(struct mg_connection *nc, int ev, void *ev_data) {
* switch (ev) {
* case MG_EV_HTTP_REQUEST: {
* struct http_message *hm = (struct http_message *) ev_data;
* struct mg_serve_http_opts opts = { .document_root = "/var/www" }; // C99
* mg_serve_http(nc, hm, opts);
* break;
* }
* ...
* }
* }
*/
/*HTTP报文解析相关接口*/
/*Parses a HTTP message.
*
* `is_req` should be set to 1 if parsing a request, 0 if reply.
*
* Returns the number of bytes parsed. If HTTP message is
* incomplete `0` is returned. On parse error, a negative number is returned.
*/
int mg_parse_http(const char *s, int n, struct http_message *hm, int is_req);
struct mg_str *mg_get_http_header(struct http_message *hm, const char *name);
int mg_http_parse_header(struct mg_str *hdr, const char *var_name, char *buf,size_t buf_size);
int mg_http_parse_header2(struct mg_str *hdr, const char *var_name, char **buf,size_t buf_size);
/*
* Gets and parses the Authorization: Basic header
* Returns -1 if no Authorization header is found, or if
* mg_parse_http_basic_auth fails parsing the resulting header.
*/
int mg_get_http_basic_auth(struct http_message *hm, char *user,
size_t user_len, char *pass, size_t pass_len);
int mg_parse_http_basic_auth(struct mg_str *hdr, char *user,
size_t user_len, char *pass, size_t pass_len);
int mg_get_http_var(const struct mg_str *buf, const char *name, char *dst,size_t dst_len);
/* 客户端在请求头部中生成鉴权信息 */
int mg_http_create_digest_auth_header(char *buf, size_t buf_len,
const char *method, const char *uri,
const char *auth_domain, const char *user,
const char *passwd, const char *nonce);
/*检查鉴权信息中的时间戳有没有过期,mongoose中实现为有效时间1小时。防止重放攻击。
*未过期则返回1,否则返回0.
* Assumption: nonce is a hexadecimal number of seconds since 1970.
*/
static int mg_check_nonce(const char *nonce);
/*
* Authenticates a HTTP request against an opened password file.
* Returns 1 if authenticated, 0 otherwise.
*/
int mg_http_check_digest_auth(struct http_message *hm, const char *auth_domain,FILE *fp);
int mg_check_digest_auth(struct mg_str method, struct mg_str uri,
struct mg_str username, struct mg_str cnonce,
struct mg_str response, struct mg_str qop,
struct mg_str nc, struct mg_str nonce,
struct mg_str auth_domain, FILE *fp);
/* 发送连接鉴权失败的回复,状态码401.*/
void mg_http_send_digest_auth_request(struct mg_connection *c,const char *domain);
/*HTTP连接相关接口*/
struct mg_connection *mg_connect_http(struct mg_mgr *mgr,
MG_CB(mg_event_handler_t ev_handler, void *user_data),
const char *url,
const char *extra_headers,
const char *post_data);
struct mg_connection *mg_connect_http_opt(struct mg_mgr *mgr,
MG_CB(mg_event_handler_t ev_handler, void *user_data),
struct mg_connect_opts opts,
const char *url,
const char *extra_headers,
const char *post_data);
struct mg_connection *mg_connect_http_base(struct mg_mgr *mgr,
MG_CB(mg_event_handler_t ev_handler, void *user_data),
struct mg_connect_opts opts,
const char *scheme1,
const char *scheme2,
const char *scheme_ssl1,
const char *scheme_ssl2,
const char *url,
struct mg_str *path,
struct mg_str *user_info,
struct mg_str *host);
HTTP Websocket相关接口
/*向对端发送websocket帧*/
void mg_send_websocket_frame(struct mg_connection *nc, int op, const void *data,size_t len);
void mg_send_websocket_framev(struct mg_connection *nc, int op,const struct mg_str *strv, int strvcnt);
void mg_printf_websocket_frame(struct mg_connection *nc, int op,const char *fmt, ...);
/*向服务端发送websocket握手报文:
* "Upgrade: websocket\r\n"
* "Connection: Upgrade\r\n"
* "Sec-WebSocket-Version: 13\r\n"
* "Sec-WebSocket-Key: xxx\r\n",
* "Sec-WebSocket-Protocol: xxx\r\n"
* 注意:nc必需已连接,且请求必需支持HTTP/1.1
*/
void mg_send_websocket_handshake2(struct mg_connection *nc, const char *path,const char *host,
const char *protocol,const char *extra_headers);
void mg_send_websocket_handshake3(struct mg_connection *nc, const char *path,const char *host,
const char *protocol,const char *extra_headers,
const char *user,const char *pass);
void mg_send_websocket_handshake3v(struct mg_connection *nc,const struct mg_str path,
const struct mg_str host, const struct mg_str protocol,
const struct mg_str extra_headers,const struct mg_str user,
const struct mg_str pass);
void mg_send_websocket_handshake(struct mg_connection *nc, const char *path,const char *extra_headers);
/*建立WebSocket连接*/
struct mg_connection *mg_connect_ws(
struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),
const char *url, const char *protocol, const char *extra_headers);
struct mg_connection *mg_connect_ws_opt(
struct mg_mgr *mgr, MG_CB(mg_event_handler_t ev_handler, void *user_data),
struct mg_connect_opts opts, const char *url, const char *protocol,
const char *extra_headers);
#define MG_SOCK_STRINGIFY_IP 1
#define MG_SOCK_STRINGIFY_PORT 2
#define MG_SOCK_STRINGIFY_REMOTE 4
/*Converts a connection's local or remote address into string
*
* The `flags` parameter is a bit mask that controls the behaviour,
* flag=MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT ,and output the local address
* flag=MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT|MG_SOCK_STRINGIFY_REMOTE, and output the remote address
*
* Return length of the stringified address.
*/
int mg_conn_addr_to_str(struct mg_connection *nc, char *buf, size_t len,int flags);
/*Convert the socket's address into string.
* `flags` is MG_SOCK_STRINGIFY_IP and/or MG_SOCK_STRINGIFY_PORT.
*/
int mg_sock_addr_to_str(const union socket_address *sa, char *buf, size_t len,int flags);
HTTP服务端、客户端代码封装
1、通用头文件
//http_message.h
#pragma once
#include <iostream>
#include <unordered_map>
#include <functional>
#include <string>
#include "mongoose.h"
using namespace std;
const int CHAR_HTTP_VAR_MAX = 128;
const int CHAR_HTTP_COOKIE_MAX = 400;
//供客户端使用,用来处理服务端回复的响应报文
using ResponseHandler = std::function<void(http_message *)>;
//供服务端使用,用来处理客户端发送的请求报文
using RequestHandler = std::function<const std::string(http_message *)>;//返回值为回复报文
class HttpMessage
{
private:
struct http_message *hm = NULL;
public:
HttpMessage(struct http_message *hm){ this->hm = hm; }
~HttpMessage(){}
const string getGetVar(const string & var_name) const
{
if (hm == NULL || hm->query_string.p == NULL)return "";
try{
char var[CHAR_HTTP_VAR_MAX] = { 0 };
mg_get_http_var(&hm->query_string, var_name.c_str(), var, sizeof(var));
string ret = var;
return ret;
}
catch (...){ return ""; }
}
const string getPostVar(const string & var_name) const
{
if (hm == NULL || hm->body.p == NULL)return "";
try{
char var[CHAR_HTTP_VAR_MAX] = { 0 };
mg_get_http_var(&hm->body, var_name.c_str(), var, sizeof(var));
string ret = var;
return ret;
}
catch (...){ return ""; }
}
const string getCookie(const string & cookie_name)const
{
return getHeadValue("cookie");
}
const string getHeadValue(const string & head_name)const
{
if (hm == NULL)return "";
try{
int i = 0;
while (hm->header_names[i].len > 0)
{
if (hm->header_names[i].p != NULL
&&hm->header_names[i].len == head_name.size()
&& mg_ncasecmp(hm->header_names[i].p, head_name.c_str(), head_name.size()))
{
return string(hm->header_values[i].p, hm->header_values[i].len);
}
++i;
}
}
catch (...){ return ""; }
}
inline const string getBody()const
{
if (hm == NULL)return "";
try{ return string(hm->body.p, hm->body.len); }
catch (...){ return ""; }
}
inline const string getURI()const
{
if (hm == NULL)return "";
try{ return string(hm->uri.p, hm->uri.len); }
catch (...){ return ""; }
}
inline const string getAllMessage()const
{
if (hm == NULL)return "";
try{ return string(hm->message.p, hm->message.len); }
catch (...){ return ""; }
}
inline const string getProto()const
{
if (hm == NULL)return "";
try{ return string(hm->proto.p, hm->proto.len); }
catch (...){ return ""; }
}
inline const string getResponseStatusMsg()const
{
if (hm == NULL)return "";
try{ return string(hm->resp_status_msg.p, hm->resp_status_msg.len); }
catch (...){ return ""; }
}
inline int getResponseStatusCode()const
{
if (hm == NULL)return -1;
try{ return hm->resp_code; }
catch (...){ return -1; }
}
inline const string getMethod() const
{
if (hm == NULL) return "";
try{ return string(hm->method.p, hm->method.len); }
catch (...){ return ""; }
}
};
static void hex_to_str(char *dst, unsigned char *src, int src_len)
{
//函数功能: 十六进制转字符串
for (int i = 0; i < src_len; i++)
{
sprintf(dst, "%02x", src[i]);
dst += 2;
}
}
#pragma region MG相关事件
typedef std::unordered_map<int, std::string> MG_EVENT;
const MG_EVENT rf_mg_event({
//std::make_pair(MG_EV_POLL, "MG_EV_POLL"), /* Sent to each connection on each mg_mgr_poll() call */
std::make_pair(MG_EV_ACCEPT, "MG_EV_ACCEPT"), /* New connection accepted. union socket_address * */
std::make_pair(MG_EV_CONNECT, "MG_EV_CONNECT"), /* connect() succeeded or failed. int * */
std::make_pair(MG_EV_RECV, "MG_EV_RECV"), /* Data has been received. int *num_bytes */
std::make_pair(MG_EV_SEND, "MG_EV_SEND"), /* Data has been written to a socket. int *num_bytes */
std::make_pair(MG_EV_CLOSE, "MG_EV_CLOSE"), /* Connection is closed. NULL */
std::make_pair(MG_EV_TIMER, "MG_EV_TIMER"), /* now >= conn->ev_timer_time. double * */
std::make_pair(MG_EV_HTTP_REQUEST, "MG_EV_HTTP_REQUEST"), /* struct http_message * */
std::make_pair(MG_EV_HTTP_REPLY, "MG_EV_HTTP_REPLY"), /* struct http_message * */
std::make_pair(MG_EV_HTTP_CHUNK, "MG_EV_HTTP_CHUNK"), /* struct http_message * */
std::make_pair(MG_EV_SSI_CALL, "MG_EV_SSI_CALL"), /* char * */
std::make_pair(MG_EV_SSI_CALL_CTX, "MG_EV_SSI_CALL_CTX"), /* struct mg_ssi_call_ctx * */
std::make_pair(MG_EV_WEBSOCKET_HANDSHAKE_REQUEST, "MG_EV_WEBSOCKET_HANDSHAKE_REQUEST"),/* struct http_message * */
std::make_pair(MG_EV_WEBSOCKET_HANDSHAKE_DONE, "MG_EV_WEBSOCKET_HANDSHAKE_DONE"), /* struct http_message * */
std::make_pair(MG_EV_WEBSOCKET_FRAME, "MG_EV_WEBSOCKET_FRAME"), /* struct websocket_message * */
std::make_pair(MG_EV_WEBSOCKET_CONTROL_FRAME, "MG_EV_WEBSOCKET_CONTROL_FRAME"), /* struct websocket_message * */
#if MG_ENABLE_HTTP_STREAMING_MULTIPART
std::make_pair(MG_EV_HTTP_MULTIPART_REQUEST, "MG_EV_HTTP_MULTIPART_REQUEST"), /* struct http_message */
std::make_pair(MG_EV_HTTP_PART_BEGIN, "MG_EV_HTTP_PART_BEGIN"), /* struct mg_http_multipart_part */
std::make_pair(MG_EV_HTTP_PART_DATA, "MG_EV_HTTP_PART_DATA"), /* struct mg_http_multipart_part */
std::make_pair(MG_EV_HTTP_PART_END, "MG_EV_HTTP_PART_END"), /* struct mg_http_multipart_part */
std::make_pair(MG_EV_HTTP_MULTIPART_REQUEST_END, "MG_EV_HTTP_MULTIPART_REQUEST_END"), /* struct mg_http_multipart_part */
#endif
std::make_pair(MG_EV_MQTT_CONNECT, "MG_EV_MQTT_CONNECT"),
std::make_pair(MG_EV_MQTT_CONNACK, "MG_EV_MQTT_CONNACK"),
std::make_pair(MG_EV_MQTT_PUBLISH, "MG_EV_MQTT_PUBLISH"),
std::make_pair(MG_EV_MQTT_PUBACK, "MG_EV_MQTT_PUBACK"),
std::make_pair(MG_EV_MQTT_PUBREC, "MG_EV_MQTT_PUBREC"),
std::make_pair(MG_EV_MQTT_PUBREL, "MG_EV_MQTT_PUBREL"),
std::make_pair(MG_EV_MQTT_PUBCOMP, "MG_EV_MQTT_PUBCOMP"),
std::make_pair(MG_EV_MQTT_SUBSCRIBE, "MG_EV_MQTT_SUBSCRIBE"),
std::make_pair(MG_EV_MQTT_SUBACK, "MG_EV_MQTT_SUBACK"),
std::make_pair(MG_EV_MQTT_UNSUBSCRIBE, "MG_EV_MQTT_UNSUBSCRIBE"),
std::make_pair(MG_EV_MQTT_UNSUBACK, "MG_EV_MQTT_UNSUBACK"),
std::make_pair(MG_EV_MQTT_PINGREQ, "MG_EV_MQTT_PINGREQ"),
std::make_pair(MG_EV_MQTT_PINGRESP, "MG_EV_MQTT_PINGRESP"),
std::make_pair(MG_EV_MQTT_DISCONNECT, "MG_EV_MQTT_DISCONNECT"),
#if MG_ENABLE_COAP
std::make_pair(MG_EV_COAP_CON, "MG_EV_COAP_CON"),
std::make_pair(MG_EV_COAP_NOC, "MG_EV_COAP_NOC"),
std::make_pair(MG_EV_COAP_ACK, "MG_EV_COAP_ACK"),
std::make_pair(MG_EV_COAP_RST, "MG_EV_COAP_RST"),
#endif
});
#pragma endregion MG相关事件
2、HTTP服务端
//http_server.h
#pragma once
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <functional>
#include <mutex>
#include <atomic>
#include <string>
#include "http_message.h"
class HttpServer
{
public:
HttpServer(int port)
{
m_handler_map.clear();
m_websocket_session_set.clear();
m_port = port;
m_server_option.enable_directory_listing = "yes";
m_server_option.document_root = "./web";// 网页根目录
// 其他http设置
// 开启 CORS,本项只针对主页加载有效
//m_server_option.extra_headers = "Access-Control-Allow-Origin: *";
}
~HttpServer()
{
m_handler_map.clear();
m_websocket_session_set.clear();
}
// 启动HttpServer,此方法将阻塞当前线程
void Start();
// 启动HttpServer,此方法不会阻塞当前线程
void AsyncStart();
// 关闭HttpServer
void Close();
// 添加事件处理函数
void AddRequestHandler(const std::string &uri, RequestHandler req_handler);
// 移除事件处理函数
void RemoveRequestHandler(const std::string &uri);
//事件响应回调
void OnServerEvent(mg_connection *connection, int event_type, void *event_data);
private:
//查找事件对应的处理函数
RequestHandler FindHttpHandler(const std::string &uri);
//发送回复信息
void SendReply(mg_connection *connection, const std::string &reply);
public:
//发送消息到指定的websocket连接
void SendWebsocketMsg(mg_connection *connection, const std::string & msg);
//广播消息到给所有的websocket连接
void BroadcastWebsocketMsg(const std::string & msg);
private:
inline void AddWebsocketConnection( mg_connection *connection);
inline void RemoveWebsocketConnection( mg_connection *connection);
private:
std::mutex m_mutex_handler;
std::unordered_map<std::string, RequestHandler> m_handler_map;// 回调函数映射表,key 为uri
std::mutex m_mutex_websocket;
std::unordered_set<mg_connection *> m_websocket_session_set; //缓存websocket连接
mg_mgr m_mgr; // 连接管理器
mg_serve_http_opts m_server_option;// 服务端选项
std::atomic<bool> m_stop;
int m_port; // 服务端监听端口
};
static void CB_OnServerEvent(mg_connection *connection, int event_type, void *event_data, void * user_data)
{
if (user_data)
{
((HttpServer*)user_data)->OnServerEvent(connection, event_type, event_data);
}
}
//http_server.cpp
#include <thread>
#include "http_server.h"
#include "glog/init_glog.h"
void HttpServer::Start()
{
m_stop = false;
mg_mgr_init(&m_mgr, NULL);
string addr = "tcp://:" + to_string(m_port);
mg_connection *connection = mg_bind(&m_mgr, addr.c_str(), CB_OnServerEvent, this);
if (connection == NULL)
{
LOG_INFO_GLOG<< "fail to start http server, listen at port="<<m_port ;
return;
}
mg_set_protocol_http_websocket(connection);// for both http and websocket
LOG_INFO_GLOG<< "success to start http server, listen at port=" << m_port ;
while (!m_stop)
{
mg_mgr_poll(&m_mgr, 1000); // 1S轮询一次是否有连接上来
}
LOG_INFO_GLOG<< "http server stopped" ;
}
void HttpServer::AsyncStart()
{
std::thread th(std::bind(&HttpServer::Start,this));
th.detach();
}
void HttpServer::Close()
{
if (m_stop==false)
{
m_stop = true;
mg_mgr_free(&m_mgr);
LOG_INFO_GLOG << "close http server";
}
}
void HttpServer::OnServerEvent(mg_connection *connection, int event_type, void *event_data)
{
switch (event_type)
{
case MG_EV_HTTP_REQUEST:
{
//user todo
http_message *hm = (struct http_message *)event_data;
if (hm->message.p == NULL)
{
break;
}
char addr[32] = {0};
mg_sock_addr_to_str(&connection->sa, addr, sizeof(addr), MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);
std::string request = std::string(hm->message.p, hm->message.len);
LOG_INFO_GLOG << "Recv Request from ["<<addr<<"] : " << request;
std::string uri = std::string(hm->uri.p, hm->uri.len);
auto request_handler = FindHttpHandler(uri);
if (request_handler)
{
std::string reply_msg=request_handler(hm);
if (!reply_msg.empty())
{
SendReply(connection, reply_msg);
}
}
else
{
LOG_INFO_GLOG<< "bad request:" << uri ;
mg_printf(connection,"%s",
"HTTP/1.1 501 Not Implemented\r\n",
"Content-Length: 0\r\n\r\n");
// 发送空白字符快,结束当前响应
mg_send_http_chunk(connection, "", 0);
}
//mg_serve_http(connection, hm, m_server_option);
break;
}
case MG_EV_WEBSOCKET_FRAME:
{
//user todo
websocket_message *websocket_msg = (struct websocket_message *)event_data;
if (websocket_msg->data==NULL)
{
break;
}
mg_str msg = { (char *)websocket_msg->data, websocket_msg->size };
std::string request(msg.p, msg.len);
LOG_INFO_GLOG << "recv websocket frame:\n" << request;
break;
}
case MG_EV_WEBSOCKET_HANDSHAKE_DONE:
{
// 获取连接客户端的IP和端口
char addr[32] = {0};
mg_sock_addr_to_str(&connection->sa, addr, sizeof(addr), MG_SOCK_STRINGIFY_IP | MG_SOCK_STRINGIFY_PORT);
LOG_INFO_GLOG<< "recv websocket connect from client, address=" << addr ;
AddWebsocketConnection(connection);
break;
}
case MG_EV_CLOSE:
{
LOG_INFO_GLOG<< "client close connection" ;
RemoveWebsocketConnection(connection);
break;
}
default:
break;
}
}
void HttpServer::AddRequestHandler(const std::string &uri, RequestHandler req_handler)
{
std::unique_lock<std::mutex> lk(m_mutex_handler);
if (m_handler_map.find(uri) != m_handler_map.end())
{
return;
}
m_handler_map.emplace(uri, req_handler);
}
void HttpServer::RemoveRequestHandler(const std::string &uri)
{
std::unique_lock<std::mutex> lk(m_mutex_handler);
auto it = m_handler_map.find(uri);
if (it != m_handler_map.end())
{
m_handler_map.erase(it);
}
}
RequestHandler HttpServer::FindHttpHandler(const std::string &uri)
{
std::unique_lock<std::mutex> lk(m_mutex_handler);
auto it = m_handler_map.find(uri);
if (it != m_handler_map.end())
{
return it->second;
}
return RequestHandler();
}
void HttpServer::SendReply(mg_connection *connection, const std::string & reply)
{
// --- 未开启CORS
mg_send_head(connection, 200, -1, NULL);
mg_printf_http_chunk(connection, "{ \"result\": %s }", reply.c_str());
// 发送空白字符快,结束当前响应
mg_send_http_chunk(connection, "", 0);
LOG_INFO_GLOG << "Send Reply: " << reply;
/*
// --- 开启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", reply.size(), reply.c_str());
*/
}
void HttpServer::SendWebsocketMsg(mg_connection *connection,const std::string & msg)
{
if (connection)
{
mg_send_websocket_frame(connection, WEBSOCKET_OP_TEXT, msg.c_str(), strlen(msg.c_str()));
}
}
void HttpServer::BroadcastWebsocketMsg(const std::string & msg)
{
std::unique_lock<std::mutex> lk(m_mutex_websocket);
for (mg_connection *connection : m_websocket_session_set)
{
if (connection)
{
mg_send_websocket_frame(connection, WEBSOCKET_OP_TEXT, msg.c_str(), strlen(msg.c_str()));
}
}
}
void HttpServer::AddWebsocketConnection(mg_connection *connection)
{
if (connection->flags & MG_F_IS_WEBSOCKET)
{
std::unique_lock<std::mutex> lk(m_mutex_websocket);
m_websocket_session_set.insert(connection);
}
}
void HttpServer::RemoveWebsocketConnection(mg_connection *connection)
{
if (connection->flags & MG_F_IS_WEBSOCKET)
{
std::unique_lock<std::mutex> lk(m_mutex_websocket);
if (m_websocket_session_set.find(connection) != m_websocket_session_set.end())
{
m_websocket_session_set.erase(connection);
}
}
}
3、HTTP客户端-长连接
//http_client_long.h
#pragma once
#include <string>
#include <atomic>
#include <thread>
#include <mutex>
#include "http_message.h"
class HttpClientLong
{
public:
HttpClientLong();
~HttpClientLong();
inline void Start();
inline void Stop();
inline void SetResponseHandler(ResponseHandler handler)
{ m_handler = handler; }
void OnClientEventLong(struct mg_connection *connection,
int event_type,
void *event_data);
void SendRequest(std::string &uri,
std::string & extra_header,
const std::string & post_data);
void SendWebsocketMsg(const std::string &uri,
const string & msg,
ResponseHandler handler);
private:
std::mutex m_mutex_mgr;
mg_mgr m_mgr;
std::thread m_thread_run;
std::atomic<bool> m_exit;
ResponseHandler m_handler;
std::string m_remote_host;
struct mg_connection * m_connection;
};
static void CB_OnClientEventLong(struct mg_connection *connection, int event_type, void *event_data, void * user_data)
{
if (user_data)
{
((HttpClientLong *)user_data)->OnClientEventLong(connection, event_type, event_data);
}
}
void already_connect_http(struct mg_connection * nc,
MG_CB(mg_event_handler_t ev_handler, void *user_data),
const char *extra_headers,const char *post_data,
struct mg_str * user,struct mg_str * password,
struct mg_str * host, struct mg_str * path);
//http_client_long.cpp
#include "http_client_long.h"
#include "glog/init_glog.h"
HttpClientLong::HttpClientLong()
{
Start();
}
HttpClientLong::~HttpClientLong()
{
Stop();
}
void HttpClientLong::Start()
{
std::unique_lock<std::mutex> lk(m_mutex_mgr);
mg_mgr_init(&m_mgr, NULL);
m_exit = false;
m_connection = nullptr;
m_remote_host.clear();
std::thread th([this](){while (!m_exit){
mg_mgr_poll(&m_mgr, 1000);
}});
m_thread_run = std::move(th);
}
void HttpClientLong::Stop()
{
std::unique_lock<std::mutex> lk(m_mutex_mgr);
m_exit = true;
m_thread_run.join();
mg_mgr_free(&m_mgr);
m_connection = nullptr;
m_remote_host.clear();
}
// 客户端的网络请求响应
void HttpClientLong::OnClientEventLong(struct mg_connection *connection, int event_type, void *event_data)
{
switch (event_type)
{
case MG_EV_CONNECT:
{
int connect_status = *(int *)event_data;
if (connect_status != 0)
{
LOG_INFO_GLOG << "fail to connect http server,ec= " << connect_status;
m_connection = nullptr;
m_remote_host.clear();
//连接失败,事件循环线程并不退出
}
break;
}
case MG_EV_HTTP_REPLY:
{
http_message *hm = (struct http_message *)event_data;
std::string rsp = std::string(hm->body.p, hm->body.len);
LOG_INFO_GLOG << "Recv Reply:\n" << rsp;
if (m_handler)
{
m_handler(hm);
}
break;
}
case MG_EV_CLOSE:
{
if (!m_exit)
{
LOG_INFO_GLOG << "Server closed connection";
m_connection = nullptr;
m_remote_host.clear();
}
break;
}
case MG_EV_WEBSOCKET_FRAME:
{
websocket_message *websocket_msg = (struct websocket_message *)event_data;
if (websocket_msg->data == NULL)
{
break;
}
mg_str msg = { (char *)websocket_msg->data, websocket_msg->size };
std::string request(msg.p, msg.len);
LOG_INFO_GLOG << "recv ws frame:\n" << request;
//DO SOME THING
break;
}
default:
break;
}
}
// 发送一次请求,并回调处理,然后关闭本次连接
void HttpClientLong::SendRequest(
std::string &uri,
std::string & extra_header,
const std::string & post_data)
{
if (extra_header.find("Connection")==std::string::npos)
{
extra_header += "Connection: keep-alive\r\n";
}
struct mg_str url = mg_mk_str(uri.c_str());
struct mg_str scheme = MG_NULL_STR;
struct mg_str user = MG_NULL_STR;
struct mg_str host = MG_NULL_STR;
unsigned int port = 0;
struct mg_str path = MG_NULL_STR;
struct mg_str query = MG_NULL_STR;
struct mg_str fragment = MG_NULL_STR;
struct mg_str password = MG_NULL_STR;
if (mg_parse_uri(url, &scheme, &user, &host, &port, &path, &query, &fragment)!=0)
{
LOG_INFO_GLOG << "err:请求格式有误";
return;
}
if (query.len > 0) path.len += query.len + 1;
if (scheme.len == 0 || mg_vcmp(&scheme, "http") == 0 )
{
if (port == 0) port = 80;
}
else if (mg_vcmp(&scheme, "https") == 0)
{
if (port == 0) port = 443;
}
else
{
LOG_INFO_GLOG << "err:请求格式有误";
return;
}
std::string remote_host = string(scheme.p,scheme.len)+"://"+
string(user.p, user.len) + "@" +
string(host.p, host.len) + ":" +to_string(port);
if (post_data.empty())
{
LOG_INFO_GLOG << "Send [GET] Request to " << remote_host << " : uri=" << uri;
}
else
{
LOG_INFO_GLOG << "Send [POST] Request to " << remote_host << " : uri=" << uri;
}
if (!m_connection)
{
//连接后请求
std::unique_lock<std::mutex> lk(m_mutex_mgr);
m_remote_host = remote_host;
m_connection = mg_connect_http(&m_mgr, CB_OnClientEventLong, this,
uri.c_str(), extra_header.c_str(), post_data.c_str());
}
else if (m_remote_host != remote_host)
{
LOG_INFO_GLOG << "err:当前连接的对端不是用户请求的对端";
return;
}
else
{
//直接请求
std::unique_lock<std::mutex> lk(m_mutex_mgr);
already_connect_http(m_connection, CB_OnClientEventLong, this,
extra_header.c_str(), post_data.c_str(),
&user, &password,&host,&path);
}
}
void HttpClientLong::SendWebsocketMsg(
const std::string &uri,
const string & msg,
ResponseHandler handler)
{
if (handler)
{
m_handler = handler;
}
/
struct mg_str url = mg_mk_str(uri.c_str());
struct mg_str scheme = MG_NULL_STR;
struct mg_str user = MG_NULL_STR;
struct mg_str host = MG_NULL_STR;
unsigned int port = 0;
struct mg_str path = MG_NULL_STR;
struct mg_str query = MG_NULL_STR;
struct mg_str fragment = MG_NULL_STR;
struct mg_str password = MG_NULL_STR;
if (mg_parse_uri(url, &scheme, &user, &host, &port, &path, &query, &fragment) != 0)
{
LOG_INFO_GLOG << "websocket err:请求格式有误";
return;
}
if (scheme.len == 0 || mg_vcmp(&scheme, "http") == 0)
{
if (port == 0) port = 80;
}
else if (mg_vcmp(&scheme, "https") == 0)
{
if (port == 0) port = 443;
}
else
{
LOG_INFO_GLOG << "websocket err:请求格式有误";
return;
}
std::string remote_host = string(scheme.p, scheme.len) + "://" +
string(user.p, user.len) + "@" +
string(host.p, host.len) + ":" + to_string(port);
if (!m_connection )
{
//连接后请求-HandShake
std::unique_lock<std::mutex> lk(m_mutex_mgr);
m_remote_host = remote_host;
m_connection = mg_connect_ws(&m_mgr, CB_OnClientEventLong,
this, uri.c_str(), NULL, NULL);
}
else if (m_remote_host != remote_host)
{
LOG_INFO_GLOG << "websocket err:当前连接的对端不是用户请求的对端";
return;
}
else
{
//直接请求
std::unique_lock<std::mutex> lk(m_mutex_mgr);
mg_send_websocket_frame(m_connection, WEBSOCKET_OP_TEXT, msg.c_str(), msg.size());
}
}
void already_connect_http(
struct mg_connection * nc, MG_CB(mg_event_handler_t ev_handler, void *user_data),
const char *extra_headers, const char *post_data,
struct mg_str * user, struct mg_str * password,
struct mg_str * host, struct mg_str * path) {
struct mbuf auth;
mbuf_init(&auth, 0);
if (user->len > 0) {//生成连接鉴权HEADER: Authorization
mg_basic_auth_header(*user, *password, &auth);
}
if (post_data == NULL) post_data = "";
if (extra_headers == NULL) extra_headers = "";
if (path->len == 0) { *path = mg_mk_str("/"); }
if (host->len == 0) { *host = mg_mk_str(""); }
mg_printf(nc, "%s %.*s HTTP/1.1\r\nHost: %.*s\r\nContent-Length: %" SIZE_T_FMT
"\r\n%.*s%s\r\n%s",
(post_data[0] == '\0' ? "GET" : "POST"), (int)path->len, path->p,
(int)(path->p - host->p), host->p, strlen(post_data), (int)auth.len,
(auth.buf == NULL ? "" : auth.buf), extra_headers, post_data);
mbuf_free(&auth);
}
4、HTTP客户端-短连接
//http_client.h
#pragma once
#include <atomic>
#include "http_message.h"
//线程安全
class HttpClient
{
public:
HttpClient() {}
~HttpClient() {}
inline void SetResponseHandler(ResponseHandler handler)
{ m_handler = handler; }
void OnClientEvent(struct mg_connection *connection,
int event_type,
void *event_data);
void SendRequest(const std::string &uri,
std::string & extra_header,
std::string & post_data,
uint32_t timeout_ms,
ResponseHandler handler = nullptr);
private:
ResponseHandler m_handler;//默认handle
};
struct HTTP_CLIENT_USER_DATA
{
HttpClient * obj;//HttpClient对象
bool exit=false;
ResponseHandler handler;//每个请求的专属handle
};
static void CB_OnClientEvent(struct mg_connection *connection, int event_type, void *event_data, void * user_data)
{
if (user_data)
{
((HTTP_CLIENT_USER_DATA *)user_data)->obj->OnClientEvent(connection, event_type, event_data);
}
}
//http_client.cpp
#include "http_client.h"
#include "glog/init_glog.h"
void HttpClient::OnClientEvent(struct mg_connection *connection, int event_type, void *event_data)
{
auto user_data=(HTTP_CLIENT_USER_DATA *)(connection->user_data);
switch (event_type)
{
case MG_EV_CONNECT:
{
int connect_status = *(int *)event_data;
if (connect_status != 0)
{
LOG_INFO_GLOG << "fail to connect http server,ec= " << connect_status;
user_data->exit = true;
}
break;
}
case MG_EV_HTTP_REPLY:
{
http_message *hm = (struct http_message *)event_data;
if (hm->resp_code == 200)
{
if (user_data->handler)
{
user_data->handler(hm);
}
else if (m_handler)
{
m_handler(hm);
}
}
connection->flags |= MG_F_SEND_AND_CLOSE;
user_data->exit = true; // 每次收到请求后关闭本次连接,重置标记
break;
}
case MG_EV_CLOSE:
{
if (!user_data->exit)
{
LOG_INFO_GLOG << "Server closed connection";
user_data->exit = true;
};
break;
}
default:
break;
}
}
void HttpClient::SendRequest(const std::string &uri,
std::string & extra_header,
std::string & post_data,
uint32_t timeout_ms,
ResponseHandler handler)
{
LOG_INFO_GLOG << "Send Request:" << uri;
extra_header += "Connection: close\r\n";
HTTP_CLIENT_USER_DATA user_data;
user_data.obj = this;
user_data.exit = false;
user_data.handler = std::move(handler);
mg_mgr mgr;
mg_mgr_init(&mgr, NULL);
auto connection = mg_connect_http(&mgr,
CB_OnClientEvent,
&user_data, uri.c_str(),
extra_header.c_str(),
post_data.c_str());
//回调和事件轮询在同一个线程
while (user_data.exit==false && timeout_ms>0)
{
timeout_ms -= 500;
mg_mgr_poll(&mgr, 500);
}
mg_mgr_free(&mgr);
}
5、代码说明
-
以上代码用到了glog来记录日志。若你的开发环境中没有这个库,可以将其中相关的代码去除或采用std::cout来替代。
-
使用时需要在mongoose中使能:MG_ENABLE_CALLBACK_USERDATA。
-
HTTP客户端-长连接和HTTP服务端的实现是支持websocket的。
-
HTTP客户端-长连接和HTTP客户端-短连接均支持线程安全。
-
mongoose中mg_connection *connection的实现方式类似链表,也就是说通过对一个连接进行遍历,得到其他连接。
但本文中将HTTP客户端-长连接实现为:一个HttpClientLong对象对应一个连接m_connection,同时对应一个m_remote_host。
若企图通过同一个HttpClientLong对象与不同的对端服务通信,将提示错误:"err:当前连接的对端不是用户请求的对端"。
-
下图为HTTP客户端-短连接访问HTTP服务端的抓包过程分析,客户端IP:10.33.38.71;服务端:10.33.38.128
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dKwrM2Eu-1592141308987)(C:\Users\36262\AppData\Roaming\Typora\typora-user-images\image-20200601135819047.png)]
-
下图为HTTP客户端-长连接访问HTTP服务端的抓包过程分析,客户端IP:10.33.38.71;服务端:10.33.38.128
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7mF6heKb-1592141308990)(C:\Users\36262\AppData\Roaming\Typora\typora-user-images\image-20200601141420421.png)]