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