Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(209)

Side by Side Diff: services/url_response_disk_cache/url_response_disk_cache_impl.cc

Issue 1088533003: Adding URLResponse Disk Cache to mojo. (Closed) Base URL: https://github.com/domokit/mojo.git@master
Patch Set: Follow review Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "services/url_response_disk_cache/url_response_disk_cache_impl.h"
6
7 #include "base/bind.h"
8 #include "base/files/file_util.h"
9 #include "base/files/important_file_writer.h"
10 #include "base/location.h"
11 #include "base/logging.h"
12 #include "base/strings/string_util.h"
13 #include "base/strings/stringprintf.h"
14 #include "mojo/common/data_pipe_utils.h"
15 #include "mojo/public/cpp/application/application_connection.h"
16 #include "mojo/public/cpp/bindings/lib/fixed_buffer.h"
17 #include "services/url_response_disk_cache/url_response_disk_cache_entry.mojom.h "
18 #include "third_party/zlib/google/zip_reader.h"
19 #include "url/gurl.h"
20
21 namespace mojo {
22 namespace url_response_disk_cache {
23
24 namespace {
25
26 const char kEtagHeader[] = "etag";
27
28 template <typename T>
29 void Serialize(T input, std::string* output) {
30 typedef typename mojo::internal::WrapperTraits<T>::DataType DataType;
31 size_t size = GetSerializedSize_(input);
32 mojo::internal::FixedBuffer buf(size);
33 DataType data_type;
34 Serialize_(input.Pass(), &buf, &data_type);
35 std::vector<Handle> handles;
36 data_type->EncodePointersAndHandles(&handles);
37 void* serialized_data = buf.Leak();
38 *output = std::string(static_cast<char*>(serialized_data), size);
39 free(serialized_data);
40 }
41
42 template <typename T>
43 void Deserialize(std::string input, T* output) {
44 typedef typename mojo::internal::WrapperTraits<T>::DataType DataType;
45 DataType data_type = reinterpret_cast<DataType>(&input[0]);
46 std::vector<Handle> handles;
47 data_type->DecodePointersAndHandles(&handles);
48 Deserialize_(data_type, output);
49 }
50
51 Array<uint8_t> PathToArray(const base::FilePath& path) {
52 if (path.empty())
53 return Array<uint8_t>();
54 const std::string& string = path.value();
55 Array<uint8_t> result(string.size());
56 memcpy(&result.front(), string.data(), string.size());
57 return result.Pass();
58 }
59
60 // Encode a string in ascii. This uses # as an escape character. It also escapes
61 // ':' because it is an usual path separator.
62 std::string EncodeString(const std::string& string) {
63 std::string result = "";
64 for (size_t i = 0; i < string.size(); ++i) {
65 unsigned char c = string[i];
66 if (c >= 32 && c < 128 && c != '#' && c != ':') {
67 result += c;
68 } else {
69 result += base::StringPrintf("#%02x", c);
70 }
71 }
72 return result;
73 }
74
75 // This service use a directory under HOME to store all of its data,
76 base::FilePath GetBaseDirectory() {
77 return base::FilePath(getenv("HOME")).Append(".mojo_url_response_disk_cache");
78 }
79
80 // Returns the directory used store cached data for the given |url|, under
81 // |base_directory|.
82 base::FilePath GetDirName(base::FilePath base_directory,
83 const std::string& url) {
84 // TODO(qsr): If the speed of directory traversal is problematic, this might
85 // need to change to use less directories.
86 return base_directory.Append(EncodeString(url));
87 }
88
89 // Returns the directory that the consumer can use to cache its own data.
90 base::FilePath GetConsumerCacheDirectory(const base::FilePath& main_cache) {
91 return main_cache.Append("consumer_cache");
92 }
93
94 // Returns the path of the sentinel used to keep track of a zipped response has
95 // already been extracted.
96 base::FilePath GetExtractedSentinel(const base::FilePath& main_cache) {
97 return main_cache.Append("extracted_sentinel");
98 }
99
100 // Runs the given callback. If |success| is false, call back with an error.
101 // Otherwise, store |entry| in |entry_path|, then call back with the given
102 // paths.
103 void RunCallbackWithSuccess(
104 const URLResponseDiskCacheImpl::FilePathPairCallback& callback,
105 const base::FilePath& content_path,
106 const base::FilePath& cache_dir,
107 const base::FilePath& entry_path,
108 CacheEntryPtr entry,
109 bool success) {
110 if (!success) {
111 callback.Run(base::FilePath(), base::FilePath());
112 return;
113 }
114 std::string serialized_entry;
115 Serialize(entry.Pass(), &serialized_entry);
116 // We can ignore write error, as it will just force to clear the cache on the
117 // next request.
118 base::ImportantFileWriter::WriteFileAtomically(entry_path, serialized_entry);
119 callback.Run(content_path, cache_dir);
120 }
121
122 // Run the given mojo callback with the given paths.
123 void RunMojoCallback(
124 const Callback<void(Array<uint8_t>, Array<uint8_t>)>& callback,
125 const base::FilePath& path1,
126 const base::FilePath& path2) {
127 callback.Run(PathToArray(path1), PathToArray(path2));
128 }
129
130 // Returns the list of values for the given |header_name| in the given list of
131 // headers.
132 std::vector<std::string> GetHeaderValues(const std::string& header_name,
133 const Array<String>& headers) {
134 std::vector<std::string> result;
135 for (std::string header : headers.storage()) {
136 if (StartsWithASCII(header, header_name, false)) {
137 auto begin = header.begin();
138 auto end = header.end();
139 begin += header_name.size();
140 // Extract the content of the header by finding the remaining string after
141 // the ':' and stripping all spaces.
142 while (begin < end && *begin != ':')
143 begin++;
144 if (begin < end) {
145 begin++;
146 while (begin < end && *begin == ' ')
147 begin++;
148 while (end > begin && *(end - 1) == ' ')
149 end--;
150 if (begin < end) {
151 result.push_back(std::string(begin, end));
152 }
153 }
154 }
155 }
156 return result;
157 }
158
159 // Returns whether the given directory |dir| constains a valid entry file for
160 // the given |response|. If this is the case and |output| is not |nullptr|, then
161 // the deserialized entry is returned in |*output|.
162 bool IsCacheEntryValid(const base::FilePath& dir,
163 URLResponse* response,
164 CacheEntryPtr* output) {
165 // Find the entry file, and deserialize it.
166 base::FilePath entry_path = dir.Append("entry");
167 if (!base::PathExists(entry_path))
168 return false;
169 std::string serialized_entry;
170 if (!ReadFileToString(entry_path, &serialized_entry))
171 return false;
172 CacheEntryPtr entry;
173 Deserialize(serialized_entry, &entry);
174
175 // If |entry| or |response| has not headers, it is not possible to check if
176 // the entry is valid, so returns |false|.
177 if (entry->headers.is_null() || response->headers.is_null())
178 return false;
179
180 // Only handle etag for the moment.
181 std::string etag_header_name = kEtagHeader;
182 std::vector<std::string> entry_etags =
183 GetHeaderValues(etag_header_name, entry->headers);
184 if (entry_etags.size() == 0)
185 return false;
186 std::vector<std::string> response_etags =
187 GetHeaderValues(etag_header_name, response->headers);
188 if (response_etags.size() == 0)
189 return false;
190
191 // Looking for the first etag header.
192 bool result = entry_etags[0] == response_etags[0];
193
194 // Returns |entry| is requested.
blundell 2015/05/07 13:57:59 nit: s/is/if
qsr 2015/05/07 14:18:07 Done.
195 if (output)
196 *output = entry.Pass();
197
198 return result;
199 }
200
201 } // namespace
202
203 URLResponseDiskCacheImpl::URLResponseDiskCacheImpl(
204 scoped_refptr<base::SequencedWorkerPool> worker_pool,
205 const std::string& remote_application_url,
206 InterfaceRequest<URLResponseDiskCache> request)
207 : worker_pool_(worker_pool), binding_(this, request.Pass()) {
208 base_directory_ = GetBaseDirectory();
209 // The cached files are shared only for application of the same origin.
210 if (remote_application_url != "") {
211 base_directory_ = base_directory_.Append(
212 EncodeString(GURL(remote_application_url).GetOrigin().spec()));
213 }
214 }
215
216 URLResponseDiskCacheImpl::~URLResponseDiskCacheImpl() {
217 }
218
219 void URLResponseDiskCacheImpl::GetFile(URLResponsePtr response,
220 const GetFileCallback& callback) {
221 return GetFileInternal(response.Pass(),
222 base::Bind(&RunMojoCallback, callback));
223 }
224
225 void URLResponseDiskCacheImpl::GetExtractedContent(
226 URLResponsePtr response,
227 const GetExtractedContentCallback& callback) {
228 base::FilePath dir = GetDirName(base_directory_, response->url);
229 base::FilePath extracted_dir = dir.Append("extracted");
230 if (IsCacheEntryValid(dir, response.get(), nullptr) &&
231 PathExists(GetExtractedSentinel(dir))) {
232 callback.Run(PathToArray(extracted_dir), PathToArray(dir));
233 return;
234 }
235
236 GetFileInternal(
237 response.Pass(),
238 base::Bind(&URLResponseDiskCacheImpl::GetExtractedContentInternal,
239 base::Unretained(this), base::Bind(&RunMojoCallback, callback),
240 extracted_dir));
241 }
242
243 void URLResponseDiskCacheImpl::GetFileInternal(
244 URLResponsePtr response,
245 const FilePathPairCallback& callback) {
246 base::FilePath dir = GetDirName(base_directory_, response->url);
247
248 // Check if the response is cached and valid. If that's the case, returns the
249 // cached value.
250 CacheEntryPtr entry;
251 if (IsCacheEntryValid(dir, response.get(), &entry)) {
252 callback.Run(base::FilePath(entry->content_path),
253 GetConsumerCacheDirectory(dir));
254 return;
255 }
256
257 // As the response was either not cached or the cached value is not valid, if
258 // the cache directory for the response exists, it needs to be cleaned.
259 if (base::PathExists(dir)) {
260 base::FilePath to_delete;
261 CHECK(CreateTemporaryDirInDir(base_directory_, "to_delete", &to_delete));
262 CHECK(Move(dir, to_delete));
263 worker_pool_->PostTask(
264 FROM_HERE,
265 base::Bind(base::IgnoreResult(&base::DeleteFile), to_delete, true));
266 }
267
268 // If the response has not a valid body, and it is not possible to create
269 // either the cache directory or the consumer cache directory, returns an
270 // error.
271 if (!response->body.is_valid() ||
272 !base::CreateDirectoryAndGetError(dir, nullptr) ||
273 !base::CreateDirectoryAndGetError(GetConsumerCacheDirectory(dir),
274 nullptr)) {
275 callback.Run(base::FilePath(), base::FilePath());
276 return;
277 }
278
279 // Fill the entry values for the request.
280 base::FilePath entry_path = dir.Append("entry");
281 base::FilePath content;
282 CHECK(CreateTemporaryFileInDir(dir, &content));
283 entry = CacheEntry::New();
284 entry->url = response->url;
285 entry->content_path = content.value();
286 entry->headers = response->headers.Pass();
287 // Asynchronously copy the response body to the cached file. The entry is send
288 // to the callback so that it is saved on disk only if the copy of the body
289 // succeded.
290 common::CopyToFile(response->body.Pass(), content, worker_pool_.get(),
291 base::Bind(&RunCallbackWithSuccess, callback, content,
292 GetConsumerCacheDirectory(dir), entry_path,
293 base::Passed(entry.Pass())));
294 }
295
296 void URLResponseDiskCacheImpl::GetExtractedContentInternal(
297 const FilePathPairCallback& callback,
298 const base::FilePath& extracted_dir,
299 const base::FilePath& content,
300 const base::FilePath& dir) {
301 // If it is not possible to get the cached file, returns an error.
302 if (content.empty()) {
303 callback.Run(base::FilePath(), base::FilePath());
304 return;
305 }
306
307 // Unzip the content to the extracted directory. In case of any error, returns
308 // an error.
309 zip::ZipReader reader;
310 if (!reader.Open(content)) {
311 callback.Run(base::FilePath(), base::FilePath());
312 return;
313 }
314 while (reader.HasMore()) {
315 bool success = reader.OpenCurrentEntryInZip();
316 success = success && reader.ExtractCurrentEntryIntoDirectory(extracted_dir);
317 success = success && reader.AdvanceToNextEntry();
318 if (!success) {
319 callback.Run(base::FilePath(), base::FilePath());
320 return;
321 }
322 }
323 // We can ignore write error, as it will just force to clear the cache on the
324 // next request.
325 WriteFile(GetExtractedSentinel(dir), nullptr, 0);
326 callback.Run(extracted_dir, GetConsumerCacheDirectory(dir));
327 }
328
329 } // namespace url_response_disk_cache
330 } // namespace mojo
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698