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