轉自:https://blog.csdn.net/u012234115/article/details/79596826
一般來說,C++的項目多是偏底層,不怎么需要跟http打交道,但有時候又需要在C++后端項目中加入一些簡單 http接口,比如游戲運營服務器,金融交易監控服務等。
但是傳統的實現方法比如采用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 <string> #include <unordered_map> #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 OnHttpEvent(mg_connection *connection, int event_type, void *event_data); static void HandleEvent(mg_connection *connection, http_message *http_req); static void SendRsp(mg_connection *connection, std::string rsp); 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:其他設置 } bool HttpServer::Start() { mg_mgr_init(&m_mgr, NULL); mg_connection *connection = mg_bind(&m_mgr, m_port.c_str(), OnHttpEvent); if (connection == NULL) return false; 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::OnHttpEvent(mg_connection *connection, int event_type, void *event_data) { http_message *http_req = (http_message *)event_data; switch (event_type) { case MG_EV_HTTP_REQUEST: HandleEvent(connection, http_req); break; default: break; } } 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::SendRsp(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::HandleEvent(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, SendRsp); } // 其他請求 if (route_check(http_req, "/")) // index page mg_serve_http(connection, http_req, s_server_option); else if (route_check(http_req, "/api/hello")) { // 直接回傳 SendRsp(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); SendRsp(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"); } } bool HttpServer::Close() { mg_mgr_free(&m_mgr); return true; }
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"> $(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); } }); }); </script> </head> <body> <h1>c++ httpserver demo</h1> <p> front end request </p> <h2>GET</h2> <div> <button id="btn">get request</button> </div> <div> <label>Result1:</label> <span id="result1"> </span> </div> <h2>POST</h2> <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> </body> </html>
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; 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->RemoveHandler("/api/fun3"); // http_server->RemoveHandler("/api/fun3"); http_server->Start(); return 0; }
- 服務器支持host靜態頁面資源,也支持rest api調用
- 需要手動設置loop polling的時間間隔
- 可以自定義靜態頁面根路徑,注冊和解注冊自定義api函數回調
- 某些變量必須聲明定義成全局或者靜態變量
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); }
- client每次請求都是一個獨立的請求
- 請求函數中加入回調用於處理網絡返回
測試
可以用瀏覽器、或者其他工具提交url,查看網絡請求返回
GET
請求
http://localhost:7999/api/hello
結果
{ "result": welcome to httpserver }
POST
請求
http://localhost:7999/api/sum?n1=20&n2=18
結果
{ "result": 38 }
源碼
csdn:demo
github: demo