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