xref: /openbmc/bmcweb/http/http_server.hpp (revision c6178aba)
1 #pragma once
2 
3 #include "http_connection.hpp"
4 #include "logging.hpp"
5 #include "ssl_key_handler.hpp"
6 
7 #include <boost/asio/ip/address.hpp>
8 #include <boost/asio/ip/tcp.hpp>
9 #include <boost/asio/signal_set.hpp>
10 #include <boost/asio/ssl/context.hpp>
11 #include <boost/asio/ssl/stream.hpp>
12 #include <boost/asio/steady_timer.hpp>
13 #include <boost/beast/core/stream_traits.hpp>
14 
15 #include <atomic>
16 #include <chrono>
17 #include <cstdint>
18 #include <filesystem>
19 #include <future>
20 #include <memory>
21 #include <string>
22 #include <utility>
23 #include <vector>
24 
25 namespace crow
26 {
27 
28 template <typename Handler, typename Adaptor = boost::asio::ip::tcp::socket>
29 class Server
30 {
31     using self_t = Server<Handler, Adaptor>;
32 
33   public:
34     Server(Handler* handlerIn, boost::asio::ip::tcp::acceptor&& acceptorIn,
35            std::shared_ptr<boost::asio::ssl::context> adaptorCtxIn,
36            std::shared_ptr<boost::asio::io_context> io) :
37         ioService(std::move(io)), acceptor(std::move(acceptorIn)),
38         signals(*ioService, SIGINT, SIGTERM, SIGHUP), handler(handlerIn),
39         adaptorCtx(std::move(adaptorCtxIn))
40     {}
41 
42     void updateDateStr()
43     {
44         time_t lastTimeT = time(nullptr);
45         tm myTm{};
46 
47         gmtime_r(&lastTimeT, &myTm);
48 
49         dateStr.resize(100);
50         size_t dateStrSz = strftime(dateStr.data(), dateStr.size() - 1,
51                                     "%a, %d %b %Y %H:%M:%S GMT", &myTm);
52         dateStr.resize(dateStrSz);
53     }
54 
55     void run()
56     {
57         loadCertificate();
58         updateDateStr();
59 
60         getCachedDateStr = [this]() -> std::string {
61             static std::chrono::time_point<std::chrono::steady_clock>
62                 lastDateUpdate = std::chrono::steady_clock::now();
63             if (std::chrono::steady_clock::now() - lastDateUpdate >=
64                 std::chrono::seconds(10))
65             {
66                 lastDateUpdate = std::chrono::steady_clock::now();
67                 updateDateStr();
68             }
69             return dateStr;
70         };
71 
72         BMCWEB_LOG_INFO("bmcweb server is running, local endpoint {}",
73                         acceptor.local_endpoint().address().to_string());
74         startAsyncWaitForSignal();
75         doAccept();
76     }
77 
78     void loadCertificate()
79     {
80         if constexpr (BMCWEB_INSECURE_DISABLE_SSL)
81         {
82             return;
83         }
84 
85         auto sslContext = ensuressl::getSslServerContext();
86 
87         adaptorCtx = sslContext;
88         handler->ssl(std::move(sslContext));
89     }
90 
91     void startAsyncWaitForSignal()
92     {
93         signals.async_wait(
94             [this](const boost::system::error_code& ec, int signalNo) {
95                 if (ec)
96                 {
97                     BMCWEB_LOG_INFO("Error in signal handler{}", ec.message());
98                 }
99                 else
100                 {
101                     if (signalNo == SIGHUP)
102                     {
103                         BMCWEB_LOG_INFO("Receivied reload signal");
104                         loadCertificate();
105                         startAsyncWaitForSignal();
106                     }
107                     else
108                     {
109                         stop();
110                     }
111                 }
112             });
113     }
114 
115     void stop()
116     {
117         ioService->stop();
118     }
119     using Socket = boost::beast::lowest_layer_type<Adaptor>;
120     using SocketPtr = std::unique_ptr<Socket>;
121 
122     void afterAccept(SocketPtr socket, const boost::system::error_code& ec)
123     {
124         if (ec)
125         {
126             BMCWEB_LOG_ERROR("Failed to accept socket {}", ec);
127             return;
128         }
129 
130         boost::asio::steady_timer timer(*ioService);
131         std::shared_ptr<Connection<Adaptor, Handler>> connection;
132 
133         if constexpr (std::is_same<Adaptor,
134                                    boost::asio::ssl::stream<
135                                        boost::asio::ip::tcp::socket>>::value)
136         {
137             if (adaptorCtx == nullptr)
138             {
139                 BMCWEB_LOG_CRITICAL(
140                     "Asked to launch TLS socket but no context available");
141                 return;
142             }
143             connection = std::make_shared<Connection<Adaptor, Handler>>(
144                 handler, std::move(timer), getCachedDateStr,
145                 Adaptor(std::move(*socket), *adaptorCtx));
146         }
147         else
148         {
149             connection = std::make_shared<Connection<Adaptor, Handler>>(
150                 handler, std::move(timer), getCachedDateStr,
151                 Adaptor(std::move(*socket)));
152         }
153 
154         boost::asio::post(*ioService, [connection] { connection->start(); });
155 
156         doAccept();
157     }
158 
159     void doAccept()
160     {
161         if (ioService == nullptr)
162         {
163             BMCWEB_LOG_CRITICAL("IoService was null");
164             return;
165         }
166 
167         SocketPtr socket = std::make_unique<Socket>(*ioService);
168         // Keep a raw pointer so when the socket is moved, the pointer is still
169         // valid
170         Socket* socketPtr = socket.get();
171 
172         acceptor.async_accept(
173             *socketPtr,
174             std::bind_front(&self_t::afterAccept, this, std::move(socket)));
175     }
176 
177   private:
178     std::shared_ptr<boost::asio::io_context> ioService;
179     std::function<std::string()> getCachedDateStr;
180     boost::asio::ip::tcp::acceptor acceptor;
181     boost::asio::signal_set signals;
182 
183     std::string dateStr;
184 
185     Handler* handler;
186 
187     std::shared_ptr<boost::asio::ssl::context> adaptorCtx;
188 };
189 } // namespace crow
190