Thread: http server using boost asio

  1. #1
    Registered User
    Join Date
    Jan 2014
    Posts
    139

    http server using boost asio

    Hello,

    I want to write a console application that
    1. has a http server (listener) that can receive data
    2. take that data and push it to several nodes
    3. every 10 minutes call an external api and push that data out to the nodes


    My main concern is having one set of variables that can be shared by processes 1 2 and 3 above while keeping the threads from blocking.


    Boost asio has 4 examples, and I am wondering which one would be the best for my needs.

    These are the examples they provide
    C++03 Examples - 1.66.0

    1. simple single-threaded server implementation of HTTP 1.0. It demonstrates how to perform a clean shutdown by cancelling all outstanding asynchronous operations.
    2. An HTTP server using an io_context-per-CPU design.
    3. An HTTP server using a single io_context and a thread pool calling io_context::run().
    4.
    A single-threaded HTTP server implemented using stackless coroutines.

    Which one of the above 4 would be best suited for what I am tying to do?
    Last edited by EverydayDiesel; 01-15-2018 at 10:18 AM.

  2. #2
    Registered User
    Join Date
    Oct 2006
    Posts
    3,445
    Boost 1.66 introduced the Beast library, which handles all the boilerplate of HTTP connections. You hand off an established TCP or SSL connection to it, and it handles the rest.
    What can this strange device be?
    When I touch it, it gives forth a sound
    It's got wires that vibrate and give music
    What can this thing be that I found?

  3. #3
    Registered User
    Join Date
    Oct 2006
    Posts
    3,445
    The author of Beast hangs out on the official C++ slack, where he is a very active user. You can sign up here Join Cpplang on Slack!
    What can this strange device be?
    When I touch it, it gives forth a sound
    It's got wires that vibrate and give music
    What can this thing be that I found?

  4. #4
    Registered User Chris87's Avatar
    Join Date
    Dec 2007
    Posts
    131
    I was just about to point out Beast, myself, lol

  5. #5
    Registered User
    Join Date
    Jan 2014
    Posts
    139
    Thank both of you for your responses! I will look into it! Thanks again

  6. #6
    Registered User
    Join Date
    Jan 2014
    Posts
    139
    So I have been working on this and I am at a point now where maybe you guys can help me.


    Essentially what I would like it to do is return a variable that, for this purpose, contains 'hello world'.

    I need to update this code to be a string instead of http:file_body::value_type

    Code:
        http::file_body::value_type body;
        body.open(path.c_str(), boost::beast::file_mode::scan, ec);
    
    
        // Handle the case where the file doesn't exist
        if(ec == boost::system::errc::no_such_file_or_directory)
            return send(not_found(req.target()));
    
    
        // Handle an unknown error
        if(ec)
            return send(server_error(ec.message()));
    
    // this is what I am trying to do but obviously wont work
        body = "hello world"; 
    
    
        // Respond to HEAD request
        if(req.method() == http::verb::head)
        {
            http::response<http::empty_body> res{http::status::ok, req.version()};
            res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
            res.set(http::field::content_type, mime_type(path));
            res.content_length(body.size());
            res.keep_alive(req.keep_alive());
            return send(std::move(res));
        }
    
    
        // Respond to GET request
        http::response<http::file_body> res{
            std::piecewise_construct,
            std::make_tuple(std::move(body)),
            std::make_tuple(http::status::ok, req.version())};
        res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
        res.set(http::field::content_type, mime_type(path));
        res.content_length(body.size());
        res.keep_alive(req.keep_alive());
        return send(std::move(res));





    // full code
    Code:
    //
    // Copyright (c) 2016-2017 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, synchronous
    //
    //------------------------------------------------------------------------------
    
    
    #include <boost/beast/core.hpp>
    #include <boost/beast/http.hpp>
    #include <boost/beast/version.hpp>
    #include <boost/asio/ip/tcp.hpp>
    #include <boost/config.hpp>
    #include <cstdlib>
    #include <iostream>
    #include <memory>
    #include <string>
    #include <thread>
    
    
    using tcp = boost::asio::ip::tcp;       // from <boost/asio/ip/tcp.hpp>
    namespace http = boost::beast::http;    // from <boost/beast/http.hpp>
    
    
    //------------------------------------------------------------------------------
    
    
    // Return a reasonable mime type based on the extension of a file.
    boost::beast::string_view
    mime_type(boost::beast::string_view path)
    {
        using boost::beast::iequals;
        auto const ext = [&path]
        {
            auto const pos = path.rfind(".");
            if(pos == boost::beast::string_view::npos)
                return boost::beast::string_view{};
            return path.substr(pos);
        }();
        if(iequals(ext, ".htm"))  return "text/html";
        if(iequals(ext, ".html")) return "text/html";
        if(iequals(ext, ".php"))  return "text/html";
        if(iequals(ext, ".css"))  return "text/css";
        if(iequals(ext, ".txt"))  return "text/plain";
        if(iequals(ext, ".js"))   return "application/javascript";
        if(iequals(ext, ".json")) return "application/json";
        if(iequals(ext, ".xml"))  return "application/xml";
        if(iequals(ext, ".swf"))  return "application/x-shockwave-flash";
        if(iequals(ext, ".flv"))  return "video/x-flv";
        if(iequals(ext, ".png"))  return "image/png";
        if(iequals(ext, ".jpe"))  return "image/jpeg";
        if(iequals(ext, ".jpeg")) return "image/jpeg";
        if(iequals(ext, ".jpg"))  return "image/jpeg";
        if(iequals(ext, ".gif"))  return "image/gif";
        if(iequals(ext, ".bmp"))  return "image/bmp";
        if(iequals(ext, ".ico"))  return "image/vnd.microsoft.icon";
        if(iequals(ext, ".tiff")) return "image/tiff";
        if(iequals(ext, ".tif"))  return "image/tiff";
        if(iequals(ext, ".svg"))  return "image/svg+xml";
        if(iequals(ext, ".svgz")) return "image/svg+xml";
        return "application/text";
    }
    
    
    // Append an HTTP rel-path to a local filesystem path.
    // The returned path is normalized for the platform.
    std::string
    path_cat(
        boost::beast::string_view base,
        boost::beast::string_view path)
    {
        if(base.empty())
            return path.to_string();
        std::string result = base.to_string();
    #if 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;
    }
    
    
    // This function produces an HTTP response for the given
    // request. The type of the response object depends on the
    // contents of the request, so the interface requires the
    // caller to pass a generic lambda for receiving the response.
    template<
        class Body, class Allocator,
        class Send>
    void
    handle_request(
        boost::beast::string_view doc_root,
        http::request<Body, http::basic_fields<Allocator>>&& req,
        Send&& send)
    {
        // Returns a bad request response
        auto const bad_request =
        [&req](boost::beast::string_view why)
        {
            http::response<http::string_body> res{http::status::bad_request, req.version()};
            res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
            res.set(http::field::content_type, "text/html");
            res.keep_alive(req.keep_alive());
            res.body() = why.to_string();
            res.prepare_payload();
            return res;
        };
    
    
        // Returns a not found response
        auto const not_found =
        [&req](boost::beast::string_view target)
        {
            http::response<http::string_body> res{http::status::not_found, req.version()};
            res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
            res.set(http::field::content_type, "text/html");
            res.keep_alive(req.keep_alive());
            res.body() = "The resource '" + target.to_string() + "' was not found.";
            res.prepare_payload();
            return res;
        };
    
    
        // Returns a server error response
        auto const server_error =
        [&req](boost::beast::string_view what)
        {
            http::response<http::string_body> res{http::status::internal_server_error, req.version()};
            res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
            res.set(http::field::content_type, "text/html");
            res.keep_alive(req.keep_alive());
            res.body() = "An error occurred: '" + what.to_string() + "'";
            res.prepare_payload();
            return res;
        };
    
    
        // Make sure we can handle the method
        if( req.method() != http::verb::get &&
            req.method() != http::verb::head)
            return send(bad_request("Unknown HTTP-method"));
    
    
        // Request path must be absolute and not contain "..".
        if( req.target().empty() ||
            req.target()[0] != '/' ||
            req.target().find("..") != boost::beast::string_view::npos)
            return send(bad_request("Illegal request-target"));
    
    
        // Build the path to the requested file
        std::string path = path_cat(doc_root, req.target());
        if(req.target().back() == '/')
            path.append("index.html");
    
    
        // Attempt to open the file
        boost::beast::error_code ec;
        http::file_body::value_type body;
        body.open(path.c_str(), boost::beast::file_mode::scan, ec);
    
    
        // Handle the case where the file doesn't exist
        if(ec == boost::system::errc::no_such_file_or_directory)
            return send(not_found(req.target()));
    
    
        // Handle an unknown error
        if(ec)
            return send(server_error(ec.message()));
    
        // Respond to HEAD request
        if(req.method() == http::verb::head)
        {
            http::response<http::empty_body> res{http::status::ok, req.version()};
            res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
            res.set(http::field::content_type, mime_type(path));
            res.content_length(body.size());
            res.keep_alive(req.keep_alive());
            return send(std::move(res));
        }
    
    
        // Respond to GET request
        http::response<http::file_body> res{
            std::piecewise_construct,
            std::make_tuple(std::move(body)),
            std::make_tuple(http::status::ok, req.version())};
        res.set(http::field::server, BOOST_BEAST_VERSION_STRING);
        res.set(http::field::content_type, mime_type(path));
        res.content_length(body.size());
        res.keep_alive(req.keep_alive());
        return send(std::move(res));
    }
    
    
    //------------------------------------------------------------------------------
    
    
    // Report a failure
    void
    fail(boost::system::error_code ec, char const* what)
    {
        std::cerr << what << ": " << ec.message() << "\n";
    }
    
    
    // This is the C++11 equivalent of a generic lambda.
    // The function object is used to send an HTTP message.
    template<class Stream>
    struct send_lambda
    {
        Stream& stream_;
        bool& close_;
        boost::system::error_code& ec_;
    
    
        explicit
        send_lambda(
            Stream& stream,
            bool& close,
            boost::system::error_code& ec)
            : stream_(stream)
            , close_(close)
            , ec_(ec)
        {
        }
    
    
        template<bool isRequest, class Body, class Fields>
        void
        operator()(http::message<isRequest, Body, Fields>&& msg) const
        {
            // Determine if we should close the connection after
            close_ = msg.need_eof();
    
    
            // We need the serializer here because the serializer requires
            // a non-const file_body, and the message oriented version of
            // http::write only works with const messages.
            http::serializer<isRequest, Body, Fields> sr{msg};
            http::write(stream_, sr, ec_);
        }
    };
    
    
    // Handles an HTTP server connection
    void
    do_session(
        tcp::socket& socket,
        std::string const& doc_root)
    {
        bool close = false;
        boost::system::error_code ec;
    
    
        // This buffer is required to persist across reads
        boost::beast::flat_buffer buffer;
    
    
        // This lambda is used to send messages
        send_lambda<tcp::socket> lambda{socket, close, ec};
    
    
        for(;;)
        {
            // Read a request
            http::request<http::string_body> req;
            http::read(socket, buffer, req, ec);
            if(ec == http::error::end_of_stream)
                break;
            if(ec)
                return fail(ec, "read");
    
    
            // Send the response
            handle_request(doc_root, std::move(req), lambda);
            if(ec)
                return fail(ec, "write");
            if(close)
            {
                // This means we should close the connection, usually because
                // the response indicated the "Connection: close" semantic.
                break;
            }
        }
    
    
        // Send a TCP shutdown
        socket.shutdown(tcp::socket::shutdown_send, ec);
    
    
        // At this point the connection is closed gracefully
    }
    
    
    //------------------------------------------------------------------------------
    
    
    int main(int argc, char* argv[])
    {
        try
        {
            // Check command line arguments.
            if (argc != 4)
            {
                std::cerr <<
                    "Usage: http-server-sync <address> <port> <doc_root>\n" <<
                    "Example:\n" <<
                    "    http-server-sync 0.0.0.0 8080 .\n";
                return EXIT_FAILURE;
            }
            auto const address = boost::asio::ip::make_address(argv[1]);
            auto const port = static_cast<unsigned short>(std::atoi(argv[2]));
            std::string const doc_root = argv[3];
    
    
            // The io_context is required for all I/O
            boost::asio::io_context ioc{1};
    
    
            // The acceptor receives incoming connections
            tcp::acceptor acceptor{ioc, {address, port}};
            for(;;)
            {
                // This will receive the new connection
                tcp::socket socket{ioc};
    
    
                // Block until we get a connection
                acceptor.accept(socket);
    
    
                // Launch the session, transferring ownership of the socket
                std::thread{std::bind(
                    &do_session,
                    std::move(socket),
                    doc_root)}.detach();
            }
        }
        catch (const std::exception& e)
        {
            std::cerr << "Error: " << e.what() << std::endl;
            return EXIT_FAILURE;
        }
    }

  7. #7
    Registered User
    Join Date
    Oct 2006
    Posts
    3,445
    look into using http::string_body instead of http::file_body as the response body type.
    What can this strange device be?
    When I touch it, it gives forth a sound
    It's got wires that vibrate and give music
    What can this thing be that I found?

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Boost.Asio: Which method of server coding is more robust?
    By Chris87 in forum C++ Programming
    Replies: 2
    Last Post: 10-29-2017, 03:30 AM
  2. Using boost::asio (2)
    By sean_cantab in forum C++ Programming
    Replies: 0
    Last Post: 09-09-2017, 08:36 PM
  3. Using boost::asio
    By sean_cantab in forum C++ Programming
    Replies: 12
    Last Post: 08-04-2017, 12:26 AM
  4. Boost::asio unithread server-client problem.
    By Bruno Miguel in forum C++ Programming
    Replies: 0
    Last Post: 08-20-2016, 04:55 PM
  5. Boost ASIO
    By PetrolMan in forum C++ Programming
    Replies: 0
    Last Post: 04-10-2009, 03:24 PM

Tags for this Thread