C++11 封装boost::beast库实现简单的异步HTTP Server和HTTPClient

这个工程做了很久,之间笔者去医院做了手术,后面才回来做这个东西。
因为boost::beast库是基于boost::asio完成的,支持基于proactor模型的异步请求,为了避免要实现一个简单的HTTP请求,还需要加入三方库的情况出现,笔者决定基于boost::beast库的异步demo进行二次开发。
使用模板加宏进行二次开发后,效果尚可。
完整代码地址,
https://gitlab.com/zhuge20100104/cpp_practice/-/tree/master/design_patterns/70_communication_proxy?ref_type=heads

本模块还有两个部分没有进行验证,纯属自娱自乐,切勿用于生产。

  1. 尚未验证 多线程情况是否会发生 data race。
  2. 尚未实现 URL encode和decode。

本工程基于conan 包管理器实现。
conanfile.txt, OpenCV非必须项,可以去掉

[requires]
boost/1.72.0
opencv/4.5.3



[generators]
cmake

CMakeLists.txt

cmake_minimum_required(VERSION 3.3)

project(70_communication_proxy)

set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:/usr/local/lib/pkgconfig/")

set ( CMAKE_CXX_FLAGS "-pthread")
set(CMAKE_CXX_STANDARD 17)
add_definitions(-g)


include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()



include_directories(${INCLUDE_DIRS})
LINK_DIRECTORIES(${LINK_DIRS})

file( GLOB main_file_list ${CMAKE_CURRENT_SOURCE_DIR}/*_test.cpp) 
file( GLOB sources ${CMAKE_CURRENT_SOURCE_DIR}/*.cc)

foreach( main_file ${main_file_list} )
    file(RELATIVE_PATH filename ${CMAKE_CURRENT_SOURCE_DIR} ${main_file})
    string(REPLACE ".cpp" "" file ${filename})
    add_executable(${file}  ${main_file} ${sources})
    target_link_libraries(${file} PRIVATE  pthread)
    target_link_libraries(${file} PRIVATE  ${CONAN_LIBS})


endforeach( main_file ${main_file_list})

http_client.hpp

#ifndef _HTTP_CLIENT_HPP_
#define _HTTP_CLIENT_HPP_

//
// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/beast
//

//------------------------------------------------------------------------------
//
// Example: HTTP client, asynchronous
//
//------------------------------------------------------------------------------

#include "http_common.h"
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/strand.hpp>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <string>

namespace beast = boost::beast;         // from <boost/beast.hpp>
namespace http = beast::http;           // from <boost/beast/http.hpp>
namespace net = boost::asio;            // from <boost/asio.hpp>
using tcp = boost::asio::ip::tcp;       // from <boost/asio/ip/tcp.hpp>

//------------------------------------------------------------------------------

// Report a failure


// Performs an HTTP GET and prints the response
template <typename Req, typename Res>
class session : public std::enable_shared_from_this<session<Req, Res>>
{
    tcp::resolver resolver_;
    beast::tcp_stream stream_;
    beast::flat_buffer buffer_; // (Must persist between reads)
    http::request<Req>& req_;
    http::response<Res>& res_;

public:
    // Objects are constructed with a strand to
    // ensure that handlers do not execute concurrently.
   
    explicit
    session(net::io_context& ioc, http::request<Req>& req,  http::response<Res>& res)
        : resolver_(net::make_strand(ioc))
        , stream_(net::make_strand(ioc)),
        req_(req),
        res_(res)
    {
    }

    http::request<Req>& get_request() {
        return req_;
    } 

    http::response<Res>& get_response() {
        return res_;
    }

    // Start the asynchronous operation
    void
    run(
        char const* host,
        char const* port,
        int version)
    {
        // Look up the domain name
        resolver_.async_resolve(
            host,
            port,
            beast::bind_front_handler(
                &session::on_resolve,
                this->shared_from_this()));
    }

    void
    on_resolve(
        beast::error_code ec,
        tcp::resolver::results_type results)
    {
        if(ec)
            return fail(ec, "resolve");

        // Set a timeout on the operation
        stream_.expires_after(std::chrono::seconds(30));

        // Make the connection on the IP address we get from a lookup
        stream_.async_connect(
            results,
            beast::bind_front_handler(
                &session::on_connect,
                this->shared_from_this()));
    }

    void
    on_connect(beast::error_code ec, tcp::resolver::results_type::endpoint_type)
    {
        if(ec)
            return fail(ec, "connect");

        // Set a timeout on the operation
        stream_.expires_after(std::chrono::seconds(30));

        // Send the HTTP request to the remote host
        http::async_write(stream_, req_,
            beast::bind_front_handler(
                &session::on_write,
                this->shared_from_this()));
    }

    void
    on_write(
        beast::error_code ec,
        std::size_t bytes_transferred)
    {
        boost::ignore_unused(bytes_transferred);

        if(ec)
            return fail(ec, "write");
        
        // Receive the HTTP response
        http::async_read(stream_, buffer_, res_,
            beast::bind_front_handler(
                &session::on_read,
                this->shared_from_this()));
    }

    void
    on_read(
        beast::error_code ec,
        std::size_t bytes_transferred)
    {
        boost::ignore_unused(bytes_transferred);

        if(ec)
            return fail(ec, "read");

        // Write the message to standard out
        // std::cout << res_ << std::endl;

        // Gracefully close the socket
        stream_.socket().shutdown(tcp::socket::shutdown_both, ec);

        // not_connected happens sometimes so don't bother reporting it.
        if(ec && ec != beast::errc::not_connected)
            return fail(ec, "shutdown");

        // If we get here then the connection is closed gracefully
    }
};

template <typename Req, typename Res>
struct ReqContext {
    http::request<Req> req;
    http::response<Res> res;

    ReqContext(http::request<Req> const& req_, http::response<Res> const&  res_): req(req_), res(res_) {} 
};

template <typename Req, typename Res>
struct ClientSession {
    net::io_context& ioc;
    http::request<Req>& req;
    http::response<Res>& res;

    std::string host;
    std::string port;
    std::string target;

    ClientSession(net::io_context& ioc_, http::request<Req>& req_,
    http::response<Res>& res_,
    std::string host_,
    std::string port_,
    std::string target_): ioc(ioc_), req(req_), res(res_), host(host_), port(port_), target(target_) {}

    void Get() {
        req.version(11);
        req.method(http::verb::get);
        req.target(target);
        req.set(http::field::host, host);
        req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
        std::make_shared<session<Req, Res>>(ioc, req, res)->run(host.data(), port.data(), 11);
        ioc.run();
        // Run the I/O service. The call will return when
        // the get operation is complete.
    }
    
    void Post(std::string const& body) {
        req.version(11);
        req.method(http::verb::post);
        req.target(target);
        req.set(http::field::host, host);
        req.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING);
        req.set(http::field::content_type, "application/json");
        req.body() =  body.data();
        req.prepare_payload();
        std::make_shared<session<Req, Res>>(ioc, req, res)->run(host.data(), port.data(), 11);
        ioc.run();
    }
};

template <typename Req, typename Res>
ReqContext<Req, Res> GetRequest(std::string const& host, std::string const& port, std::string const& target) {
    net::io_context ioc;
    http::request<Req> req;
    http::response<Res> res;
    ClientSession<Req, Res>  client(ioc, req, res, host, port, target);
    client.Get();
    return ReqContext<Req ,  Res>(req, res);
}

template <typename Req, typename Res>
ReqContext<Req, Res> PostRequest(std::string const& host, std::string const& port, std::string const& target, 
    std::string const& body) {
    net::io_context ioc;
    http::request<Req> req;
    http::response<Res> res;
    ClientSession<Req, Res>  client(ioc, req, res, host, port, target);
    client.Post(body);
    return ReqContext<Req ,  Res>(req, res);
}

#define GET GetRequest<http::empty_body, http::string_body>
#define POST PostRequest<http::string_body, http::string_body>

#endif

http_server.hpp

#ifndef _FREDRIC_HTTP_SERVER_HPP_
#define _FREDRIC_HTTP_SERVER_HPP_
//
// Copyright (c) 2016-2019 Vinnie Falco (vinnie dot falco at gmail dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
// Official repository: https://github.com/boostorg/beast
//

//------------------------------------------------------------------------------
//
// Example: HTTP server, asynchronous
//
//------------------------------------------------------------------------------
#include "http_common.h"
#include <boost/functional/hash.hpp>
#include <map>
#include <boost/algorithm/string.hpp>
#include <regex>


#define  MAKE_REQUEST_RESP(request_name,  content_type_, status_code, request)    auto const request_name = \
    [&request](beast::string_view resp_content) \
    {   \
        http::response<http::string_body> res{status_code, request.version()}; \
        res.set(http::field::server, BOOST_BEAST_VERSION_STRING); \
        res.set(http::field::content_type, content_type_); \
        res.keep_alive(request.keep_alive()); \
        res.body() = std::string(resp_content); \
        res.prepare_payload(); \
        return res; \
    }; \


struct RequestKey {
    http::verb method;
    std::string target;

    bool operator<(RequestKey const& other) const {
        return (int)method < (int)(other.method) && target < other.target;
    }
};

template<
    class Body,
    class Send>
struct Function {

    Function(std::string const& long_target_,    
        std::function<void(http::request<Body>&, Send&, std::vector<std::string> const&)> func_):
         long_match_regex(long_target_), callback_func(func_) {

    }
    std::string long_match_regex;
    std::function<void(http::request<Body>&, Send&, std::vector<std::string> const&)> callback_func; 
    std::vector<std::string> params;
    bool is_reg{false};

    bool operator<(Function<Body, Send> const& other) const {
        return long_match_regex < other.long_match_regex;
    }
};

struct TargetResult {
    std::string short_target;
    std::string long_target;
    bool is_reg {false};
    bool is_query {false};
};


template<
    class Body,
    class Send>
struct RequestHandler {
    
    static std::map<RequestKey,  std::set<Function<Body, Send>> > callback_map;
    /**
     * NOTE: 带问号的URL需要单独处理,去掉问号后面的,
     * 问号 前面的 作为 target 和 long_match_regex
     * 
     * RequestKey 里面保存 一级path, 例如, 
     * http::verb::get   api
     *
     *  long_match_regex 里面保存 二级path,例如,
     *  api/(.*)/(.*) 
     * 
     *  1. api/(.*)/(.*) 
     *  2. api?a=b&c=d  
     *  3. api   --> find 处理的是第三种情况
     * 
     * 
     * struct Funcion {
     *      std::string long_match_regex;
     *      std::function<void(http::request<Body>&, Send&)> callback_func;  
     * };
     * 
     * std::map<RequestKey, std::set<Function>>
    */

   static void preprocess_target(std::string const& target, TargetResult& tar_result) {
            tar_result.short_target = target;
            tar_result.long_target = target;
            if(target.find("?") != std::string::npos)  {
                tar_result.short_target = std::string(target.begin() ,  target.begin() + target.find("?"));
                tar_result.long_target =  tar_result.short_target;
                tar_result.is_query = true;
            } else if(target.find(R"((.*))") != std::string::npos) {
                tar_result.short_target = boost::algorithm::replace_all_copy(target, R"(/(.*))", "");
                tar_result.long_target = target;
                tar_result.is_reg = true;
            }
   }

    static void register_request(RequestKey const& key, 
        std::function<void(http::request<Body>&, Send&, std::vector<std::string> const&)> const& callback)  {
            std::string target = key.target;
            TargetResult tar_result; 
            preprocess_target(target, tar_result);

            RequestKey fixed_key = RequestKey{key.method, tar_result.short_target};

            if(callback_map.find(fixed_key) == callback_map.end()) {
                callback_map[fixed_key] = std::set<Function<Body, Send>>();
            }

            Function<Body, Send> func_{tar_result.long_target, callback};
            func_.is_reg = tar_result.is_reg;
            callback_map[fixed_key].insert(func_);
    }
    
    static void handle_request(
        beast::string_view doc_root,
        http::request<Body>&& req,
        Send& send) {

        // 处理 target 情况
        // 1. api/(.*)/(.*) 
        // 2. api?a=b&c=d  
        // 3. api   --> find 处理的是第三种情况

        /** 
         * NOTE: 带问号的URL需要单独处理,去掉问号后面的,
         *  问号 前面的 作为 target 和 long_match_regex
         *
         * api/pingpong 短的, api/pingpong/(.*)
         * 例如 api/pingpong/hello
         * 1. 先找 api/pingpong,看有没有, {short_target}
         * 2. 再根据正则表达式匹配,  {long_target.match(req.target())}
        */

        auto target = std::string(req.target());

        TargetResult tar_result; 
        preprocess_target(target, tar_result);
       
        RequestKey req_key{req.method(),  tar_result.short_target};
        bool has_matched {false};

        if(callback_map.find(req_key) != callback_map.end()) {
            auto& callback_set = callback_map[req_key];
            for(auto& callback_func: callback_set) {
                auto match_regex = callback_func.long_match_regex;
                auto func = callback_func.callback_func;
                auto params = callback_func.params;
                auto is_reg = callback_func.is_reg;
                std::string actual_req_target = target;
                // 处理正则表达式匹配, /api/pingpong/(.*)/(.*)
                if(is_reg) {
                    std::regex rgx(match_regex);
                    std::smatch matches;
                    
                    params.clear();
                    if(std::regex_search(actual_req_target, matches, rgx)) {
                        std::cout << "Match found\n";
                        for (size_t i = 0; i < matches.size(); ++i) {
                            // 第一个match 是整个字符串,跳过
                            if(i!=0) {
                                params.push_back(matches[i].str());
                            }
                        }
                        func(req, send, params);
                        has_matched = true;
                        break;
                    }   // 处理问号匹配  /query?a=b&c=d
                } else if(tar_result.is_query) {
                    params.clear();
                    if(match_regex == tar_result.long_target) {
                        std::cout << "Match found\n";
                        std::vector<std::string> result;
                        boost::split(result, actual_req_target, boost::is_any_of("?"));
                        std::string params_str = result[1];
                        boost::split(params, params_str, boost::is_any_of("&"));
                        func(req, send, params);
                        has_matched = true;
                        break;
                    }   // 处理普通匹配 /api/aa 
                } else {
                    if(match_regex == actual_req_target) {
                        std::cout << "Match found\n";
                        func(req, send, params);
                        has_matched = true;
                        break;
                    }
                }
            }
        }
        
        // 都没匹配到,返回默认not found 页面
        if(!has_matched) {
            std::cout << "Match not found\n";
            MAKE_REQUEST_RESP(req_resp_not_found, "text/html", http::status::not_found, req);
            return send(req_resp_not_found("The resource '" + std::string(req.target()) + "' was not found."));
        } 
    }
};

template<
    class Body,
    class Send>
 std::map<RequestKey, std::set<Function<Body, Send>> >  RequestHandler<Body, Send>::callback_map;



//------------------------------------------------------------------------------

// Handles an HTTP server connection
class session : public std::enable_shared_from_this<session>
{
public:
    // This is the C++11 equivalent of a generic lambda.
    // The function object is used to send an HTTP message.
    struct send_lambda
    {
        session& self_;

        explicit
        send_lambda(session& self)
            : self_(self)
        {
        }

        template<bool isRequest, class Body, class Fields>
        void
        operator()(http::message<isRequest, Body, Fields>&& msg) const
        {
            // The lifetime of the message has to extend
            // for the duration of the async operation so
            // we use a shared_ptr to manage it.
            auto sp = std::make_shared<
                http::message<isRequest, Body, Fields>>(std::move(msg));

            // Store a type-erased version of the shared
            // pointer in the class to keep it alive.
            self_.res_ = sp;

            // Write the response
            http::async_write(
                self_.stream_,
                *sp,
                beast::bind_front_handler(
                    &session::on_write,
                    self_.shared_from_this(),
                    sp->need_eof()));
        }
    };

private:
    beast::tcp_stream stream_;
    beast::flat_buffer buffer_;
    std::shared_ptr<std::string const> doc_root_;
    http::request<http::string_body> req_;
    std::shared_ptr<void> res_;
    send_lambda lambda_;

public:
    // Take ownership of the stream
    session(
        tcp::socket&& socket,
        std::shared_ptr<std::string const> const& doc_root)
        : stream_(std::move(socket))
        , doc_root_(doc_root)
        , lambda_(*this)
    {
    }

    // Start the asynchronous operation
    void
    run()
    {
        // We need to be executing within a strand to perform async operations
        // on the I/O objects in this session. Although not strictly necessary
        // for single-threaded contexts, this example code is written to be
        // thread-safe by default.
        net::dispatch(stream_.get_executor(),
                      beast::bind_front_handler(
                          &session::do_read,
                          shared_from_this()));
    }

    void
    do_read()
    {
        // Make the request empty before reading,
        // otherwise the operation behavior is undefined.
        req_ = {};

        // Set the timeout.
        stream_.expires_after(std::chrono::seconds(30));

        // Read a request
        http::async_read(stream_, buffer_, req_,
            beast::bind_front_handler(
                &session::on_read,
                shared_from_this()));
    }

    void
    on_read(
        beast::error_code ec,
        std::size_t bytes_transferred)
    {
        boost::ignore_unused(bytes_transferred);

        // This means they closed the connection
        if(ec == http::error::end_of_stream)
            return do_close();

        if(ec)
            return fail(ec, "read");

        // Send the response
        RequestHandler<http::string_body, send_lambda>::handle_request(*doc_root_, std::move(req_), lambda_);
    }

    void
    on_write(
        bool close,
        beast::error_code ec,
        std::size_t bytes_transferred)
    {
        boost::ignore_unused(bytes_transferred);

        if(ec)
            return fail(ec, "write");

        if(close)
        {
            // This means we should close the connection, usually because
            // the response indicated the "Connection: close" semantic.
            return do_close();
        }

        // We're done with the response so delete it
        res_ = nullptr;

        // Read another request
        do_read();
    }

    void
    do_close()
    {
        // Send a TCP shutdown
        beast::error_code ec;
        stream_.socket().shutdown(tcp::socket::shutdown_send, ec);

        // At this point the connection is closed gracefully
    }
};

//------------------------------------------------------------------------------

// Accepts incoming connections and launches the sessions
class listener : public std::enable_shared_from_this<listener>
{
    net::io_context& ioc_;
    tcp::acceptor acceptor_;
    std::shared_ptr<std::string const> doc_root_;

public:
    listener(
        net::io_context& ioc,
        tcp::endpoint endpoint,
        std::shared_ptr<std::string const> const& doc_root)
        : ioc_(ioc)
        , acceptor_(net::make_strand(ioc))
        , doc_root_(doc_root)
    {
        beast::error_code ec;

        // Open the acceptor
        acceptor_.open(endpoint.protocol(), ec);
        if(ec)
        {
            fail(ec, "open");
            return;
        }

        // Allow address reuse
        acceptor_.set_option(net::socket_base::reuse_address(true), ec);
        if(ec)
        {
            fail(ec, "set_option");
            return;
        }

        // Bind to the server address
        acceptor_.bind(endpoint, ec);
        if(ec)
        {
            fail(ec, "bind");
            return;
        }

        // Start listening for connections
        acceptor_.listen(
            net::socket_base::max_listen_connections, ec);
        if(ec)
        {
            fail(ec, "listen");
            return;
        }
    }

    // Start accepting incoming connections
    void
    run()
    {
        do_accept();
    }

private:
    void
    do_accept()
    {
        // The new connection gets its own strand
        acceptor_.async_accept(
            net::make_strand(ioc_),
            beast::bind_front_handler(
                &listener::on_accept,
                shared_from_this()));
    }

    void
    on_accept(beast::error_code ec, tcp::socket socket)
    {
        if(ec)
        {
            fail(ec, "accept");
        }
        else
        {
            // Create the session and run it
            std::make_shared<session>(
                std::move(socket),
                doc_root_)->run();
        }

        // Accept another connection
        do_accept();
    }
};

//------------------------------------------------------------------------------


struct Server {
    Server(std::string const& host_, std::string const& port_, std::string const& doc_root_, int threads_)
        : ioc{threads_}, host(host_), port(static_cast<unsigned short>(std::atoi(port_.data()))),
        doc_root(doc_root_), threads(std::max<int>(1, threads_))  {

    }

    void run() {
        auto address = net::ip::make_address(host);
        auto doc_root_inner = std::make_shared<std::string>(doc_root.data());
    
        // Create and launch a listening port
        std::make_shared<listener>(
            ioc,
            tcp::endpoint{address, port},
            doc_root_inner)->run();

        // Run the I/O service on the requested number of threads
        std::vector<std::thread> v;
        v.reserve(threads - 1);
        for(auto i = threads - 1; i > 0; --i)
            v.emplace_back(
            [this]
            {
                ioc.run();
            });
        ioc.run();
    }

private:
    net::io_context ioc;
    std::string host;
    unsigned short port;
    std::string doc_root;
    int threads;
};

#define  HTTPRequestHandler  RequestHandler<http::string_body, session::send_lambda>
#define  REGIST_REQ(method, target, func)     HTTPRequestHandler::register_request({method, target}, func)

#define  ReqType http::request<http::string_body>
#define  SendType session::send_lambda
#endif

http_common.h

#ifndef _FREDRIC_HTTP_COMMON_H_
#define _FREDRIC_HTTP_COMMON_H_

//------------------------------------------------------------------------------
//
// Example: HTTP server, asynchronous
//
//------------------------------------------------------------------------------
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <boost/asio/dispatch.hpp>
#include <boost/asio/strand.hpp>
#include <boost/config.hpp>
#include <algorithm>
#include <cstdlib>
#include <functional>
#include <iostream>
#include <memory>
#include <string>
#include <thread>
#include <vector>


namespace beast = boost::beast;         // from <boost/beast.hpp>
namespace http = beast::http;           // from <boost/beast/http.hpp>
namespace net = boost::asio;            // from <boost/asio.hpp>
using tcp = boost::asio::ip::tcp;       // from <boost/asio/ip/tcp.hpp>

void
fail(beast::error_code ec, char const* what);

std::string path_cat(beast::string_view base, beast::string_view path);

#endif

http_common.cc

#include "http_common.h"

void fail(beast::error_code ec, char const* what) {
    std::cerr << what << ": " << ec.message() << "\n";
}



// Append an HTTP rel-path to a local filesystem path.
// The returned path is normalized for the platform.
std::string path_cat(beast::string_view base, beast::string_view path){
    if(base.empty())
        return std::string(path);
    std::string result(base);
#ifdef BOOST_MSVC
    char constexpr path_separator = '\\';
    if(result.back() == path_separator)
        result.resize(result.size() - 1);
    result.append(path.data(), path.size());
    for(auto& c : result)
        if(c == '/')
            c = path_separator;
#else
    char constexpr path_separator = '/';
    if(result.back() == path_separator)
        result.resize(result.size() - 1);
    result.append(path.data(), path.size());
#endif
    return result;
}

server_test.cpp

#include "http_server.hpp"
#include <sstream>
#include "json.hpp"

using json = nlohmann::json;

int main(int argc, char* argv[])
{
    // Check command line arguments.
    if (argc != 5)
    {
        std::cerr <<
            "Usage: http-server-async <address> <port> <doc_root> <threads>\n" <<
            "Example:\n" <<
            "    http-server-async 0.0.0.0 8080 . 1\n";
        return EXIT_FAILURE;
    }
    
    std::string host = argv[1];
    std::string port = argv[2];
    std::string doc_root = argv[3];
    int thread = std::atoi(argv[4]);

    Server s(host, port, doc_root, thread);

    REGIST_REQ(http::verb::get, "/",  [](ReqType& req, SendType& send, std::vector<std::string> const&  params) {
           MAKE_REQUEST_RESP(req_resp_html, "text/html", http::status::ok, req);
            return send(req_resp_html("Hello World!"));
    });

    REGIST_REQ(http::verb::post, "/plus",  [](ReqType& req, SendType& send, std::vector<std::string> const& params) {
            MAKE_REQUEST_RESP(req_resp_json, "application/json", http::status::ok, req);
            std::stringstream body_ss;
            body_ss << req.body();
            auto js_params = json::parse(body_ss.str());
            int x = js_params["x"].get<int>();
            int y = js_params["y"].get<int>();
            int sum = x + y;
            json result;
            result["sum"] = sum;
            std::string res = result.dump(4);
            return send(req_resp_json(res.data()));
    });

    REGIST_REQ(http::verb::get, R"(/api/pingpong/(.*))",  [](ReqType& req, SendType& send, std::vector<std::string> const& params) {
            MAKE_REQUEST_RESP(req_resp_html, "text/html", http::status::ok, req);
            std::stringstream ss;
            ss << params[0] << " " << "pong";
            return send(req_resp_html(ss.str()));
    });

    REGIST_REQ(http::verb::get, R"(/query)",  [](ReqType& req, SendType& send, std::vector<std::string> const& params) {
            MAKE_REQUEST_RESP(req_resp_html, "text/html", http::status::ok, req);
            std::stringstream ss;
            ss << params[0] << " " << params[1] << " " << "Query";
            return send(req_resp_html(ss.str()));
    });


    s.run();    
    return EXIT_SUCCESS;
}

client_test.cpp

#include "http_client.hpp"


void test_get() {
    auto res = GET("127.0.0.1", "8080", "/conaninfo.txt");

    std::cout << "Result Code:" << res.res.result_int() << std::endl;
    std::cout << res.res << std::endl;

    res = GET("127.0.0.1", "8080", "/");

    // Get Status Code
    std::cout << "Result Code:" << res.res.result_int() << std::endl;
    // Get Response Body
    std::cout << res.res.body() << std::endl;

    std::cout << "Get all response fields using iteration: \n";
    for(auto const& field : res.res) {
        std::cout << field.name() << "---> " << field.value() << "\n";
    } 

    std::cout << "Get all response fields using key names: \n";
    std::cout << "Server: " << res.res[http::field::server] << std::endl;
    std::cout << "Content Type: " << res.res[http::field::content_type] << std::endl;
    std::cout << "Content Length: " << res.res[http::field::content_length] << std::endl;
}


void test_post() {
    std::string body = R"({"x": 3, "y": 2})";
    auto res1 = POST("127.0.0.1", "8080", "/plus", body);
      // Get Status Code
    std::cout << "Result Code:" << res1.res.result_int() << std::endl;
    // Get Response Body
    std::cout << res1.res.body() << std::endl;

    std::cout << "Get all response fields using iteration: \n";
    for(auto const& field : res1.res) {
        std::cout << field.name() << "---> " << field.value() << "\n";
    } 
}

void test_pingpong() {
    auto res = GET("127.0.0.1", "8080", "/api/pingpong/hello");
    std::cout << res.res.body() << std::endl;

    res = GET("127.0.0.1", "8080", "/api/pingpong/ping");
    std::cout << res.res.body() << std::endl;
}

void test_query_param() {
    auto res = GET("127.0.0.1", "8080", "/query?a=b&c=d");
    std::cout << res.res.body() << std::endl;
}


int main(int argc, char** argv) {    
    test_get();
    test_post();
    test_pingpong();
    test_query_param();
    return EXIT_SUCCESS;
}

程序输入如下,


image.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容