xref: /openbmc/bmcweb/http/http_response.hpp (revision 7b9e2569)
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 
84         // Only need to move completion handler if not already completed
85         // Note, there are cases where we might move out of a Response object
86         // while in a completion handler for that response object.  This check
87         // is intended to prevent destructing the functor we are currently
88         // executing from in that case.
89         if (!r.completed)
90         {
91             completeRequestHandler = std::move(r.completeRequestHandler);
92             r.completeRequestHandler = nullptr;
93         }
94         else
95         {
96             completeRequestHandler = nullptr;
97         }
98         completed = r.completed;
99         return *this;
100     }
101 
102     void result(unsigned v)
103     {
104         fields().result(v);
105     }
106 
107     void result(http::status v)
108     {
109         fields().result(v);
110     }
111 
112     void copyBody(const Response& res)
113     {
114         response.body() = res.response.body();
115     }
116 
117     http::status result() const
118     {
119         return fields().result();
120     }
121 
122     unsigned resultInt() const
123     {
124         return fields().result_int();
125     }
126 
127     std::string_view reason() const
128     {
129         return fields().reason();
130     }
131 
132     bool isCompleted() const noexcept
133     {
134         return completed;
135     }
136 
137     const std::string* body()
138     {
139         return &response.body().str();
140     }
141 
142     std::string_view getHeaderValue(std::string_view key) const
143     {
144         return fields()[key];
145     }
146 
147     void keepAlive(bool k)
148     {
149         response.keep_alive(k);
150     }
151 
152     bool keepAlive() const
153     {
154         return response.keep_alive();
155     }
156 
157     std::optional<uint64_t> size()
158     {
159         return response.body().payloadSize();
160     }
161 
162     void preparePayload()
163     {
164         // This code is a throw-free equivalent to
165         // beast::http::message::prepare_payload
166         std::optional<uint64_t> pSize = response.body().payloadSize();
167         if (!pSize)
168         {
169             return;
170         }
171         using http::status;
172         using http::status_class;
173         using http::to_status_class;
174         bool is1XXReturn = to_status_class(result()) ==
175                            status_class::informational;
176         if (*pSize > 0 && (is1XXReturn || result() == status::no_content ||
177                            result() == status::not_modified))
178         {
179             BMCWEB_LOG_CRITICAL("{} Response content provided but code was "
180                                 "no-content or not_modified, which aren't "
181                                 "allowed to have a body",
182                                 logPtr(this));
183             response.content_length(0);
184             return;
185         }
186         response.content_length(*pSize);
187     }
188 
189     void clear()
190     {
191         BMCWEB_LOG_DEBUG("{} Clearing response containers", logPtr(this));
192         response.clear();
193         response.body().clear();
194 
195         jsonValue = nullptr;
196         completed = false;
197         expectedHash = std::nullopt;
198     }
199 
200     std::string computeEtag() const
201     {
202         // Only set etag if this request succeeded
203         if (result() != http::status::ok)
204         {
205             return "";
206         }
207         // and the json response isn't empty
208         if (jsonValue.empty())
209         {
210             return "";
211         }
212         size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
213         return "\"" + intToHexString(hashval, 8) + "\"";
214     }
215 
216     void write(std::string&& bodyPart)
217     {
218         response.body().str() = std::move(bodyPart);
219     }
220 
221     void end()
222     {
223         if (completed)
224         {
225             BMCWEB_LOG_ERROR("{} Response was ended twice", logPtr(this));
226             return;
227         }
228         completed = true;
229         BMCWEB_LOG_DEBUG("{} calling completion handler", logPtr(this));
230         if (completeRequestHandler)
231         {
232             BMCWEB_LOG_DEBUG("{} completion handler was valid", logPtr(this));
233             completeRequestHandler(*this);
234         }
235     }
236 
237     void setCompleteRequestHandler(std::function<void(Response&)>&& handler)
238     {
239         BMCWEB_LOG_DEBUG("{} setting completion handler", logPtr(this));
240         completeRequestHandler = std::move(handler);
241 
242         // Now that we have a new completion handler attached, we're no longer
243         // complete
244         completed = false;
245     }
246 
247     std::function<void(Response&)> releaseCompleteRequestHandler()
248     {
249         BMCWEB_LOG_DEBUG("{} releasing completion handler{}", logPtr(this),
250                          static_cast<bool>(completeRequestHandler));
251         std::function<void(Response&)> ret = completeRequestHandler;
252         completeRequestHandler = nullptr;
253         completed = true;
254         return ret;
255     }
256 
257     void setHashAndHandleNotModified()
258     {
259         // Can only hash if we have content that's valid
260         if (jsonValue.empty() || result() != http::status::ok)
261         {
262             return;
263         }
264         size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
265         std::string hexVal = "\"" + intToHexString(hashval, 8) + "\"";
266         addHeader(http::field::etag, hexVal);
267         if (expectedHash && hexVal == *expectedHash)
268         {
269             jsonValue = nullptr;
270             result(http::status::not_modified);
271         }
272     }
273 
274     void setExpectedHash(std::string_view hash)
275     {
276         expectedHash = hash;
277     }
278 
279     bool openFile(const std::filesystem::path& path,
280                   bmcweb::EncodingType enc = bmcweb::EncodingType::Raw)
281     {
282         boost::beast::error_code ec;
283         response.body().open(path.c_str(), boost::beast::file_mode::read, ec);
284         response.body().encodingType = enc;
285         if (ec)
286         {
287             BMCWEB_LOG_ERROR("Failed to open file {}", path.c_str());
288             return false;
289         }
290         return true;
291     }
292 
293     bool openFd(int fd, bmcweb::EncodingType enc = bmcweb::EncodingType::Raw)
294     {
295         boost::beast::error_code ec;
296         response.body().encodingType = enc;
297         response.body().setFd(fd, ec);
298         if (ec)
299         {
300             BMCWEB_LOG_ERROR("Failed to set fd");
301             return false;
302         }
303         return true;
304     }
305 
306   private:
307     std::optional<std::string> expectedHash;
308     bool completed = false;
309     std::function<void(Response&)> completeRequestHandler;
310 };
311 } // namespace crow
312