1 /* 2 * Copyright 2021 Google LLC 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 #ifndef FCP_CLIENT_HTTP_IN_MEMORY_REQUEST_RESPONSE_H_ 17 #define FCP_CLIENT_HTTP_IN_MEMORY_REQUEST_RESPONSE_H_ 18 19 #include <cstdint> 20 #include <memory> 21 #include <optional> 22 #include <string> 23 #include <utility> 24 #include <vector> 25 26 #include "absl/base/thread_annotations.h" 27 #include "absl/status/status.h" 28 #include "absl/status/statusor.h" 29 #include "absl/strings/cord.h" 30 #include "absl/strings/string_view.h" 31 #include "absl/synchronization/mutex.h" 32 #include "absl/time/time.h" 33 #include "fcp/client/cache/resource_cache.h" 34 #include "fcp/client/http/http_client.h" 35 #include "fcp/client/interruptible_runner.h" 36 37 namespace fcp { 38 namespace client { 39 namespace http { 40 41 // Simple `HttpRequest` implementation with an in-memory request body. 42 class InMemoryHttpRequest : public HttpRequest { 43 public: 44 // Factory method for creating an instance. 45 // 46 // Callers are recommended to `std::move` the `body` parameter (or to rely on 47 // copy elision), to avoid unnecessary copies of the data. 48 // 49 // Note that a "Content-Length" header will be constructed automatically, and 50 // must not be provided by the caller. 51 // 52 // If "use_compression" is true, the body will be compressed with 53 // gzip. A "Content-Encoding" header will be added, and the "Content-Length" 54 // header will be the compressed length. 55 // 56 // Returns an INVALID_ARGUMENT error if: 57 // - the URI is a non-HTTPS URI, 58 // - the request has a body but the request method doesn't allow it, 59 // - the headers contain a "Content-Length" header. 60 static absl::StatusOr<std::unique_ptr<HttpRequest>> Create( 61 absl::string_view uri, Method method, HeaderList extra_headers, 62 std::string body, bool use_compression); 63 uri()64 absl::string_view uri() const override { return uri_; }; method()65 Method method() const override { return method_; }; extra_headers()66 const HeaderList& extra_headers() const override { return headers_; } HasBody()67 bool HasBody() const override { return !body_.empty(); }; 68 69 absl::StatusOr<int64_t> ReadBody(char* buffer, int64_t requested) override; 70 71 private: InMemoryHttpRequest(absl::string_view uri,Method method,HeaderList extra_headers,std::string body)72 InMemoryHttpRequest(absl::string_view uri, Method method, 73 HeaderList extra_headers, std::string body) 74 : uri_(uri), 75 method_(method), 76 body_(std::move(body)), 77 headers_(std::move(extra_headers)) {} 78 79 const std::string uri_; 80 const Method method_; 81 const std::string body_; 82 const HeaderList headers_; 83 int64_t cursor_ ABSL_GUARDED_BY(mutex_) = 0; 84 mutable absl::Mutex mutex_; 85 }; 86 87 // Simple container class for holding an HTTP response code, headers, and 88 // in-memory request body, as well as metadata for the client-side cache. 89 struct InMemoryHttpResponse { 90 int code; 91 // This is empty if no "Content-Encoding" header was present in the response 92 // headers. 93 std::string content_encoding; 94 // This is empty if no "Content-Type" header was present in the response 95 // headers. 96 std::string content_type; 97 absl::Cord body; 98 }; 99 100 // Simple `HttpRequestCallback` implementation that stores the response and its 101 // body in an `InMemoryHttpResponse` object for later consumption. 102 class InMemoryHttpRequestCallback : public HttpRequestCallback { 103 public: 104 InMemoryHttpRequestCallback() = default; 105 106 absl::Status OnResponseStarted(const HttpRequest& request, 107 const HttpResponse& response) override; 108 void OnResponseError(const HttpRequest& request, 109 const absl::Status& error) override; 110 absl::Status OnResponseBody(const HttpRequest& request, 111 const HttpResponse& response, 112 absl::string_view data) override; 113 void OnResponseBodyError(const HttpRequest& request, 114 const HttpResponse& response, 115 const absl::Status& error) override; 116 void OnResponseCompleted(const HttpRequest& request, 117 const HttpResponse& response) override; 118 absl::StatusOr<InMemoryHttpResponse> Response() const; 119 120 private: 121 absl::Status status_ ABSL_GUARDED_BY(mutex_) = 122 absl::UnavailableError("No response received"); 123 std::optional<int> response_code_ ABSL_GUARDED_BY(mutex_); 124 std::string content_encoding_ ABSL_GUARDED_BY(mutex_); 125 std::string content_type_ ABSL_GUARDED_BY(mutex_); 126 std::optional<int64_t> expected_content_length_ ABSL_GUARDED_BY(mutex_); 127 absl::Cord response_buffer_ ABSL_GUARDED_BY(mutex_); 128 mutable absl::Mutex mutex_; 129 std::string client_cache_id_; 130 }; 131 132 // Utility for performing a single HTTP request and returning the results (incl. 133 // the response body) via an in-memory object, in an interruptible way. 134 // 135 // If `bytes_received_acc` and `bytes_sent_acc` are non-null then those 136 // accumulators will also be incremented by the amount of data that was 137 // received/sent by the request. 138 // 139 // Returns an error if the request failed. 140 absl::StatusOr<InMemoryHttpResponse> PerformRequestInMemory( 141 HttpClient& http_client, InterruptibleRunner& interruptible_runner, 142 std::unique_ptr<http::HttpRequest> request, int64_t* bytes_received_acc, 143 int64_t* bytes_sent_acc); 144 145 // Utility for performing multiple HTTP requests and returning the results 146 // (incl. the response body) in memory. 147 // 148 // Returns an error if issuing the joint `PerformRequests` call failed. 149 // Otherwise it returns a vector containing the result of each request 150 // (in the same order the requests were provided in). 151 absl::StatusOr<std::vector<absl::StatusOr<InMemoryHttpResponse>>> 152 PerformMultipleRequestsInMemory( 153 HttpClient& http_client, InterruptibleRunner& interruptible_runner, 154 std::vector<std::unique_ptr<http::HttpRequest>> requests, 155 int64_t* bytes_received_acc, int64_t* bytes_sent_acc); 156 157 // Simple class representing a resource for which data is already available 158 // in-memory (`inline_data`) or for which data needs to be fetched by an HTTP 159 // GET request (via `uri`). Only one field can ever be set to a non-empty value, 160 // or both fields may be empty (indicating a zero-length resource for which 161 // nothing has to be fetched). 162 class UriOrInlineData { 163 public: 164 struct InlineData { 165 enum class CompressionFormat { 166 kUncompressed, 167 kGzip, 168 }; 169 170 absl::Cord data; 171 CompressionFormat compression_format = CompressionFormat::kUncompressed; 172 }; 173 174 struct Uri { 175 std::string uri; 176 std::string client_cache_id; 177 absl::Duration max_age; 178 }; 179 180 // Creates an instance representing a URI from which data has to be fetched. 181 // If the resource represented by the uri should be cached, both 182 // `client_cache_id` and `max_age` must be set, otherwise they may be 183 // empty/zero. CreateUri(std::string uri,std::string client_cache_id,absl::Duration max_age)184 static UriOrInlineData CreateUri(std::string uri, std::string client_cache_id, 185 absl::Duration max_age) { 186 return UriOrInlineData({.uri = std::move(uri), 187 .client_cache_id = std::move(client_cache_id), 188 .max_age = max_age}, 189 {}); 190 } 191 // Creates an instance representing a resource's already-available (or empty) 192 // data. CreateInlineData(absl::Cord inline_data,InlineData::CompressionFormat compression_format)193 static UriOrInlineData CreateInlineData( 194 absl::Cord inline_data, 195 InlineData::CompressionFormat compression_format) { 196 return UriOrInlineData({}, {std::move(inline_data), compression_format}); 197 } 198 uri()199 const Uri& uri() const { return uri_; } inline_data()200 const InlineData& inline_data() const { return inline_data_; } 201 202 private: UriOrInlineData(Uri uri,InlineData inline_data)203 UriOrInlineData(Uri uri, InlineData inline_data) 204 : uri_(std::move(uri)), inline_data_(std::move(inline_data)) {} 205 206 const Uri uri_; 207 const InlineData inline_data_; 208 }; 209 210 // Utility for (potentially) fetching multiple resources at once, each of which 211 // either needs to be fetched from a URI using a HTTP GET request, or for which 212 // its data is already available, and returning the eventual results (incl. the 213 // response body) via in-memory objects, in an interruptible way. If resources 214 // do need to be fetched, then a single `HttpClient::PerformRequests` call will 215 // be made for all resources at once. 216 // 217 // This makes it a convenient way for callers to gather the data for a related 218 // set of resources (some of which might already have their data available) in 219 // one go, and to then access the data for the first resource at index 0, the 220 // second resource at index 1, etc., transparently handling the various 221 // permutations that are possible (e.g. resource A having data inline but 222 // B having to be fetched, or both being inlined, or ...) via a unified access 223 // pattern and error handling mechanism. 224 // 225 // If `bytes_received_acc` and `bytes_sent_acc` are non-null then those 226 // accumulators will also be incremented by the aggregate amount of data that 227 // was received/sent by the HTTP requests that were issued. 228 // 229 // Returns an error if issuing the joint `HttpClient::PerformRequests` call 230 // failed. Otherwise it returns a vector containing the result for each 231 // resource (in the same order the resources were provided in). 232 absl::StatusOr<std::vector<absl::StatusOr<InMemoryHttpResponse>>> 233 FetchResourcesInMemory(HttpClient& http_client, 234 InterruptibleRunner& interruptible_runner, 235 const std::vector<UriOrInlineData>& resources, 236 int64_t* bytes_received_acc, int64_t* bytes_sent_acc, 237 cache::ResourceCache* resource_cache); 238 239 // Used by the class and in tests only. 240 namespace internal { 241 absl::StatusOr<std::string> CompressWithGzip( 242 const std::string& uncompressed_data); 243 absl::StatusOr<absl::Cord> UncompressWithGzip( 244 const std::string& compressed_data); 245 } // namespace internal 246 247 }; // namespace http 248 }; // namespace client 249 }; // namespace fcp 250 251 #endif // FCP_CLIENT_HTTP_IN_MEMORY_REQUEST_RESPONSE_H_ 252