Move to file_body in boost

As is, it reads the whole file into memory before sending it.  While
fairly fast for the user, this wastes ram, and makes bmcweb less useful
on less capable systems.

This patch enables using the boost::beast::http::file_body type, which
has more efficient serialization semantics than using a std::string.  To
do this, it adds a openFile() handler to http::Response, which can be
used to properly open a file.  Once the file is opened, the existing
string body is ignored, and the file payload is sent instead.
openFile() also returns success or failure, to allow users to properly
handle 404s and other errors.

To prove that it works, I moved over every instance of direct use of the
body() method over to using this, including the webasset handler.  The
webasset handler specifically should help with system load when doing an
initial page load of the webui.

Tested:
Redfish service validator passes.

Change-Id: Ic7ea9ffefdbc81eb985de7edc0fac114822994ad
Signed-off-by: Ed Tanous <ed@tanous.net>
diff --git a/redfish-core/include/redfish_aggregator.hpp b/redfish-core/include/redfish_aggregator.hpp
index 335c64e..680e1b0 100644
--- a/redfish-core/include/redfish_aggregator.hpp
+++ b/redfish-core/include/redfish_aggregator.hpp
@@ -728,7 +728,7 @@
         std::function<void(crow::Response&)> cb =
             std::bind_front(processResponse, prefix, asyncResp);
 
-        std::string data = thisReq.req.body();
+        std::string data = thisReq.body();
         boost::urls::url url(sat->second);
         url.set_path(path);
         if (targetURI.has_query())
@@ -756,7 +756,7 @@
             {
                 url.set_query(thisReq.url().query());
             }
-            std::string data = thisReq.req.body();
+            std::string data = thisReq.body();
             client.sendDataWithCallback(std::move(data), url, thisReq.fields(),
                                         thisReq.method(), cb);
         }
@@ -781,7 +781,7 @@
             boost::urls::url url(sat.second);
             url.set_path(thisReq.url().path());
 
-            std::string data = thisReq.req.body();
+            std::string data = thisReq.body();
 
             client.sendDataWithCallback(std::move(data), url, thisReq.fields(),
                                         thisReq.method(), cb);
@@ -874,8 +874,8 @@
         if (boost::iequals(contentType, "application/json") ||
             boost::iequals(contentType, "application/json; charset=utf-8"))
         {
-            nlohmann::json jsonVal = nlohmann::json::parse(resp.body(), nullptr,
-                                                           false);
+            nlohmann::json jsonVal = nlohmann::json::parse(*resp.body(),
+                                                           nullptr, false);
             if (jsonVal.is_discarded())
             {
                 BMCWEB_LOG_ERROR("Error parsing satellite response as JSON");
@@ -898,7 +898,7 @@
         {
             // We allow any Content-Type that is not "application/json" now
             asyncResp->res.result(resp.result());
-            asyncResp->res.write(resp.body());
+            asyncResp->res.copyBody(resp);
         }
         addAggregatedHeaders(asyncResp->res, resp, prefix);
     }
@@ -927,7 +927,7 @@
             if (asyncResp->res.resultInt() != 200)
             {
                 asyncResp->res.result(resp.result());
-                asyncResp->res.write(resp.body());
+                asyncResp->res.copyBody(resp);
             }
             return;
         }
@@ -938,8 +938,8 @@
         if (boost::iequals(contentType, "application/json") ||
             boost::iequals(contentType, "application/json; charset=utf-8"))
         {
-            nlohmann::json jsonVal = nlohmann::json::parse(resp.body(), nullptr,
-                                                           false);
+            nlohmann::json jsonVal = nlohmann::json::parse(*resp.body(),
+                                                           nullptr, false);
             if (jsonVal.is_discarded())
             {
                 BMCWEB_LOG_ERROR("Error parsing satellite response as JSON");
@@ -1061,7 +1061,7 @@
             if (asyncResp->res.resultInt() != 200)
             {
                 asyncResp->res.result(resp.result());
-                asyncResp->res.write(resp.body());
+                asyncResp->res.copyBody(resp);
             }
             return;
         }
@@ -1073,8 +1073,8 @@
             boost::iequals(contentType, "application/json; charset=utf-8"))
         {
             bool addedLinks = false;
-            nlohmann::json jsonVal = nlohmann::json::parse(resp.body(), nullptr,
-                                                           false);
+            nlohmann::json jsonVal = nlohmann::json::parse(*resp.body(),
+                                                           nullptr, false);
             if (jsonVal.is_discarded())
             {
                 BMCWEB_LOG_ERROR("Error parsing satellite response as JSON");
diff --git a/redfish-core/lib/log_services.hpp b/redfish-core/lib/log_services.hpp
index 116bc8a..54284cf 100644
--- a/redfish-core/lib/log_services.hpp
+++ b/redfish-core/lib/log_services.hpp
@@ -766,8 +766,9 @@
         return;
     }
 
-    asyncResp->res.body().resize(static_cast<size_t>(size), '\0');
-    rc = read(fd, asyncResp->res.body().data(), asyncResp->res.body().size());
+    std::string body;
+    body.resize(static_cast<size_t>(size), '\0');
+    rc = read(fd, body.data(), body.size());
     if ((rc == -1) || (rc != size))
     {
         BMCWEB_LOG_ERROR("Failed to read in file");
@@ -776,16 +777,17 @@
         return;
     }
     close(fd);
-
     if (downloadEntryType == "System")
     {
-        // We need to encode the data.  Doing it this way saves the other paths
-        // from having to make a copy into a temporary variable.
-        asyncResp->res.body() =
-            crow::utility::base64encode(asyncResp->res.body());
+        // Base64 encode response.
+        asyncResp->res.write(crow::utility::base64encode(body));
         asyncResp->res.addHeader(
             boost::beast::http::field::content_transfer_encoding, "Base64");
     }
+    else
+    {
+        asyncResp->res.write(std::move(body));
+    }
 
     asyncResp->res.addHeader(boost::beast::http::field::content_type,
                              "application/octet-stream");
@@ -3545,14 +3547,11 @@
                 return;
             }
 
-            if (!std::filesystem::exists(dbusFilepath))
+            if (!asyncResp->res.openFile(dbusFilepath))
             {
                 messages::resourceNotFound(asyncResp->res, "LogEntry", logID);
                 return;
             }
-            std::ifstream ifs(dbusFilepath, std::ios::in | std::ios::binary);
-            asyncResp->res.body() =
-                std::string(std::istreambuf_iterator<char>{ifs}, {});
 
             // Configure this to be a file download when accessed
             // from a browser
@@ -4314,7 +4313,7 @@
                                      "application/octet-stream");
             asyncResp->res.addHeader(
                 boost::beast::http::field::content_transfer_encoding, "Base64");
-            asyncResp->res.body() = crow::utility::base64encode(strData);
+            asyncResp->res.write(crow::utility::base64encode(strData));
         },
             "xyz.openbmc_project.State.Boot.PostCode0",
             "/xyz/openbmc_project/State/Boot/PostCode0",