C++實現輕量級極簡httpserver和httpclient(轉)


轉自: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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM