xref: /openbmc/bmcweb/http/http_response.hpp (revision c51afd54)
1 #pragma once
2 #include "http_file_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::FileBody> 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 
194         jsonValue = nullptr;
195         completed = false;
196         expectedHash = std::nullopt;
197     }
198 
199     std::string computeEtag() const
200     {
201         // Only set etag if this request succeeded
202         if (result() != http::status::ok)
203         {
204             return "";
205         }
206         // and the json response isn't empty
207         if (jsonValue.empty())
208         {
209             return "";
210         }
211         size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
212         return "\"" + intToHexString(hashval, 8) + "\"";
213     }
214 
215     void write(std::string&& bodyPart)
216     {
217         response.body().str() = std::move(bodyPart);
218     }
219 
220     void end()
221     {
222         if (completed)
223         {
224             BMCWEB_LOG_ERROR("{} Response was ended twice", logPtr(this));
225             return;
226         }
227         completed = true;
228         BMCWEB_LOG_DEBUG("{} calling completion handler", logPtr(this));
229         if (completeRequestHandler)
230         {
231             BMCWEB_LOG_DEBUG("{} completion handler was valid", logPtr(this));
232             completeRequestHandler(*this);
233         }
234     }
235 
236     void setCompleteRequestHandler(std::function<void(Response&)>&& handler)
237     {
238         BMCWEB_LOG_DEBUG("{} setting completion handler", logPtr(this));
239         completeRequestHandler = std::move(handler);
240 
241         // Now that we have a new completion handler attached, we're no longer
242         // complete
243         completed = false;
244     }
245 
246     std::function<void(Response&)> releaseCompleteRequestHandler()
247     {
248         BMCWEB_LOG_DEBUG("{} releasing completion handler{}", logPtr(this),
249                          static_cast<bool>(completeRequestHandler));
250         std::function<void(Response&)> ret = completeRequestHandler;
251         completeRequestHandler = nullptr;
252         completed = true;
253         return ret;
254     }
255 
256     void setHashAndHandleNotModified()
257     {
258         // Can only hash if we have content that's valid
259         if (jsonValue.empty() || result() != http::status::ok)
260         {
261             return;
262         }
263         size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
264         std::string hexVal = "\"" + intToHexString(hashval, 8) + "\"";
265         addHeader(http::field::etag, hexVal);
266         if (expectedHash && hexVal == *expectedHash)
267         {
268             jsonValue = nullptr;
269             result(http::status::not_modified);
270         }
271     }
272 
273     void setExpectedHash(std::string_view hash)
274     {
275         expectedHash = hash;
276     }
277 
278     bool openFile(const std::filesystem::path& path,
279                   bmcweb::EncodingType enc = bmcweb::EncodingType::Raw)
280     {
281         boost::beast::error_code ec;
282         response.body().open(path.c_str(), boost::beast::file_mode::read, ec);
283         response.body().encodingType = enc;
284         if (ec)
285         {
286             BMCWEB_LOG_ERROR("Failed to open file {}", path.c_str());
287             return false;
288         }
289         return true;
290     }
291 
292     bool openFd(int fd, bmcweb::EncodingType enc = bmcweb::EncodingType::Raw)
293     {
294         boost::beast::error_code ec;
295         response.body().encodingType = enc;
296         response.body().setFd(fd, ec);
297         if (ec)
298         {
299             BMCWEB_LOG_ERROR("Failed to set fd");
300             return false;
301         }
302         return true;
303     }
304 
305   private:
306     std::optional<std::string> expectedHash;
307     bool completed = false;
308     std::function<void(Response&)> completeRequestHandler;
309 };
310 } // namespace crow
311