xref: /openbmc/bmcweb/http/http_response.hpp (revision ed76121b)
1 #pragma once
2 #include "logging.hpp"
3 #include "nlohmann/json.hpp"
4 
5 #include <boost/beast/http/message.hpp>
6 #include <boost/beast/http/string_body.hpp>
7 #include <utils/hex_utils.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)), completed(res.completed)
45     {
46         jsonValue = std::move(res.jsonValue);
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     }
157 
158     void write(std::string_view bodyPart)
159     {
160         stringResponse->body() += std::string(bodyPart);
161     }
162 
163     void end()
164     {
165         // Only set etag if this request succeeded
166         if (result() == boost::beast::http::status::ok)
167         {
168             // and the json response isn't empty
169             if (!jsonValue.empty())
170             {
171                 size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
172                 std::string hexVal = "\"" + intToHexString(hashval, 8) + "\"";
173                 addHeader(boost::beast::http::field::etag, hexVal);
174             }
175         }
176         if (completed)
177         {
178             BMCWEB_LOG_ERROR << this << " Response was ended twice";
179             return;
180         }
181         completed = true;
182         BMCWEB_LOG_DEBUG << this << " calling completion handler";
183         if (completeRequestHandler)
184         {
185             BMCWEB_LOG_DEBUG << this << " completion handler was valid";
186             completeRequestHandler(*this);
187         }
188     }
189 
190     bool isAlive() const
191     {
192         return isAliveHelper && isAliveHelper();
193     }
194 
195     void setCompleteRequestHandler(std::function<void(Response&)>&& handler)
196     {
197         BMCWEB_LOG_DEBUG << this << " setting completion handler";
198         completeRequestHandler = std::move(handler);
199 
200         // Now that we have a new completion handler attached, we're no longer
201         // complete
202         completed = false;
203     }
204 
205     std::function<void(Response&)> releaseCompleteRequestHandler()
206     {
207         BMCWEB_LOG_DEBUG << this << " releasing completion handler"
208                          << static_cast<bool>(completeRequestHandler);
209         std::function<void(Response&)> ret = completeRequestHandler;
210         completeRequestHandler = nullptr;
211         completed = true;
212         return ret;
213     }
214 
215     void setIsAliveHelper(std::function<bool()>&& handler)
216     {
217         isAliveHelper = std::move(handler);
218     }
219 
220     std::function<bool()> releaseIsAliveHelper()
221     {
222         std::function<bool()> ret = std::move(isAliveHelper);
223         isAliveHelper = nullptr;
224         return ret;
225     }
226 
227   private:
228     bool completed = false;
229     std::function<void(Response&)> completeRequestHandler;
230     std::function<bool()> isAliveHelper;
231 };
232 } // namespace crow
233