-
Notifications
You must be signed in to change notification settings - Fork 1
boost beast example #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
StephanKa
merged 8 commits into
Zuehlke:main
from
StephanKa:feature/add-boost-beast-example
Apr 27, 2022
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
cb66a12
added windows builds, extend to multiple compiler
StephanKa 0b2d69c
removed windows builds, minimized README
StephanKa f8bb51b
revert new line
StephanKa 460ab86
revert README.md
StephanKa ba70603
Merge branch 'Zuehlke:main' into main
StephanKa 30a4027
initial boost beast example
StephanKa 64e52f2
fixed review findings
StephanKa f32d58b
fixed review findings
StephanKa File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| ADD_EXECUTABLE(test_boost_beast main.cpp) | ||
| TARGET_LINK_LIBRARIES(test_boost_beast PRIVATE CONAN_PKG::boost CONAN_PKG::nlohmann_json CONAN_PKG::fmt) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| #pragma once | ||
| #include <nlohmann/json.hpp> | ||
| #include <string> | ||
|
|
||
| namespace data { | ||
| // a simple struct to model a person | ||
| struct person | ||
| { | ||
| std::string name; | ||
| std::string address; | ||
| int age = {0}; | ||
| int id = {0}; | ||
| }; | ||
|
|
||
| inline void to_json(nlohmann::json &j, const person &p) { j = nlohmann::json{ { "name", p.name }, { "address", p.address }, { "age", p.age }, { "id", p.id } }; } | ||
|
|
||
| inline void from_json(const nlohmann::json &j, person &p) | ||
| { | ||
| j.at("name").get_to(p.name); | ||
| j.at("address").get_to(p.address); | ||
| j.at("age").get_to(p.age); | ||
| j.at("id").get_to(p.id); | ||
| } | ||
| }// namespace data | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| #pragma once | ||
| #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 <fmt/format.h> | ||
|
|
||
| // from <boost/beast.hpp> | ||
| namespace beast = boost::beast; | ||
| // from <boost/beast/http.hpp> | ||
| namespace http = beast::http; | ||
| // from <boost/asio.hpp> | ||
| namespace asio = boost::asio; | ||
| // from <boost/asio/ip/tcp.hpp> | ||
| using tcp = boost::asio::ip::tcp; | ||
|
|
||
|
|
||
| inline void fail(beast::error_code ec, char const *what) { fmt::format("FAILED {0}: {1}", what, ec.message()); } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| #pragma once | ||
| #include "error_handling.h" | ||
| #include "data.h" | ||
| #include "session.h" | ||
|
|
||
| // Accepts incoming connections and launches the sessions | ||
| class Listener : public std::enable_shared_from_this<Listener> | ||
| { | ||
| public: | ||
| Listener(asio::io_context &ioc, const tcp::endpoint &endpoint) : m_ioc(ioc), m_acceptor(asio::make_strand(ioc)) | ||
| { | ||
| beast::error_code ec; | ||
|
|
||
| // Open the acceptor | ||
| m_acceptor.open(endpoint.protocol(), ec); | ||
| if (ec) | ||
| { | ||
| fail(ec, "open"); | ||
| return; | ||
| } | ||
|
|
||
| // Allow address reuse | ||
| m_acceptor.set_option(asio::socket_base::reuse_address(true), ec); | ||
| if (ec) | ||
| { | ||
| fail(ec, "set_option"); | ||
| return; | ||
| } | ||
|
|
||
| // Bind to the server address | ||
| m_acceptor.bind(endpoint, ec); | ||
| if (ec) | ||
| { | ||
| fail(ec, "bind"); | ||
| return; | ||
| } | ||
|
|
||
| // Start listening for connections | ||
| m_acceptor.listen(asio::socket_base::max_listen_connections, ec); | ||
| if (ec) | ||
| { | ||
| fail(ec, "listen"); | ||
| return; | ||
| } | ||
| } | ||
|
|
||
| // Start accepting incoming connections | ||
| void run() { acceptNextConnection(); } | ||
|
|
||
| private: | ||
| void acceptNextConnection() | ||
| { | ||
| // The new connection gets its own strand | ||
| m_acceptor.async_accept(asio::make_strand(m_ioc), beast::bind_front_handler(&Listener::onAccept, shared_from_this())); | ||
| } | ||
|
|
||
| void onAccept(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), m_persons)->run(); | ||
| } | ||
|
|
||
| // Accept another connection | ||
| acceptNextConnection(); | ||
| } | ||
|
|
||
| asio::io_context &m_ioc; | ||
| tcp::acceptor m_acceptor; | ||
| std::vector<data::person> m_persons; | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| #include <cstdlib> | ||
| #include <memory> | ||
| #include <thread> | ||
| #include <vector> | ||
| #include "listener.h" | ||
|
|
||
|
|
||
| int main() | ||
| { | ||
| const auto address = asio::ip::make_address("0.0.0.0"); | ||
| const uint16_t port = 8080u; | ||
| const auto threads = 1; | ||
|
|
||
| // The io_context is required for all I/O | ||
| asio::io_context ioc{ threads }; | ||
|
|
||
| // Create and launch a listening port | ||
| std::make_shared<Listener>(ioc, tcp::endpoint{ address, port })->run(); | ||
|
|
||
| // Run the I/O service on the requested number of threads | ||
| std::vector<std::thread> v; | ||
| v.reserve(threads); | ||
| for (auto i = 0; i < threads; i++) | ||
| { | ||
| v.emplace_back([&ioc] { ioc.run(); }); | ||
| } | ||
| ioc.run(); | ||
|
|
||
| return EXIT_SUCCESS; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
| #pragma once | ||
| #include "error_handling.h" | ||
|
|
||
| template<typename Request> http::response<http::string_body> inline createResponse(Request &&req, http::status status, const nlohmann::json &jsonResponse) | ||
| { | ||
| http::response<http::string_body> res{ status, req.version() }; | ||
| res.set(http::field::server, BOOST_BEAST_VERSION_STRING); | ||
| res.set(http::field::content_type, "application/json"); | ||
| res.keep_alive(req.keep_alive()); | ||
| res.body() = jsonResponse.dump(); | ||
| res.prepare_payload(); | ||
| return res; | ||
| } | ||
|
|
||
|
|
||
| // 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> inline void handleRequest(http::request<Body, http::basic_fields<Allocator>> &&req, Send &&send, std::vector<data::person> &data) | ||
| { | ||
| // Returns a bad request response | ||
| const auto badRequest = [&req](beast::string_view why) { | ||
| const auto j = nlohmann::json::parse(std::string(why)); | ||
| return createResponse(req, http::status::bad_request, j); | ||
| }; | ||
|
|
||
| // Make sure we can handle the method | ||
| switch (req.method()) | ||
| { | ||
| case http::verb::get: { | ||
| // Respond to GET request | ||
| nlohmann::json j = data; | ||
| return send(std::move(createResponse(req, http::status::ok, j))); | ||
| } | ||
| case http::verb::put: { | ||
| try | ||
| { | ||
| const auto j = nlohmann::json::parse(req.body()); | ||
| const data::person d = j; | ||
| if (d.id > data.size() - 1 || d.id < 0) | ||
| { | ||
| const nlohmann::json j = R"({"error": "id is larger than data list or negative"})"; | ||
| return send(std::move(createResponse(req, http::status::internal_server_error, j))); | ||
| } | ||
| auto &temp = data.at(d.id - 1); | ||
| temp.name = d.name; | ||
| temp.address = d.address; | ||
| temp.age = d.age; | ||
| return send(std::move(createResponse(req, http::status::ok, j))); | ||
| } | ||
| catch (nlohmann::json::exception &e) | ||
| { | ||
| return send(std::move(badRequest(e.what()))); | ||
| } | ||
| } | ||
| case http::verb::post: { | ||
| try | ||
| { | ||
| const auto j = nlohmann::json::parse(req.body()); | ||
| data.push_back(j); | ||
| data.back().id = static_cast<int>(data.size()); | ||
| return send(std::move(createResponse(req, http::status::ok, j))); | ||
| } | ||
| catch (nlohmann::json::exception &e) | ||
| { | ||
| return send(std::move(badRequest(e.what()))); | ||
| } | ||
| } | ||
| default: { | ||
| const nlohmann::json j = R"({"error": "http method not supported"})"; | ||
| return send(std::move(createResponse(req, http::status::internal_server_error, j))); | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| #pragma once | ||
| #include "error_handling.h" | ||
| #include "request.h" | ||
|
|
||
| // Handles an HTTP server connection | ||
| class Session : public std::enable_shared_from_this<Session> | ||
| { | ||
| public: | ||
| // Take ownership of the stream | ||
| explicit Session(tcp::socket &&socket, std::vector<data::person> &person) : m_person(person), m_stream(std::move(socket)), m_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. | ||
| asio::dispatch(m_stream.get_executor(), beast::bind_front_handler(&Session::doRead, shared_from_this())); | ||
| } | ||
|
|
||
| void doRead() | ||
| { | ||
| // Make the request empty before reading, | ||
| // otherwise the operation behavior is undefined. | ||
| m_req = {}; | ||
|
|
||
| // Set the timeout. | ||
| m_stream.expires_after(std::chrono::seconds(30)); | ||
|
|
||
| // Read a request | ||
| http::async_read(m_stream, m_buffer, m_req, beast::bind_front_handler(&Session::onRead, shared_from_this())); | ||
| } | ||
|
|
||
| void onRead(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 doClose(); | ||
| } | ||
|
|
||
| if (ec) | ||
| { | ||
| return fail(ec, "read"); | ||
| } | ||
|
|
||
| // Send the response | ||
| handleRequest(std::move(m_req), m_lambda, m_person); | ||
| } | ||
|
|
||
| void onWrite(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 doClose(); | ||
| } | ||
|
|
||
| // We're done with the response so delete it | ||
| m_res = nullptr; | ||
|
|
||
| // Read another request | ||
| doRead(); | ||
| } | ||
|
|
||
| void doClose() | ||
| { | ||
| // Send a TCP shutdown | ||
| beast::error_code ec; | ||
| m_stream.socket().shutdown(tcp::socket::shutdown_send, ec); | ||
|
|
||
| // At this point the connection is closed gracefully | ||
| } | ||
|
|
||
| private: | ||
| std::vector<data::person> &m_person; | ||
|
|
||
| // The function object is used to send an HTTP message. | ||
| struct sendLambda | ||
| { | ||
| public: | ||
| explicit sendLambda(Session &self) : m_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. | ||
| m_self.m_res = sp; | ||
|
|
||
| // Write the response | ||
| http::async_write(m_self.m_stream, *sp, beast::bind_front_handler(&Session::onWrite, m_self.shared_from_this(), sp->need_eof())); | ||
| } | ||
|
|
||
| private: | ||
| Session &m_self; | ||
| }; | ||
| beast::tcp_stream m_stream; | ||
| beast::flat_buffer m_buffer; | ||
| http::request<http::string_body> m_req; | ||
| std::shared_ptr<void> m_res; | ||
| sendLambda m_lambda; | ||
| }; |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.