xref: /openbmc/bmcweb/http/http_response.hpp (revision d8a5d5d8)
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(boost::beast::http::status v)
96     {
97         stringResponse->result(v);
98     }
99 
100     boost::beast::http::status result() const
101     {
102         return stringResponse->result();
103     }
104 
105     unsigned resultInt() const
106     {
107         return stringResponse->result_int();
108     }
109 
110     std::string_view reason() const
111     {
112         return stringResponse->reason();
113     }
114 
115     bool isCompleted() const noexcept
116     {
117         return completed;
118     }
119 
120     std::string& body()
121     {
122         return stringResponse->body();
123     }
124 
125     std::string_view getHeaderValue(std::string_view key) const
126     {
127         return stringResponse->base()[key];
128     }
129 
130     void keepAlive(bool k)
131     {
132         stringResponse->keep_alive(k);
133     }
134 
135     bool keepAlive() const
136     {
137         return stringResponse->keep_alive();
138     }
139 
140     void preparePayload()
141     {
142         stringResponse->prepare_payload();
143     }
144 
145     void clear()
146     {
147         BMCWEB_LOG_DEBUG << this << " Clearing response containers";
148         stringResponse.emplace(response_type{});
149         jsonValue.clear();
150         completed = false;
151     }
152 
153     void write(std::string_view bodyPart)
154     {
155         stringResponse->body() += std::string(bodyPart);
156     }
157 
158     void end()
159     {
160         // Only set etag if this request succeeded
161         if (result() == boost::beast::http::status::ok)
162         {
163             // and the json response isn't empty
164             if (!jsonValue.empty())
165             {
166                 size_t hashval = std::hash<nlohmann::json>{}(jsonValue);
167                 std::string hexVal = "\"" + intToHexString(hashval, 8) + "\"";
168                 addHeader(boost::beast::http::field::etag, hexVal);
169             }
170         }
171         if (completed)
172         {
173             BMCWEB_LOG_ERROR << this << " Response was ended twice";
174             return;
175         }
176         completed = true;
177         BMCWEB_LOG_DEBUG << this << " calling completion handler";
178         if (completeRequestHandler)
179         {
180             BMCWEB_LOG_DEBUG << this << " completion handler was valid";
181             completeRequestHandler(*this);
182         }
183     }
184 
185     bool isAlive() const
186     {
187         return isAliveHelper && isAliveHelper();
188     }
189 
190     void setCompleteRequestHandler(std::function<void(Response&)>&& handler)
191     {
192         BMCWEB_LOG_DEBUG << this << " setting completion handler";
193         completeRequestHandler = std::move(handler);
194 
195         // Now that we have a new completion handler attached, we're no longer
196         // complete
197         completed = false;
198     }
199 
200     std::function<void(Response&)> releaseCompleteRequestHandler()
201     {
202         BMCWEB_LOG_DEBUG << this << " releasing completion handler"
203                          << static_cast<bool>(completeRequestHandler);
204         std::function<void(Response&)> ret = completeRequestHandler;
205         completeRequestHandler = nullptr;
206         completed = true;
207         return ret;
208     }
209 
210     void setIsAliveHelper(std::function<bool()>&& handler)
211     {
212         isAliveHelper = std::move(handler);
213     }
214 
215     std::function<bool()> releaseIsAliveHelper()
216     {
217         std::function<bool()> ret = std::move(isAliveHelper);
218         isAliveHelper = nullptr;
219         return ret;
220     }
221 
222   private:
223     bool completed = false;
224     std::function<void(Response&)> completeRequestHandler;
225     std::function<bool()> isAliveHelper;
226 };
227 } // namespace crow
228