Skip to content
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

Added an HTTP server example using C++20 coroutines #385

Open
wants to merge 5 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
466 changes: 466 additions & 0 deletions example/3_advanced/http_server_cpp20/handle_request.cpp

Large diffs are not rendered by default.

36 changes: 36 additions & 0 deletions example/3_advanced/http_server_cpp20/handle_request.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 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)
//

#ifndef BOOST_MYSQL_EXAMPLE_3_ADVANCED_CONNECTION_POOL_HANDLE_REQUEST_HPP
#define BOOST_MYSQL_EXAMPLE_3_ADVANCED_CONNECTION_POOL_HANDLE_REQUEST_HPP

//[example_connection_pool_handle_request_hpp
//
// File: handle_request.hpp
//

#include <boost/mysql/connection_pool.hpp>

#include <boost/asio/awaitable.hpp>
#include <boost/asio/error.hpp>
#include <boost/asio/spawn.hpp>
#include <boost/beast/http/message.hpp>
#include <boost/beast/http/string_body.hpp>

namespace orders {

// Handles an individual HTTP request, producing a response.
boost::asio::awaitable<boost::beast::http::response<boost::beast::http::string_body>> handle_request(
const boost::beast::http::request<boost::beast::http::string_body>& request,
boost::mysql::connection_pool& pool
);

} // namespace orders

//]

#endif
42 changes: 42 additions & 0 deletions example/3_advanced/http_server_cpp20/log_error.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 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)
//

#ifndef BOOST_MYSQL_EXAMPLE_3_ADVANCED_CONNECTION_POOL_LOG_ERROR_HPP
#define BOOST_MYSQL_EXAMPLE_3_ADVANCED_CONNECTION_POOL_LOG_ERROR_HPP

//[example_connection_pool_log_error_hpp
//
// File: log_error.hpp
//

#include <iostream>
#include <mutex>

// Helper function to safely write diagnostics to std::cerr.
// Since we're in a multi-threaded environment, directly writing to std::cerr
// can lead to interleaved output, so we should synchronize calls with a mutex.
// This function is only called in rare cases (e.g. unhandled exceptions),
// so we can afford the synchronization overhead.

namespace orders {

// TODO: is there a better way?
template <class... Args>
void log_error(const Args&... args)
{
static std::mutex mtx;

// Acquire the mutex, then write the passed arguments to std::cerr.
std::unique_lock<std::mutex> lock(mtx);
std::cerr << (... << args) << std::endl;
}

} // namespace orders

//]

#endif
175 changes: 175 additions & 0 deletions example/3_advanced/http_server_cpp20/main.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
//
// Copyright (c) 2019-2024 Ruben Perez Hidalgo (rubenperez038 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)
//

#include <boost/mysql/pfr.hpp>

#include <boost/asio/awaitable.hpp>
#if defined(BOOST_ASIO_HAS_CO_AWAIT) && BOOST_PFR_CORE_NAME_ENABLED

//[example_http_server_cpp20_main_cpp

/**
* TODO: review this
* This example demonstrates how to use a connection_pool.
* It implements a minimal REST API to manage notes.
* A note is a simple object containing a user-defined title and content.
* The REST API offers CRUD operations on such objects:
* GET /products?search={s} Returns a list of products
* GET /orders Returns all orders
* GET /orders?id={} Returns a single order
* POST /orders Creates a new order.
* POST /orders/items?order-id={} Adds a new order item to an existing order.
* DELETE /orders/items?id={} Deletes an order item
* POST /orders/checkout?id={} Checks out an order
* POST /orders/complete?id={} Completes an order
*
* Notes are stored in MySQL. The note_repository class encapsulates
* access to MySQL, offering friendly functions to manipulate notes.
* server.cpp encapsulates all the boilerplate to launch an HTTP server,
* match URLs to API endpoints, and invoke the relevant note_repository functions.
* All communication happens asynchronously. We use stackful coroutines to simplify
* development, using boost::asio::spawn and boost::asio::yield_context.
* This example requires linking to Boost::context, Boost::json and Boost::url.
*/

#include <boost/mysql/any_address.hpp>
#include <boost/mysql/connection_pool.hpp>
#include <boost/mysql/pfr.hpp>
#include <boost/mysql/pool_params.hpp>

#include <boost/asio/awaitable.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
#include <boost/asio/signal_set.hpp>
#include <boost/asio/thread_pool.hpp>
#include <boost/system/error_code.hpp>

#include <cstddef>
#include <cstdlib>
#include <exception>
#include <iostream>
#include <memory>
#include <string>

#include "server.hpp"

using namespace orders;
namespace mysql = boost::mysql;
namespace asio = boost::asio;

// The number of threads to use
static constexpr std::size_t num_threads = 5;

int main_impl(int argc, char* argv[])
{
// Check command line arguments.
if (argc != 5)
{
std::cerr << "Usage: " << argv[0] << " <username> <password> <mysql-hostname> <port>\n";
return EXIT_FAILURE;
}

// Application config
const char* mysql_username = argv[1];
const char* mysql_password = argv[2];
const char* mysql_hostname = argv[3];
auto port = static_cast<unsigned short>(std::stoi(argv[4]));

// An event loop, where the application will run.
// We will use the main thread to run the pool, too, so we use
// one thread less than configured
asio::thread_pool th_pool(num_threads - 1);

// Create a connection pool
mysql::connection_pool pool(
// Use the thread pool as execution context
th_pool,

// Pool configuration
mysql::pool_params{
// Connect using TCP, to the given hostname and using the default port
.server_address = mysql::host_and_port{mysql_hostname},

// Authenticate using the given username
.username = mysql_username,

// Password for the above username
.password = mysql_password,

// Database to use when connecting
.database = "boost_mysql_examples",

// Using thread_safe will make the pool thread-safe by internally
// creating and using a strand.
// This allows us to share the pool between sessions, which may run
// concurrently, on different threads.
.thread_safe = true,
}
);

// Launch the MySQL pool
pool.async_run(asio::detached);

// A signal_set allows us to intercept SIGINT and SIGTERM and
// exit gracefully
asio::signal_set signals{th_pool.get_executor(), SIGINT, SIGTERM};

// Capture SIGINT and SIGTERM to perform a clean shutdown
signals.async_wait([&th_pool](boost::system::error_code, int) {
// Stop the execution context. This will cause main to exit
th_pool.stop();
});

// Start listening for HTTP connections. This will run until the context is stopped
asio::co_spawn(
th_pool,
[&pool, port] { return listener(pool, port); },
[](std::exception_ptr exc) {
if (exc)
std::rethrow_exception(exc);
}
);

// Attach the current thread to the thread pool. This will block
// until stop() is called
th_pool.attach();

// Wait until all threads have exited
th_pool.join();

std::cout << "Server exiting" << std::endl;

// (If we get here, it means we got a SIGINT or SIGTERM)
return EXIT_SUCCESS;
}

int main(int argc, char** argv)
{
try
{
main_impl(argc, argv);
}
catch (const std::exception& err)
{
std::cerr << "Error: " << err.what() << std::endl;
return 1;
}
}

//]

#else

#include <iostream>

int main()
{
std::cout << "Sorry, your compiler doesn't have the required capabilities to run this example"
<< std::endl;
}

#endif
Loading
Loading