HTTP and WebSocket built on Boost.Asio in C++11

Overview

Boost.Beast Title

HTTP and WebSocket built on Boost.Asio in C++11

Branch Linux/OSX Windows Coverage Documentation Matrix
master Build Status Build status codecov Documentation Matrix
develop Build Status Build status codecov Documentation Matrix

Contents

Introduction

Beast is a C++ header-only library serving as a foundation for writing interoperable networking libraries by providing low-level HTTP/1, WebSocket, and networking protocol vocabulary types and algorithms using the consistent asynchronous model of Boost.Asio.

This library is designed for:

  • Symmetry: Algorithms are role-agnostic; build clients, servers, or both.

  • Ease of Use: Boost.Asio users will immediately understand Beast.

  • Flexibility: Users make the important decisions such as buffer or thread management.

  • Performance: Build applications handling thousands of connections or more.

  • Basis for Further Abstraction. Components are well-suited for building upon.

Appearances

CppCon 2018 Bishop Fox 2018
Beast Beast Security Review
CppCon 2017 CppCast 2017 CppCon 2016
Beast Vinnie Falco Beast

Description

This software is in its first official release. Interfaces may change in response to user feedback. For recent changes see the CHANGELOG.

Requirements

This library is for programmers familiar with Boost.Asio. Users who wish to use asynchronous interfaces should already know how to create concurrent network programs using callbacks or coroutines.

  • C++11: Robust support for most language features.
  • Boost: Boost.Asio and some other parts of Boost.
  • OpenSSL: Required for using TLS/Secure sockets and examples/tests

When using Microsoft Visual C++, Visual Studio 2017 or later is required.

One of these components is required in order to build the tests and examples:

  • Properly configured bjam/b2
  • CMake 3.5.1 or later (Windows only)

Building

Beast is header-only. To use it just add the necessary #include line to your source files, like this:

#include <boost/beast.hpp>

If you use coroutines you'll need to link with the Boost.Coroutine library. Please visit the Boost documentation for instructions on how to do this for your particular build system.

GitHub

To use the latest official release of Beast, simply obtain the latest Boost distribution and follow the instructions for integrating it into your development environment. If you wish to build the examples and tests, or if you wish to preview upcoming changes and features, it is suggested to clone the "Boost superproject" and work with Beast "in-tree" (meaning, the libs/beast subdirectory of the superproject).

The official repository contains the following branches:

  • master This holds the most recent snapshot with code that is known to be stable.

  • develop This holds the most recent snapshot. It may contain unstable code.

Each of these branches requires a corresponding Boost branch and all of its subprojects. For example, if you wish to use the master branch version of Beast, you should clone the Boost superproject, switch to the master branch in the superproject and acquire all the Boost libraries corresponding to that branch including Beast.

To clone the superproject locally, and switch into the main project's directory use:

git clone --recursive https://github.com/boostorg/boost.git
cd boost

"bjam" is used to build Beast and the Boost libraries. On a non-Windows system use this command to build bjam:

./bootstrap.sh

From a Windows command line, build bjam using this command:

.\BOOTSTRAP.BAT

Building tests and examples

Building tests and examples requires OpenSSL installed. If OpenSSL is installed in a non-system location, you will need to copy the user-config.jam file into your home directory and set the OPENSSL_ROOT environment variable to the path that contains an installation of OpenSSL.

Ubuntu/Debian

If installed into a system directory, OpenSSL will be automatically found and used.

sudo apt install libssl-dev

Windows

Replace path in the following code snippets with the path you installed vcpkg to. Examples assume a 32-bit build, if you build a 64-bit version replace x32-windows with x64-windows in the path.

vcpkg install openssl --triplet x32-windows
SET OPENSSL_ROOT=path\installed\x32-windows
  • Using vcpkg and PowerShell:
vcpkg install openssl --triplet x32-windows
$env:OPENSSL_ROOT = "path\x32-windows"
vcpkg.exe install openssl --triplet x32-windows
export OPENSSL_ROOT=path/x32-windows

Mac OS

Using brew:

brew install openssl
export OPENSSL_ROOT=$(brew --prefix openssl)
# install bjam tool user specific configuration file to read OPENSSL_ROOT
# see https://boostorg.github.io/build/manual/develop/index.html
cp ./libs/beast/tools/user-config.jam $HOME

Make sure the bjam tool (also called "b2") is available in the path your shell uses to find executables. The Beast project is located in "libs/beast" relative to the directory containing the Boot superproject. To build the Beast tests, examples, and documentation use these commands:

export PATH=$PWD:$PATH
b2 -j2 libs/beast/test cxxstd=11      # bjam must be in your $PATH
b2 -j2 libs/beast/example cxxstd=11   # "-j2" means use two processors
b2 libs/beast/doc                     # Doxygen and Saxon are required for this

Additional instructions for configuring, using, and building libraries in superproject may be found in the Boost Wiki.

Visual Studio

CMake may be used to generate a very nice Visual Studio solution and a set of Visual Studio project files using these commands:

cd libs/beast
mkdir bin
cd bin
cmake ..                                    # for 32-bit Windows builds, or
cmake -G"Visual Studio 15 2017 Win64" ..    # for 64-bit Windows builds (VS2017)

The files in the repository are laid out thusly:

./
    bin/            Create this to hold executables and project files
    bin64/          Create this to hold 64-bit Windows executables and project files
    doc/            Source code and scripts for the documentation
    include/        Where the header files are located
    example/        Self contained example programs
    meta/           Metadata for Boost integration
    test/           The unit tests for Beast
    tools/          Scripts used for CI testing

Usage

These examples are complete, self-contained programs that you can build and run yourself (they are in the example directory).

https://www.boost.org/doc/libs/develop/libs/beast/doc/html/beast/quick_start.html

License

Distributed under the Boost Software License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at https://www.boost.org/LICENSE_1_0.txt)

Contact

Please report issues or questions here: https://github.com/boostorg/beast/issues


Contributing (We Need Your Help!)

If you would like to contribute to Beast and help us maintain high quality, consider performing code reviews on active pull requests. Any feedback from users and stakeholders, even simple questions about how things work or why they were done a certain way, carries value and can be used to improve the library. Code review provides these benefits:

  • Identify bugs
  • Documentation proof-reading
  • Adjust interfaces to suit use-cases
  • Simplify code

You can look through the Closed pull requests to get an idea of how reviews are performed. To give a code review just sign in with your GitHub account and then add comments to any open pull requests below, don't be shy!

https://github.com/boostorg/beast/pulls

Here are some resources to learn more about code reviews:

Beast thrives on code reviews and any sort of feedback from users and stakeholders about its interfaces. Even if you just have questions, asking them in the code review or in issues provides valuable information that can be used to improve the library - do not hesitate, no question is insignificant or unimportant!

Issues
  • Receive/parse the message body one chunk at a time

    Receive/parse the message body one chunk at a time

    I really like the library but am still missing one critical functionality, to be able to read the message one chunk (HTTP chunk on the wire) at a time. I would want to have a parser for which async_parse can be called multiple times to fill the message one chunk at a time. After each completion of async_parse I should be able to either drain the message body to reduce memory usage or leave it there so the next async_parse would append to the body buffer. I think this functionality is available for websockets.

    Another useful feature would be to be able to specify async_parse to complete after reading at most X amount of bytes in the body. Calling async_parse again should continue appending data to the message body.

    Feature Design 
    opened by georgi-d 122
  • Boost 1.66 upgrade: assertion failure and mixing async/sync ops

    Boost 1.66 upgrade: assertion failure and mixing async/sync ops

    I recently upgraded from Beast beta 0.69 to official Boost 1.66. (Belated congratz to Vinnie :-)

    I have a SSL websocket client test application for sending various test traffic to my SSL websocket server application. This test client can create multiple concurrent websocket connections and multiple worker threads all managed under a single asio::io_service. Each connection session uses a mix of asynchronous reads and synchronous writes (including synchronous websocket::stream::close()). The async read callbacks are wrapped by a strand. The synchronous writes/close are executed from within strand-wrapped callbacks or strand-posted functions. There is at most one active async_read() at a time.

    This test client worked fine with Beast 0.69. After upgrading to Boost 1.66, I noticed that when these client websocket connections attempt to initiate the close:

    1. websocket::stream::close() always produced the error code websocket::error::failed ("boost.beast.websocket: WebSocket connection failed due to a protocol violation")
    2. Sometimes one or more of the connection sessions would assert fail at: beast/websocket/impl/read.ipp:576 Assertion `ws_.status_ == status::open' failed.

    As an experiment, I changed close() to async_close() and then I noticed:

    1. The error code websocket::error::failed went away. async_close's callback always receives a success status.
    2. The read.ipp:576 "ws_.status_ == status::open" assertion failure went away.
    3. A new assertion failure appeared, but much less frequently: beast/websocket/impl/read.ipp:217 Assertion `ws_.rd_block_' failed. The async_read() callback handler was not reached, so I'm guessing this asserted while processing the peer close frame response from the server.

    Questions:

    1. Is it incorrect to mix async and sync calls?

    I understand that there can be no more than one active async read, write, ping, close op without an intervening corresponding callback. I also understand that websocket objects are not thread safe so some kind of synchronization control (like an explicit strand) is needed when using multiple threads.

    I think I'm following these rules... But perhaps if websocket::stream::close() is internally performing async ops on the websocket, then these async ops might get executed on another thread without strand protection. This could race with my earlier call to async_read() if the timing is unlucky and end up with two threads modifying the same websocket.

    But the docs for close() says it's implemented with the next layer's write_some() which are blocking calls.

    Not sure if this is my bug or Beast bug

    1. what might be causing the new assertion failure at read.ipp:217?
    opened by vchang-akamai 86
  • websocket handshake differs from curl output, fails with obscure server

    websocket handshake differs from curl output, fails with obscure server

    beast version 67

    note I have replaced the host with [host] throughout, I can privately share the real host to help testing

    I'm using this curl command to handshake with a server based on a pascal websocket server (http://www.esegece.com/websockets/)

    curl -v -i -N -H "Connection: Upgrade" -H "Upgrade: websocket" -H "Host: [host]" -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" -H "Sec-WebSocket-Version: 13" https://[host]
    

    I am trying to do the same handshake with beast, even copying the curl headers:

            beast::websocket::response_type res;
            ws.handshake_ex(res, host, "/",
                            [](beast::websocket::request_type &m)
                            {
                                m.insert(beast::http::field::accept, "*/*");
                                m.insert(beast::http::field::user_agent, "curl/7.47.0");
                                std::cout << m << std::endl;
                            },
                            ec);
    
            std::cout << res << std::endl;
    

    the server accepts the curl connection but rejects the beast connection. the server's logs show that the headers are received with extra newlines or something like newlines in the case of beast:

    server logs for curl connection:

    Recv: GET / HTTP/1.1
    Host: [host]
    User-Agent: curl/7.47.0
    Accept: */*
    Connection: Upgrade
    Upgrade: websocket
    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
    Sec-WebSocket-Version: 13
    
    Sent: HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
    Server: sgcWebSockets 3.5
    

    server logs for beast:

    Recv: GET
    Recv:  /
    Recv:  HTTP/1.1
    
    Recv: Host: [host]
    
    Recv: Upgrade: websocket
    
    Recv: Connection: upgrade
    
    Recv: Sec-WebSocket-Key: lD7oPd7/ryIbKDcgimpDAA==
    
    Recv: Sec-WebSocket-Version: 13
    
    Recv: Accept: */*
    
    Recv: User-Agent: curl/7.47.0
    
    Recv: 
    
    Sent: HTTP/1.1 302 Moved Temporarily
    Connection: close
    Content-Length: 0
    Date: Tue, 27 Jun 2017 17:01:11 GMT
    Server: sgcWebSockets 3.5
    Location: login
    Set-Cookie: IDHTTPSESSIONID=ANv2nTA6vZ8PuVx; Path=/
    

    I also tested beast using mitmproxy (https://mitmproxy.org/) instead of connecting to the server directly, and it works correctly in this case, as I assume mitmproxy is putting the headers together. I assume my problem has to do with the extra newlines in the beast output. This may be standards-compliant (I don't know), but it's making my server unhappy. Is there a way to change this?

    opened by larsonmpdx 76
  • Adding POST handling code to http_server_async.cpp Example File

    Adding POST handling code to http_server_async.cpp Example File

    I want to create a web app that implements a currency converter with a Google Map GUI. An info window centered on the user's geolocation coordinates obtained by using HTML5 geolocation will pop up when the application starts. By default, the "from" currency would be the currency used at the country or whatever place the user is in, but it can be changed with the drop-down menu on the HTML form displayed on the info window. The form would have an input field where the user would enter a money amount, and two drop-down menus: one for the "from" currency and the other for the "to" currency. The form would send the inputted data to the server code via POST. This is what I want to implement.

    What I'm trying to do for that is to take the http_server_async.cpp example code and add code to it to handle the POST method. The request-handling function from the example is like this now:

    // 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 &&
    		req.method() != http::verb::post)
    		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
    	else if (req.method() == http::verb::get)
    	{
    		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));
    	}
    
    	// Respond to POST request
    	else if (req.method() == http::verb::post)
    	{
    		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));
    }
    

    I need to parse the URI and check if the path is what I expect, right? And then I have to decode the request to check the encoding and decode it. I have to check the Content-type for the encoding, right? If so, how should I do that?

    So for the path, what I'm thinking of doing is checking the req.target() object and to see if there's anything before the '/' character other than a space, and if the path itself is what I expect. I need a way to find the dot character and see what the extension is as well. Do I need a regex library for URIs for this? A URI regex library and a mimetic library. Or is there also an easy way to do this without those libraries that I should know? Like perhaps using a string_view to check the request target and encoding. Would that be possible? I should say I'm still learning and that this is the first time I'm trying to make this kind of library (and I'm also new to network programming itself). But I want to see how to do this myself as well, if just because I don't want to have to deal with too many libraries at once.

    I'm only working on the server code for now and then I'll do the client; I haven't done anything for the application code itself because I wanted to get the server and client code done first. But what I'm thinking of is having a CGI script receive the POST request from the form. I've configured my Apache server to accept .cgi and .exe extensions for that, by the way.

    opened by DragonOsman 69
  • MAC OSX Command + R crashes websocket

    MAC OSX Command + R crashes websocket

    Hi Vinnie,

    I use beast to handle websocket. One issue I noticed is that in the Web browser, on MAC OSX if I press "Command" + R key, after multiple tries (normally 5 or 6 times). My service quit without any errors in my log. It does generate a core file. Please see attached core file _sbin_stingray.0.crash.zip

    Also, I used "strace -p xxx" to attach my serive PID. When it crashes, it is something like below: The last few lines point to "write(2, "stingray: /usr/include/beast/web"..., 988) = -1 EPIPE (Broken pipe)"

    sendmsg(35, {msg_name(0)=NULL, msg_iov(1)=[{"\27\3\3\0000vB\255\254S\276\265\232a\254<6\260\365p`hn)z\235\17~IJ{\215"..., 53}], msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL) = 53 epoll_wait(8, [], 128, 0) = 0 sendmsg(35, {msg_name(0)=NULL, msg_iov(1)=[{"\27\3\3\00002,\177\352\213\204 \357\206\7\366\373ec3\276C8v\316r\6\252\354\207y\373"..., 53}], msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL) = 53 epoll_wait(8, [], 128, 0) = 0 sendmsg(35, {msg_name(0)=NULL, msg_iov(1)=[{"\27\3\3\0000u\257\rn\27212\215\376y\21L\330\362\272C,>\7\312\321r?\251\3647\34"..., 53}], msg_controllen=0, msg_flags=0}, MSG_NOSIGNAL) = 53 epoll_wait(8, [], 128, 0) = 0 write(2, "stingray: /usr/include/beast/web"..., 988) = -1 EPIPE (Broken pipe) --- SIGPIPE {si_signo=SIGPIPE, si_code=SI_USER, si_pid=1221, si_uid=0} --- mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f5c17275000 rt_sigprocmask(SIG_UNBLOCK, [ABRT], NULL, 8) = 0 tgkill(1221, 1221, SIGABRT) = 0 --- SIGABRT {si_signo=SIGABRT, si_code=SI_TKILL, si_pid=1221, si_uid=0} --- +++ killed by SIGABRT (core dumped) +++

    If you could look into this, it would be much appreciated.

    opened by goflatworld 48
  • implicit-fallthrough warnings while builing http-tests

    implicit-fallthrough warnings while builing http-tests

    Beast/v68 gcc 7.0.1 -std=c++17 boost 1.63

    So many warnings, you can't see real errors...

    In file included from /home/mike/workspace/Beast/include/beast/http/serializer.hpp:333:0,
                     from /home/mike/workspace/Beast/include/beast/http.hpp:24,
                     from /home/mike/workspace/Beast/include/beast.hpp:14,
                     from /home/mike/workspace/Beast/./example/doc/http_examples.hpp:8,
                     from /home/mike/workspace/Beast/test/http/doc_examples.cpp:8:
    /home/mike/workspace/Beast/include/beast/http/impl/serializer.ipp: In member function ‘void beast::http::serializer<isRequest, Body, Fields, ChunkDecorator>::get(beast::error_code&, Visit&&) [with Visit = beast::http::detail::write_lambda<beast::test::pipe::stream>&; bool isRequest = false; Body = file_body; Fields = beast::http::basic_fields<std::allocator<char> >; ChunkDecorator = beast::http::no_chunk_decorator]’:
    /home/mike/workspace/Beast/include/beast/http/impl/serializer.ipp:65:9: warning: this statement may fall through [-Wimplicit-fallthrough=]
             s_ = do_init;
             ^~
    /home/mike/workspace/Beast/include/beast/http/impl/serializer.ipp:69:5: note: here
         case do_init:
         ^~~~
    /home/mike/workspace/Beast/include/beast/http/impl/serializer.ipp:89:26: warning: this statement may fall through [-Wimplicit-fallthrough=]
             BOOST_FALLTHROUGH;
                              ^
    /home/mike/workspace/Beast/include/beast/http/impl/serializer.ipp:92:5: note: here
         case do_header:
         ^~~~
    /home/mike/workspace/Beast/include/beast/http/impl/serializer.ipp:98:9: warning: this statement may fall through [-Wimplicit-fallthrough=]
             s_ = do_header_only;
             ^~
    /home/mike/workspace/Beast/include/beast/http/impl/serializer.ipp:99:5: note: here
         case do_header_only:
         ^~~~
    /home/mike/workspace/Beast/include/beast/http/impl/serializer.ipp:110:9: warning: this statement may fall through [-Wimplicit-fallthrough=]
             s_ = do_body + 1;
             ^~
    
    opened by octopus-prime 44
  • Add support for

    Add support for "HTTP-like" protocols

    There are various "HTTP-like" protocols that exist and are in common use. They are similar but not totally identical. SIP references the HTTP RFC for the message and header field definitions with the qualifier that the character encoding is UTF-8.

    Examples include:

    Others surely exist, I know of some proprietary protocols that borrow the HTTP framework.

    There are differences:

    • UTF-8 supported vs. ASCII only. SIP and RTSP use UTF-8, HTTP is ASCII.
    • Supporting or not supporting chunked encoding.
    • Requiring or not requiring content-length when a response body is present.
    • Connection keepalive rules.
    • UDP support vs streams only

    Being able to support these would be useful. Is this something worth considering? Thoughts/suggestions on the approach?

    Design 
    opened by jm-mikkelsen 41
  • Deadlock problem under high concurrency

    Deadlock problem under high concurrency

    We write a http example with beast, and we use multiple threads to run io_service. Then we conduct a large number of concurrent connection tests. Each connection will only send and receive packets once, and then disconnect. Later, we found that it is difficult to connect to the opened ports. We think that a deadlock occurred in async_accept.Because if we use single thread, this problem will not happen. Is there any solution for this problem? I would very appreciate it if who can help me.

    Stale 
    opened by Yyyyshen 40
  • Need help handling XHR GET request from client-side code

    Need help handling XHR GET request from client-side code

    Hi, everyone.

    Please note: this is only a problem I have; it is not a bug report or anything like that.

    I have an environment variable set to the access key for the currency API I'm using for my currency convertor web app. I need to get that key to my JavaScript code so I can use it there to request a list of currencies and populate two dropdowns with it in an HTML form. That form submits to the server app via POST with a "to currency" and a money to convert.

    I think I may be handling the GET request for the access key wrong. Here's my C++ code for reference: https://gist.github.com/DragonOsman/7bbdb049ea2a77173c5dafbecd5f1be2 and this is the JS code: https://gist.github.com/DragonOsman/c6e8fb15343544e662f474c5a526d1c2 .

    The problem is in the handle_request function, I think, so I'll post that here.

    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
    	const auto 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
    	const auto 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
    	const auto 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 &&
    		req.method() != http::verb::post)
    	{
    		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");
    	}
    
    	// In case of POST request, check the path URI
    	else if (req.target().front() == '/' && req.target().size() > 1 && req.method() == http::verb::post)
    	{
    		using boost::beast::iequals;
    		const auto ext = [&path]
    		{
    			const auto pos = path.rfind(".");
    			if (pos == boost::beast::string_view::npos)
    			{
    				return boost::beast::string_view{};
    			}
    			const auto pos2 = path.find(" ");
    			if (pos2 != boost::beast::string_view::npos)
    			{
    				return boost::beast::string_view{};
    			}
    			return boost::beast::string_view(path.substr(pos));
    		}();
    		if (!iequals(ext, ".exe") && !iequals(ext, ".cgi"))
    		{
    			return send(bad_request("Illegal request-target"));
    		}
    	}
    
    	// 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
    	else if (req.method() == http::verb::get)
    	{
    		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));
    	}
    
    	// Respond to GET request for currency API access key from JavaScript code
    	else if (req.method() == http::verb::get && req.target() == "/?q=accesskey")
    	{
    		http::string_body::value_type str_body{ accesskey };
    		http::response<http::string_body> res{
    			std::piecewise_construct,
    			std::make_tuple(std::move(str_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("/q?=accesskey"));
    		res.content_length(str_body.size());
    		res.keep_alive(req.keep_alive());
    		return send(std::move(res));
    	}
    
    	// Respond to POST request
    	else if (req.method() == http::verb::post)
    	{
    		boost::beast::string_view content_type = req[http::field::content_type];
    		if (content_type.find("multipart/form-data") == std::string::npos && 
    			content_type.find("application/x-www-form-urlencoded") == std::string::npos)
    		{
    			return send(bad_request("Bad request"));
    		}
    
    		std::map<std::string, std::string> parsed_value = parse(req.body());
    		std::cout << "POST body example:\n" << req.body() << '\n';
    		std::cout << "parsed_value map:\n";
    		for (const auto &x : parsed_value)
    		{
    			std::cout << x.first << ": " << x.second << '\n';
    		}
    		double money_amount = std::stod(parsed_value["currency_amount"]);
    		std::string to_currency = parsed_value["to_currency"];
    		std::cout << "Line 474: to_currency is: " << to_currency << '\n';
    		std::string currency_abbr = to_currency.substr(0, 3);
    		double conversion_result = convert(currency_abbr, money_amount);
    		
    		http::string_body::value_type str_body = std::to_string(conversion_result);
    		http::response<http::string_body> res{
    			std::piecewise_construct,
    			std::make_tuple(std::move(str_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(str_body.size());
    		res.keep_alive(req.keep_alive());
    		return send(std::move(res));
    	}
    }
    

    I have this error in the browser's console:

     HTTP500: SERVER ERROR - The server encountered an unexpected condition that prevented it from fulfilling the request.
    (XHR)GET - http://localhost:8080/?q=accesskey
    

    So what did I do wrong?

    opened by DragonOsman 40
  • Can not read file name with umlauts in boost::beast::http::file_body

    Can not read file name with umlauts in boost::beast::http::file_body

    Thanks again for the very cool library, first and foremost! We have a minor issue with umlauts (low priority).

    Version of Beast

    #define BOOST_BEAST_VERSION 290
    

    Steps necessary to reproduce the problem

    My code is based on the advanced_server_flex.cpp example, and my problem appears specifically around line https://github.com/boostorg/beast/blob/develop/example/advanced/server-flex/advanced_server_flex.cpp#L179:

        // Attempt to open the file
        beast::error_code ec;
        http::file_body::value_type body;
        body.open(path.c_str(), beast::file_mode::scan, ec);
    
        // Handle the case where the file doesn't exist
        if(ec == beast::errc::no_such_file_or_directory)  //< This is true for us when the file name has umlauts.
    

    We used this code successfully on multiple platforms (MSVC, Ubuntu and MacOSX) for about a year now. However as soon as we started adding clang-cl to the list of compilers, the webserver can no longer read files that have Umlauts in the file name.

    We tested with a file named

    üöälaut.txt
    

    that is not found on Windows when using clang-cl, whereas all other platforms/compilers work fine.

    All relevant compiler information

    The error comes with Visual Studio 2019 x64 16.7.1 with the Microsoft-supplied clang-cl version 10.0.0. Using the Microsoft-Compiler and not clang-cl, everything works.

    Bug 
    opened by emmenlau 39
  • read error: partial message

    read error: partial message

    Hi Thanks for the library it really helped me to develop some great tools to overcome some issues we faced during pandemic time:-) I am getting "partial message read error" in response reading phase after making a post request. I am using dedicated thread for network communication on which I called ioc.run()(only place it is called). The code is the same as "https://www.boost.org/doc/libs/develop/libs/beast/example/http/client/async-ssl/http_client_async_ssl.cpp". Initially, I was using http::responsehttp::string_body res_. In order to check the response content, I move to http::response_parser< http::string_body> . When I inspected the content I see that the response body contains the full message sent from the server. But still, the "partial message read error" occurs. What might be the reason for this?

    Stale 
    opened by abhilashraju 35
  • Compilation fail with Clang trunk

    Compilation fail with Clang trunk

    Version of Beast

    The one included in Boost 1.79.0

    Steps necessary to reproduce the problem

    Minified version https://godbolt.org/z/WjbGacE7o

    I can't understand on which side the problem is so I'll report it to LLVM too, maybe it's a bug in their trunk since latest release works fine. I'll report back if that's the case.

    opened by vient 3
  • Is there any portable way to know if the socket has timed out on client side vs on server side?

    Is there any portable way to know if the socket has timed out on client side vs on server side?

    Beast version 1.71

    Hi, Is there any portable way to know if the socket has timed out on client side vs on server side? As far as i have tested, on linux, i get error code 1 for both client side timeout and server side socket close [not able to emulate serverside socket timeout so instead closed the socket] On windows, i get error code 1 for client side timeout and 10053 for server side socket close.

    So how do i definitely know on linux if its a client vs server socket timeout?

    opened by ashjas 3
  • Add support for Proxy Protocol (v2) on incoming connections

    Add support for Proxy Protocol (v2) on incoming connections

    Version of Beast

    master

    Steps necessary to reproduce the problem

    I'd like to seek you to look into supporting proxy protocol v2 which allows proxy servers / TCP load balancers to forward information about a client to the backend.

    HAProxy does supports and actually originates this binary protocol specifying all sorts of fields to be piggybacked to the first TCP packet to the backend. This is instead of using HTTP headers the likes of X-Forwarded-For, X-Forwarded-Proto, which obviously only work when speaking HTTP and have other drawbacks.

    • https://www.haproxy.com/blog/use-the-proxy-protocol-to-preserve-a-clients-ip-address/
    • https://github.com/haproxy/haproxy/blob/master/doc/proxy-protocol.txt (includes sample code)

    But apart from HAProxy there are many more load balancers providing source info via proxy protocol v2 on outgoing connections to their backends. To not just name services from AWS, Azure, GCP which do support it for TCP connections via their load balancers or other networking services which terminate a TCP connection and would therefore "NAT" away source info.

    The proxy protocol v2 can also carry info like client certificates in case of TLS offloading and last but lot least is extensible via custom TLVs (type, length, value). Implementations of the proxy protocol v2 sometimes internally replace data like source ip and port with the values received via proxy protocol v2 and treat the connection as it was directly established this way. So logging, rate limits, ACLs and other uses of this data then transparently uses the actual client info and are not reduced to only seeing the load balancer as client.

    To give you a bit of back story ... I am a user of Ceph which uses Beast in their S3/Swift object storage service RGW (Rados Rateway), see:

    • https://docs.ceph.com/en/quincy/radosgw/frontends/#beast
    • https://github.com/ceph/ceph/blob/main/src/rgw/rgw_asio_frontend.cc

    RGW is usually deployed in multiple instances and placed behind a load balancer, like HAProxy. And while it's certainly possible to have HAProxy (https://cbonte.github.io/haproxy-dconv/2.0/configuration.html#option%20forwardfor) or and other HTTP reverse proxy to send headers which Ceph RGW using beast then logs (rgw_log_http_headers) this does not work for load balancers in TCP.

    All relevant compiler information

    opened by frittentheke 11
  • Some documentation pages are unexpectedly blank

    Some documentation pages are unexpectedly blank

    Version of Beast

    it seems all versions

    Steps necessary to reproduce the problem

    Small documentation issue.

    Main documentation page [master, 1.78.0, 1.68.0] -> see "HTTP Examples" or "More Examples" with subitems. Click it. HTTP Examples documentation page [master, 1.78.0, 1.68.0] -> empty page. I would expect the subitems on the previous page to be listed here

    I did a small amount of digging. It looks like HTTP Examples page is generated from this QBK? I see the text These examples in this section are working functions that may be found in the examples directory. They demonstrate the usage of the library for a variety of scenarios. is there and additional text describing each sub item. But I am not familiar with QBK to diagnose why the page is empty.

    It seems some other sections are similarly empty. The Concepts 1.78.0 page is similarly empty as is the 1.78.0 Configuration page. The 1.78.0 Quick Look page is empty -- but it at least links to the example directory listing even if it doesn't describe which examples do what.

    Other sections appear to be correctly filled out. For example, the 1.78.0 WebSockets section has content.

    It looks like the WebSocket section QBK mentions only a single section (with notes, headings, tables, includes, etc) and links to other sections while the HTTP Examples QBK has multiple sections.

    opened by inetknght 8
  • g++-4.8 support

    g++-4.8 support

    The posix (gcc-4.8, 11, ubuntu-20.04, ubuntu:16.04, g++-4.8, true) workflow has been failing with:

    In file included from ./boost/asio/spawn.hpp:904:0,
                     from libs/beast/test/extras/include/boost/beast/test/yield_to.hpp:15,
                     from libs/beast/test/beast/websocket/test.hpp:20,
                     from libs/beast/test/beast/websocket/accept.cpp:16:
    ./boost/asio/impl/spawn.hpp: In member function 'virtual void boost::asio::detail::spawned_fiber_thread::resume()':
    ./boost/asio/impl/spawn.hpp:204:64: error: passing 'boost::asio::detail::spawned_fiber_thread::fiber_type {aka boost::context::fiber}' as 'this' argument of 'boost::context::fiber boost::context::fiber::resume() &&' discards qualifiers [-fpermissive]
         callee_ = BOOST_ASIO_MOVE_CAST(fiber_type)(callee_).resume();
    

    I don't know when this error was introduced. It fails in some recent runs that were only touching the documentation.

    I think I saw in some other library that GCC 4.8 doesn't work well with && this qualifiers, which might be the problem.

    opened by alandefreitas 2
Owner
Boost.org
Boost provides free peer-reviewed portable C++ source libraries.
Boost.org
Minimalistic socket library inspired by Asio/Boost Asio, implemented in 1 single header file

cz-spas czspas (Small Portable Asynchronous Sockets) is minimalistic socket library inspired by Asio/Boost Asio, implemented in 1 single header file.

Rui Figueira 25 Jun 12, 2022
A very simple, fast, multithreaded, platform independent HTTP and HTTPS server and client library implemented using C++11 and Boost.Asio.

A very simple, fast, multithreaded, platform independent HTTP and HTTPS server and client library implemented using C++11 and Boost.Asio. Created to be an easy way to make REST resources available from C++ applications.

Ole Christian Eidheim 2.3k Aug 2, 2022
Packio - An asynchronous msgpack-RPC and JSON-RPC library built on top of Boost.Asio.

Header-only | JSON-RPC | msgpack-RPC | asio | coroutines This library requires C++17 and is designed as an extension to boost.asio. It will let you bu

Quentin Chateau 47 Jul 21, 2022
Asynchronous gRPC with Boost.Asio executors

asio-grpc This library provides an implementation of boost::asio::execution_context that dispatches work to a grpc::CompletionQueue. Making it possibl

Dennis 136 Jul 27, 2022
Boost::ASIO low-level redis client (connector)

bredis Boost::ASIO low-level redis client (connector), github gitee Features header only zero-copy (currently only for received replies from Redis) lo

Ivan Baidakou 142 Jul 21, 2022
Mongoose Embedded Web Server Library - a multi-protocol embedded networking library with TCP/UDP, HTTP, WebSocket, MQTT built-in protocols, async DNS resolver, and non-blocking API.

Mongoose - Embedded Web Server / Embedded Networking Library Mongoose is a networking library for C/C++. It implements event-driven non-blocking APIs

Cesanta Software 8.6k Jul 29, 2022
Pushpin is a reverse proxy server written in C++ that makes it easy to implement WebSocket, HTTP streaming, and HTTP long-polling services.

Pushpin is a reverse proxy server written in C++ that makes it easy to implement WebSocket, HTTP streaming, and HTTP long-polling services. The project is unique among realtime push solutions in that it is designed to address the needs of API creators. Pushpin is transparent to clients and integrates easily into an API stack.

Fanout 3.1k Aug 4, 2022
cuehttp is a modern c++ middleware framework for http(http/https)/websocket(ws/wss).

cuehttp 简介 cuehttp是一个使用Modern C++(C++17)编写的跨平台、高性能、易用的HTTP/WebSocket框架。基于中间件模式可以方便、高效、优雅的增加功能。cuehttp基于boost.asio开发,使用picohttpparser进行HTTP协议解析。内部依赖了nl

xcyl 26 Jul 21, 2022
Boost.GIL - Generic Image Library | Requires C++11 since Boost 1.68

Documentation GitHub Actions AppVeyor Azure Pipelines CircleCI Regression Codecov Boost.GIL Introduction Documentation Requirements Branches Community

Boost.org 146 Jul 11, 2022
H2O - the optimized HTTP/1, HTTP/2, HTTP/3 server

H2O - an optimized HTTP server with support for HTTP/1.x, HTTP/2 and HTTP/3 (experimental) Copyright (c) 2014-2019 DeNA Co., Ltd., Kazuho Oku, Tatsuhi

H2O 10.1k Aug 4, 2022
Cross-platform, efficient, customizable, and robust asynchronous HTTP/WebSocket server C++14 library with the right balance between performance and ease of use

What Is RESTinio? RESTinio is a header-only C++14 library that gives you an embedded HTTP/Websocket server. It is based on standalone version of ASIO

Stiffstream 871 Jul 28, 2022
Ultra fast and low latency asynchronous socket server & client C++ library with support TCP, SSL, UDP, HTTP, HTTPS, WebSocket protocols and 10K connections problem solution

CppServer Ultra fast and low latency asynchronous socket server & client C++ library with support TCP, SSL, UDP, HTTP, HTTPS, WebSocket protocols and

Ivan Shynkarenka 866 Jul 27, 2022
websocket and http client and server library, coming with ws, a command line swiss army knife utility

Hello world IXWebSocket is a C++ library for WebSocket client and server development. It has minimal dependencies (no boost), is very simple to use an

Machine Zone, Inc. 326 Jul 31, 2022
Small and fast cross-platform networking library, with support for messaging, IPv6, HTTP, SSL and WebSocket.

frnetlib Frnetlib, is a cross-platform, small and fast networking library written in C++. There are no library dependencies (unless you want to use SS

Fred Nicolson 22 May 16, 2022
BingBing 54 Jul 27, 2022
Simple embeddable C++11 async tcp,http and websocket serving.

net11 Simple embeddable C++11 async tcp,http and websocket serving. What is it? An easily embeddable C++11 networking library designed to make buildin

Jonas Lund 9 Mar 28, 2020
C++ peer to peer library, built on the top of boost

Breep What is Breep? Breep is a c++ bridged peer to peer library. What does that mean? It means that even though the network is constructed as a peer

Lucas Lazare 112 Jul 15, 2022
Gromox - Groupware server backend with MAPI/HTTP, RPC/HTTP, IMAP, POP3 and PHP-MAPI support for grommunio

Gromox is the central groupware server component of grommunio. It is capable of serving as a replacement for Microsoft Exchange and compatibles. Conne

grommunio 120 Jul 2, 2022
Filter Garry'sMod built-in HTTP requests with a lua hook

Filter Garry'sMod built-in HTTP requests with a lua hook

Earu 3 Jul 28, 2022