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