Add callback for response handling to HttpClient

Adds sendDataWithCallback() which allows the caller to include a
callback specifying how to handle the response to that
request.  This will be utilized for Redfish Aggregation
including returning the responses received when forwarding
requests to satellite BMCs.

Change-Id: I93826c8b254a5f28a982295d4145453352a90fae
Signed-off-by: Carson Labrado <clabrado@google.com>
diff --git a/http/http_client.hpp b/http/http_client.hpp
index 1637823..a672447 100644
--- a/http/http_client.hpp
+++ b/http/http_client.hpp
@@ -73,11 +73,12 @@
 struct PendingRequest
 {
     std::string requestData;
-    std::function<void(bool, uint32_t)> callback;
+    std::function<void(bool, uint32_t, Response&)> callback;
     RetryPolicyData retryPolicy;
-    PendingRequest(const std::string& requestData,
-                   const std::function<void(bool, uint32_t)>& callback,
-                   const RetryPolicyData& retryPolicy) :
+    PendingRequest(
+        const std::string& requestData,
+        const std::function<void(bool, uint32_t, Response&)>& callback,
+        const RetryPolicyData& retryPolicy) :
         requestData(requestData),
         callback(callback), retryPolicy(retryPolicy)
     {}
@@ -105,9 +106,10 @@
         boost::beast::http::response_parser<boost::beast::http::string_body>>
         parser;
     boost::beast::flat_static_buffer<httpReadBodyLimit> buffer;
+    Response res;
 
     // Ascync callables
-    std::function<void(bool, uint32_t)> callback;
+    std::function<void(bool, uint32_t, Response&)> callback;
     crow::async_resolve::Resolver resolver;
     boost::beast::tcp_stream conn;
     boost::asio::steady_timer timer;
@@ -258,7 +260,12 @@
                 BMCWEB_LOG_DEBUG << "recvMessage() keepalive : "
                                  << self->parser->keep_alive();
 
-                self->callback(self->parser->keep_alive(), self->connId);
+                // Copy the response into a Response object so that it can be
+                // processed by the callback function.
+                self->res.clear();
+                self->res.stringResponse = self->parser->release();
+                self->callback(self->parser->keep_alive(), self->connId,
+                               self->res);
             });
     }
 
@@ -269,16 +276,22 @@
             BMCWEB_LOG_ERROR << "Maximum number of retries reached.";
             BMCWEB_LOG_DEBUG << "Retry policy: "
                              << retryPolicy.retryPolicyAction;
+
+            // We want to return a 502 to indicate there was an error with the
+            // external server
+            res.clear();
+            redfish::messages::operationFailed(res);
+
             if (retryPolicy.retryPolicyAction == "TerminateAfterRetries")
             {
                 // TODO: delete subscription
                 state = ConnState::terminated;
-                callback(false, connId);
+                callback(false, connId, res);
             }
             if (retryPolicy.retryPolicyAction == "SuspendRetries")
             {
                 state = ConnState::suspended;
-                callback(false, connId);
+                callback(false, connId, res);
             }
             // Reset the retrycount to zero so that client can try connecting
             // again if needed
@@ -483,12 +496,18 @@
         }
     }
 
-    void sendData(std::string& data, const RetryPolicyData& retryPolicy)
+    void sendData(std::string& data, const RetryPolicyData& retryPolicy,
+                  std::function<void(Response&)>& resHandler)
     {
         std::weak_ptr<ConnectionPool> weakSelf = weak_from_this();
 
         // Callback to be called once the request has been sent
-        auto cb = [weakSelf](bool keepAlive, uint32_t connId) {
+        auto cb = [weakSelf, resHandler](bool keepAlive, uint32_t connId,
+                                         Response& res) {
+            // Allow provided callback to perform additional processing of the
+            // request
+            resHandler(res);
+
             // If requests remain in the queue then we want to reuse this
             // connection to send the next request
             std::shared_ptr<ConnectionPool> self = weakSelf.lock();
@@ -600,6 +619,14 @@
     std::unordered_map<std::string, RetryPolicyData> retryInfo;
     HttpClient() = default;
 
+    // Used as a dummy callback by sendData() in order to call
+    // sendDataWithCallback()
+    static void genericResHandler(Response& res)
+    {
+        BMCWEB_LOG_DEBUG << "Response handled with return code: "
+                         << std::to_string(res.resultInt());
+    };
+
   public:
     HttpClient(const HttpClient&) = delete;
     HttpClient& operator=(const HttpClient&) = delete;
@@ -613,12 +640,29 @@
         return handler;
     }
 
+    // Send a request to destIP:destPort where additional processing of the
+    // result is not required
     void sendData(std::string& data, const std::string& id,
                   const std::string& destIP, const uint16_t destPort,
                   const std::string& destUri,
                   const boost::beast::http::fields& httpHeader,
                   std::string& retryPolicyName)
     {
+        std::function<void(Response&)> cb = genericResHandler;
+        sendDataWithCallback(data, id, destIP, destPort, destUri, httpHeader,
+                             retryPolicyName, cb);
+    }
+
+    // Send request to destIP:destPort and use the provided callback to
+    // handle the response
+    void sendDataWithCallback(std::string& data, const std::string& id,
+                              const std::string& destIP,
+                              const uint16_t destPort,
+                              const std::string& destUri,
+                              const boost::beast::http::fields& httpHeader,
+                              std::string& retryPolicyName,
+                              std::function<void(Response&)>& resHandler)
+    {
         std::string clientKey = destIP + ":" + std::to_string(destPort);
         // Use nullptr to avoid creating a ConnectionPool each time
         auto result = connectionPools.try_emplace(clientKey, nullptr);
@@ -647,7 +691,7 @@
 
         // Send the data using either the existing connection pool or the newly
         // created connection pool
-        result.first->second->sendData(data, policy.first->second);
+        result.first->second->sendData(data, policy.first->second, resHandler);
     }
 
     void setRetryConfig(const uint32_t retryAttempts,
diff --git a/http/http_response.hpp b/http/http_response.hpp
index 58a8029..6c842d7 100644
--- a/http/http_response.hpp
+++ b/http/http_response.hpp
@@ -76,7 +76,7 @@
         return stringResponse->result();
     }
 
-    unsigned resultInt()
+    unsigned resultInt() const
     {
         return stringResponse->result_int();
     }