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 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