Github User Fetcher 1.0.0
C Application with Server and GUI
Loading...
Searching...
No Matches
server.cpp
Go to the documentation of this file.
1/**
2 * @file server.cpp
3 * @brief Implements an HTTP server using Boost.Beast and Boost.Asio to serve
4 * GitHub user data.
5 */
6
7#include "server.hpp"
8#include "user.hpp"
9
10#include <boost/asio.hpp>
11#include <boost/asio/signal_set.hpp>
12#include <boost/beast/core.hpp>
13#include <boost/beast/http.hpp>
14#include <boost/beast/version.hpp>
15#include <nlohmann/json.hpp>
16
17#include <iostream>
18#include <memory>
19#include <string>
20#include <thread>
21#include <vector>
22
23namespace beast = boost::beast;
24namespace http = beast::http;
25namespace net = boost::asio;
26using tcp = net::ip::tcp;
27
28/**
29 * @class HttpSession
30 * @brief Manages a single HTTP session with a client.
31 *
32 * This class handles reading an HTTP request, processing it by fetching a
33 * GitHub user's data, and sending an appropriate HTTP response. Each client
34 * connection is handled in its own instance.
35 */
36class HttpSession : public std::enable_shared_from_this<HttpSession> {
37public:
38 /**
39 * @brief Constructs an HttpSession with the given socket.
40 * @param socket The TCP socket associated with the client connection.
41 */
42 explicit HttpSession(tcp::socket socket) : socket_(std::move(socket)) {}
43
44 /**
45 * @brief Starts processing the HTTP session.
46 */
47 void start() { read_request(); }
48
49private:
50 tcp::socket socket_; ///< The socket used for communication with the client.
51 beast::flat_buffer buffer_; ///< Buffer for reading data from the socket.
52 http::request<http::string_body> req_; ///< HTTP request object.
53
54 /**
55 * @brief Initiates asynchronous read of the HTTP request from the client.
56 */
57 void read_request() {
58 auto self = shared_from_this();
59 http::async_read(socket_, buffer_, req_,
60 [self](beast::error_code error_code, std::size_t) {
61 if (!error_code) {
62 self->handle_request();
63 }
64 });
65 }
66
67 /**
68 * @brief Processes the HTTP request and sends a corresponding response.
69 *
70 * If the request target contains a valid GitHub username, it fetches the
71 * user's data and returns it as JSON. Otherwise, it responds with an error
72 * message.
73 */
75 auto res = std::make_shared<http::response<http::string_body>>();
76 res->version(req_.version());
77 res->keep_alive(false);
78
79 std::string target(req_.target());
80 std::string username = target.length() > 1 ? target.substr(1) : "";
81
82 if (username.empty()) {
83 res->result(http::status::bad_request);
84 res->set(http::field::content_type, "text/plain");
85 res->body() = "Missing username in request URI.";
86 } else {
87 try {
88 User user = User::fetch_github_user(username);
89 nlohmann::json json = {{"login", user.login},
90 {"name", user.name},
91 {"company", user.company},
92 {"location", user.location}};
93 res->result(http::status::ok);
94 res->set(http::field::content_type, "application/json");
95 res->body() = json.dump();
96 } catch (const std::exception &e) {
97 res->result(http::status::not_found);
98 res->set(http::field::content_type, "text/plain");
99 res->body() = std::string("Error fetching user: ") + e.what();
100 }
101 }
102
103 res->prepare_payload();
104
105 auto self = shared_from_this();
106 http::async_write(
107 socket_, *res,
108 [self, res](beast::error_code /*error_code*/, std::size_t) {
109 try {
110 self->socket_.shutdown(tcp::socket::shutdown_send);
111 } catch (const boost::system::system_error &system_error) {
112 std::cerr << "Shutdown error: " << system_error.what() << '\n';
113 }
114 });
115 }
116};
117
118/**
119 * @brief Starts the HTTP server on the specified port.
120 *
121 * Sets up an `io_context`, signal handlers for graceful shutdown, and an
122 * asynchronous TCP acceptor to listen for and handle incoming HTTP connections
123 * using `HttpSession`.
124 *
125 * The server runs in a multi-threaded fashion, utilizing all available hardware
126 * threads.
127 *
128 * @param port The port number on which the HTTP server should listen.
129 */
130void start_http_server(int port) {
131 try {
132 net::io_context ioc{};
133
134 // Set up signal handling for clean shutdown
135 net::signal_set signals(ioc, SIGINT, SIGTERM);
136 signals.async_wait([&](auto, auto) {
137 std::cout << "\nShutting down server...\n";
138 ioc.stop();
139 });
140
141 tcp::acceptor acceptor{ioc, {tcp::v4(), static_cast<unsigned short>(port)}};
142
143 /**
144 * @brief Lambda for accepting incoming connections recursively.
145 *
146 * This lambda captures itself and re-issues accept calls until the server
147 * is stopped.
148 */
149 auto do_accept = [&](auto &&self) -> void {
150 acceptor.async_accept(
151 [&](beast::error_code error_code, tcp::socket socket) {
152 if (!error_code) {
153 std::make_shared<HttpSession>(std::move(socket))->start();
154 }
155 if (!ioc.stopped()) {
156 self(self); // Accept next connection
157 }
158 });
159 };
160
161 do_accept(do_accept);
162
163 std::cout << "Server started on port " << port
164 << ". Visit http://localhost:" << port << "/USERNAME\n";
165 std::cout << "Press Ctrl+C to quit.\n";
166
167 // Run io_context on multiple threads
168 const auto num_threads = std::max(1U, std::thread::hardware_concurrency());
169 std::vector<std::thread> threads;
170 threads.reserve(num_threads - 1);
171
172 for (unsigned i = 0; i < num_threads - 1; ++i) {
173 threads.emplace_back([&ioc]() { ioc.run(); });
174 }
175
176 ioc.run();
177
178 for (auto &thread : threads) {
179 thread.join();
180 }
181 } catch (const std::exception &e) {
182 std::cerr << "Server error: " << e.what() << "\n";
183 }
184}
Manages a single HTTP session with a client.
Definition server.cpp:36
void start()
Starts processing the HTTP session.
Definition server.cpp:47
void read_request()
Initiates asynchronous read of the HTTP request from the client.
Definition server.cpp:57
http::request< http::string_body > req_
HTTP request object.
Definition server.cpp:52
tcp::socket socket_
The socket used for communication with the client.
Definition server.cpp:50
beast::flat_buffer buffer_
Buffer for reading data from the socket.
Definition server.cpp:51
HttpSession(tcp::socket socket)
Constructs an HttpSession with the given socket.
Definition server.cpp:42
void handle_request()
Processes the HTTP request and sends a corresponding response.
Definition server.cpp:74
Definition doctest.h:522
decltype(sizeof(void *)) size_t
Definition doctest.h:524
net::ip::tcp tcp
Definition server.cpp:26
void start_http_server(int port)
Starts the HTTP server on the specified port.
Definition server.cpp:130
Structure representing a GitHub user.
Definition user.h:9
const char * name
Full name.
Definition user.h:11
const char * login
GitHub username.
Definition user.h:10
const char * location
User's location.
Definition user.h:13
const char * company
Company name.
Definition user.h:12