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