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