xref: /aosp_15_r20/external/federated-compute/fcp/client/http/in_memory_request_response.h (revision 14675a029014e728ec732f129a32e299b2da0601)
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