1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
|
//
// Copyright (c) 2019-2025 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/asio/awaitable.hpp>
#include <boost/pfr/config.hpp>
#if defined(BOOST_ASIO_HAS_CO_AWAIT) && BOOST_PFR_CORE_NAME_ENABLED
//[example_http_server_cpp20_main_cpp
/**
* Implements a HTTP REST API using Boost.MySQL and Boost.Beast.
* The API models a simplified order management system for an online store.
* Using the API, users can query the store's product catalog, create and
* edit orders, and check them out for payment.
*
* The API defines the following endpoints:
*
* 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 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
*
* Each order can have any number of order items. An order item
* represents an individual product that has been added to an order.
* Orders are created empty, in a 'draft' state. Items can then be
* added and removed from the order. After adding the desired items,
* orders can be checked out for payment. A third-party service, like Stripe,
* would be used to collect the payment. For simplicity, we've left this part
* out of the example. Once checked out, an order is no longer editable.
* Finally, after successful payment, order are transitioned to the
* 'complete' status.
*
* The server uses C++20 coroutines and is multi-threaded.
* It also requires linking to Boost::json and Boost::url.
* The database schema is defined in db_setup.sql, in the same directory as this file.
* You need to source this file before running the example.
*/
#include <boost/mysql/any_address.hpp>
#include <boost/mysql/connection_pool.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 <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_orders",
// We're using multi-queries
.multi_queries = true,
// 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(
// Use the thread pool to run the listener coroutine
th_pool,
// The coroutine to run
[&pool, port] { return run_server(pool, port); },
// If an exception is thrown in the listener coroutine, propagate it
[](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
|