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