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