xref: /openbmc/bmcweb/http/http_response.hpp (revision 5ebb9d33)
1 #pragma once
2 #include "logging.hpp"
3 #include "utils/hex_utils.hpp"
4 
5 #include <boost/beast/http/message.hpp>
6 #include <boost/beast/http/string_body.hpp>
7 #include <nlohmann/json.hpp>
8 
9 #include <optional>
10 #include <string>
11 #include <string_view>
12 
13 namespace crow
14 {
15 
16 template <typename Adaptor, typename Handler>
17 class Connection;
18 
19 struct Response
20 {
21     template <typename Adaptor, typename Handler>
22     friend class crow::Connection;
23     using response_type =
24         boost::beast::http::response<boost::beast::http::string_body>;
25 
26     response_type stringResponse;
27 
28     nlohmann::json jsonValue;
29 
30     void addHeader(std::string_view key, std::string_view value)
31     {
32         stringResponse.insert(key, value);
33     }
34 
35     void addHeader(boost::beast::http::field key, std::string_view value)
36     {
37         stringResponse.insert(key, value);
38     }
39 
40     void clearHeader(boost::beast::http::field key)
41     {
42         stringResponse.erase(key);
43     }
44 
45     Response() = default;
46 
47     Response(Response&& res) noexcept :
48         stringResponse(std::move(res.stringResponse)),
49         jsonValue(std::move(res.jsonValue)), completed(res.completed)
50     {
51         // See note in operator= move handler for why this is needed.
52         if (!res.completed)
53         {
54             completeRequestHandler = std::move(res.completeRequestHandler);
55             res.completeRequestHandler = nullptr;
56         }
57         isAliveHelper = res.isAliveHelper;
58         res.isAliveHelper = nullptr;
59     }
60 
61     ~Response() = default;
62 
63     Response(const Response&) = delete;
64 
65     Response& operator=(const Response& r) = delete;
66 
67     Response& operator=(Response&& r) noexcept
68     {
69         BMCWEB_LOG_DEBUG("Moving response containers; this: {}; other: {}",
70                          logPtr(this), logPtr(&r));
71         if (this == &r)
72         {
73             return *this;
74         }
75         stringResponse = std::move(r.stringResponse);
76         r.stringResponse.clear();
77         jsonValue = std::move(r.jsonValue);
78 
79         // Only need to move completion handler if not already completed
80         // Note, there are cases where we might move out of a Response object
81         // while in a completion handler for that response object.  This check
82         // is intended to prevent destructing the functor we are currently
83         // executing from in that case.
84         if (!r.completed)
85         {
86             completeRequestHandler = std::move(r.completeRequestHandler);
87             r.completeRequestHandler = nullptr;
88         }
89         else
90         {
91             completeRequestHandler = nullptr;
92         }
93         completed = r.completed;
94         isAliveHelper = std::move(r.isAliveHelper);
95         r.isAliveHelper = nullptr;
96         return *this;
97     }
98 
99     void result(unsigned v)
100     {
101         stringResponse.result(v);
102     }
103 
104     void result(boost::beast::http::status v)
105     {
106         stringResponse.result(v);
107     }
108 
109     boost::beast::http::status result() const
110     {
111         return stringResponse.result();
112     }
113 
114     unsigned resultInt() const
115     {
116         return stringResponse.result_int();
117     }
118 
119     std::string_view reason() const
120     {
121         return stringResponse.reason();
122     }
123 
124     bool isCompleted() const noexcept
125     {
126         return completed;
127     }
128 
129     std::string& body()
130     {
131         return stringResponse.body();
132     }
133 
134     std::string_view getHeaderValue(std::string_view key) const
135     {
136         return stringResponse.base()[key];
137     }
138 
139     void keepAlive(bool k)
140     {
141         stringResponse.keep_alive(k);
142     }
143 
144     bool keepAlive() const
145     {
146         return stringResponse.keep_alive();
147     }
148 
149     void preparePayload()
150     {
151         // This code is a throw-free equivalent to
152         // beast::http::message::prepare_payload
153         boost::optional<uint64_t> pSize = stringResponse.payload_size();
154         using boost::beast::http::status;
155         using boost::beast::http::status_class;
156         using boost::beast::http::to_status_class;
157         if (!pSize)
158         {
159             pSize = 0;
160         }
161         else
162         {
163             bool is1XXReturn = to_status_class(stringResponse.result()) ==
164                                status_class::informational;
165             if (*pSize > 0 &&
166                 (is1XXReturn || stringResponse.result() == status::no_content ||
167                  stringResponse.result() == status::not_modified))
168             {
169                 BMCWEB_LOG_CRITICAL(
170                     "{} Response content provided but code was no-content or not_modified, which aren't allowed to have a body",
171                     logPtr(this));
172                 pSize = 0;
173                 body().clear();
174             }
175         }
176         stringResponse.content_length(*pSize);
177     }
178 
179     void clear()
180     {
181         BMCWEB_LOG_DEBUG("{} Clearing response containers", logPtr(this));
182         stringResponse.clear();
183         stringResponse.body().shrink_to_fit();
184         jsonValue = nullptr;
185         completed = false;
186         expectedHash = std::nullopt;
187     }
188 
189     void write(std::string_view bodyPart)
190     {
191         stringResponse.body() += std::string(bodyPart);
192     }
193 
194     std::string computeEtag() const
195     {
196         // Only set etag if this request succeeded
197         if (result() != boost::beast::http::status::ok)
198         {
199             return "";
200         }
201         // and the json response isn't empty
202         if (jsonValue.empty())
203         {
204             return "";
205         }
206         size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
207         return "\"" + intToHexString(hashval, 8) + "\"";
208     }
209 
210     void end()
211     {
212         std::string etag = computeEtag();
213         if (!etag.empty())
214         {
215             addHeader(boost::beast::http::field::etag, etag);
216         }
217         if (completed)
218         {
219             BMCWEB_LOG_ERROR("{} Response was ended twice", logPtr(this));
220             return;
221         }
222         completed = true;
223         BMCWEB_LOG_DEBUG("{} calling completion handler", logPtr(this));
224         if (completeRequestHandler)
225         {
226             BMCWEB_LOG_DEBUG("{} completion handler was valid", logPtr(this));
227             completeRequestHandler(*this);
228         }
229     }
230 
231     bool isAlive() const
232     {
233         return isAliveHelper && isAliveHelper();
234     }
235 
236     void setCompleteRequestHandler(std::function<void(Response&)>&& handler)
237     {
238         BMCWEB_LOG_DEBUG("{} setting completion handler", logPtr(this));
239         completeRequestHandler = std::move(handler);
240 
241         // Now that we have a new completion handler attached, we're no longer
242         // complete
243         completed = false;
244     }
245 
246     std::function<void(Response&)> releaseCompleteRequestHandler()
247     {
248         BMCWEB_LOG_DEBUG("{} releasing completion handler{}", logPtr(this),
249                          static_cast<bool>(completeRequestHandler));
250         std::function<void(Response&)> ret = completeRequestHandler;
251         completeRequestHandler = nullptr;
252         completed = true;
253         return ret;
254     }
255 
256     void setIsAliveHelper(std::function<bool()>&& handler)
257     {
258         isAliveHelper = std::move(handler);
259     }
260 
261     std::function<bool()> releaseIsAliveHelper()
262     {
263         std::function<bool()> ret = std::move(isAliveHelper);
264         isAliveHelper = nullptr;
265         return ret;
266     }
267 
268     void setHashAndHandleNotModified()
269     {
270         // Can only hash if we have content that's valid
271         if (jsonValue.empty() || result() != boost::beast::http::status::ok)
272         {
273             return;
274         }
275         size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
276         std::string hexVal = "\"" + intToHexString(hashval, 8) + "\"";
277         addHeader(boost::beast::http::field::etag, hexVal);
278         if (expectedHash && hexVal == *expectedHash)
279         {
280             jsonValue = nullptr;
281             result(boost::beast::http::status::not_modified);
282         }
283     }
284 
285     void setExpectedHash(std::string_view hash)
286     {
287         expectedHash = hash;
288     }
289 
290   private:
291     std::optional<std::string> expectedHash;
292     bool completed = false;
293     std::function<void(Response&)> completeRequestHandler;
294     std::function<bool()> isAliveHelper;
295 };
296 } // namespace crow
297