OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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 "net/tools/quic/quic_in_memory_cache.h" | |
6 | |
7 #include "base/files/file_enumerator.h" | |
8 #include "base/files/file_util.h" | |
9 #include "base/stl_util.h" | |
10 #include "base/strings/string_number_conversions.h" | |
11 #include "net/tools/balsa/balsa_headers.h" | |
12 | |
13 using base::FilePath; | |
14 using base::StringPiece; | |
15 using std::string; | |
16 | |
17 // Specifies the directory used during QuicInMemoryCache | |
18 // construction to seed the cache. Cache directory can be | |
19 // generated using `wget -p --save-headers <url> | |
20 | |
21 namespace net { | |
22 namespace tools { | |
23 | |
24 std::string FLAGS_quic_in_memory_cache_dir = ""; | |
25 | |
26 namespace { | |
27 | |
28 // BalsaVisitor implementation (glue) which caches response bodies. | |
29 class CachingBalsaVisitor : public NoOpBalsaVisitor { | |
30 public: | |
31 CachingBalsaVisitor() : done_framing_(false) {} | |
32 void ProcessBodyData(const char* input, size_t size) override { | |
33 AppendToBody(input, size); | |
34 } | |
35 void MessageDone() override { done_framing_ = true; } | |
36 void HandleHeaderError(BalsaFrame* framer) override { UnhandledError(); } | |
37 void HandleHeaderWarning(BalsaFrame* framer) override { UnhandledError(); } | |
38 void HandleChunkingError(BalsaFrame* framer) override { UnhandledError(); } | |
39 void HandleBodyError(BalsaFrame* framer) override { UnhandledError(); } | |
40 void UnhandledError() { | |
41 LOG(DFATAL) << "Unhandled error framing HTTP."; | |
42 } | |
43 void AppendToBody(const char* input, size_t size) { | |
44 body_.append(input, size); | |
45 } | |
46 bool done_framing() const { return done_framing_; } | |
47 const string& body() const { return body_; } | |
48 | |
49 private: | |
50 bool done_framing_; | |
51 string body_; | |
52 }; | |
53 | |
54 } // namespace | |
55 | |
56 // static | |
57 QuicInMemoryCache* QuicInMemoryCache::GetInstance() { | |
58 return Singleton<QuicInMemoryCache>::get(); | |
59 } | |
60 | |
61 const QuicInMemoryCache::Response* QuicInMemoryCache::GetResponse( | |
62 const BalsaHeaders& request_headers) const { | |
63 ResponseMap::const_iterator it = responses_.find(GetKey(request_headers)); | |
64 if (it == responses_.end()) { | |
65 return nullptr; | |
66 } | |
67 return it->second; | |
68 } | |
69 | |
70 void QuicInMemoryCache::AddSimpleResponse(StringPiece method, | |
71 StringPiece path, | |
72 StringPiece version, | |
73 StringPiece response_code, | |
74 StringPiece response_detail, | |
75 StringPiece body) { | |
76 BalsaHeaders request_headers, response_headers; | |
77 request_headers.SetRequestFirstlineFromStringPieces(method, | |
78 path, | |
79 version); | |
80 response_headers.SetRequestFirstlineFromStringPieces(version, | |
81 response_code, | |
82 response_detail); | |
83 response_headers.AppendHeader("content-length", | |
84 base::IntToString(body.length())); | |
85 | |
86 AddResponse(request_headers, response_headers, body); | |
87 } | |
88 | |
89 void QuicInMemoryCache::AddResponse(const BalsaHeaders& request_headers, | |
90 const BalsaHeaders& response_headers, | |
91 StringPiece response_body) { | |
92 VLOG(1) << "Adding response for: " << GetKey(request_headers); | |
93 if (ContainsKey(responses_, GetKey(request_headers))) { | |
94 LOG(DFATAL) << "Response for given request already exists!"; | |
95 return; | |
96 } | |
97 Response* new_response = new Response(); | |
98 new_response->set_headers(response_headers); | |
99 new_response->set_body(response_body); | |
100 responses_[GetKey(request_headers)] = new_response; | |
101 } | |
102 | |
103 void QuicInMemoryCache::AddSpecialResponse(StringPiece method, | |
104 StringPiece path, | |
105 StringPiece version, | |
106 SpecialResponseType response_type) { | |
107 BalsaHeaders request_headers, response_headers; | |
108 request_headers.SetRequestFirstlineFromStringPieces(method, | |
109 path, | |
110 version); | |
111 AddResponse(request_headers, response_headers, ""); | |
112 responses_[GetKey(request_headers)]->response_type_ = response_type; | |
113 } | |
114 | |
115 QuicInMemoryCache::QuicInMemoryCache() { | |
116 Initialize(); | |
117 } | |
118 | |
119 void QuicInMemoryCache::ResetForTests() { | |
120 STLDeleteValues(&responses_); | |
121 Initialize(); | |
122 } | |
123 | |
124 void QuicInMemoryCache::Initialize() { | |
125 // If there's no defined cache dir, we have no initialization to do. | |
126 if (FLAGS_quic_in_memory_cache_dir.empty()) { | |
127 VLOG(1) << "No cache directory found. Skipping initialization."; | |
128 return; | |
129 } | |
130 VLOG(1) << "Attempting to initialize QuicInMemoryCache from directory: " | |
131 << FLAGS_quic_in_memory_cache_dir; | |
132 | |
133 FilePath directory(FLAGS_quic_in_memory_cache_dir); | |
134 base::FileEnumerator file_list(directory, | |
135 true, | |
136 base::FileEnumerator::FILES); | |
137 | |
138 FilePath file = file_list.Next(); | |
139 while (!file.empty()) { | |
140 // Need to skip files in .svn directories | |
141 if (file.value().find("/.svn/") != std::string::npos) { | |
142 file = file_list.Next(); | |
143 continue; | |
144 } | |
145 | |
146 BalsaHeaders request_headers, response_headers; | |
147 | |
148 string file_contents; | |
149 base::ReadFileToString(file, &file_contents); | |
150 | |
151 // Frame HTTP. | |
152 CachingBalsaVisitor caching_visitor; | |
153 BalsaFrame framer; | |
154 framer.set_balsa_headers(&response_headers); | |
155 framer.set_balsa_visitor(&caching_visitor); | |
156 size_t processed = 0; | |
157 while (processed < file_contents.length() && | |
158 !caching_visitor.done_framing()) { | |
159 processed += framer.ProcessInput(file_contents.c_str() + processed, | |
160 file_contents.length() - processed); | |
161 } | |
162 | |
163 if (!caching_visitor.done_framing()) { | |
164 LOG(DFATAL) << "Did not frame entire message from file: " << file.value() | |
165 << " (" << processed << " of " << file_contents.length() | |
166 << " bytes)."; | |
167 } | |
168 if (processed < file_contents.length()) { | |
169 // Didn't frame whole file. Assume remainder is body. | |
170 // This sometimes happens as a result of incompatibilities between | |
171 // BalsaFramer and wget's serialization of HTTP sans content-length. | |
172 caching_visitor.AppendToBody(file_contents.c_str() + processed, | |
173 file_contents.length() - processed); | |
174 processed += file_contents.length(); | |
175 } | |
176 | |
177 StringPiece base = file.value(); | |
178 if (response_headers.HasHeader("X-Original-Url")) { | |
179 base = response_headers.GetHeader("X-Original-Url"); | |
180 response_headers.RemoveAllOfHeader("X-Original-Url"); | |
181 // Remove the protocol so that the string is of the form host + path, | |
182 // which is parsed properly below. | |
183 if (StringPieceUtils::StartsWithIgnoreCase(base, "https://")) { | |
184 base.remove_prefix(8); | |
185 } else if (StringPieceUtils::StartsWithIgnoreCase(base, "http://")) { | |
186 base.remove_prefix(7); | |
187 } | |
188 } | |
189 int path_start = base.find_first_of('/'); | |
190 DCHECK_LT(0, path_start); | |
191 StringPiece host(base.substr(0, path_start)); | |
192 StringPiece path(base.substr(path_start)); | |
193 if (path[path.length() - 1] == ',') { | |
194 path.remove_suffix(1); | |
195 } | |
196 // Set up request headers. Assume method is GET and protocol is HTTP/1.1. | |
197 request_headers.SetRequestFirstlineFromStringPieces("GET", | |
198 path, | |
199 "HTTP/1.1"); | |
200 request_headers.ReplaceOrAppendHeader("host", host); | |
201 | |
202 VLOG(1) << "Inserting 'http://" << GetKey(request_headers) | |
203 << "' into QuicInMemoryCache."; | |
204 | |
205 AddResponse(request_headers, response_headers, caching_visitor.body()); | |
206 | |
207 file = file_list.Next(); | |
208 } | |
209 } | |
210 | |
211 QuicInMemoryCache::~QuicInMemoryCache() { | |
212 STLDeleteValues(&responses_); | |
213 } | |
214 | |
215 string QuicInMemoryCache::GetKey(const BalsaHeaders& request_headers) const { | |
216 StringPiece uri = request_headers.request_uri(); | |
217 if (uri.size() == 0) { | |
218 return ""; | |
219 } | |
220 StringPiece host; | |
221 if (uri[0] == '/') { | |
222 host = request_headers.GetHeader("host"); | |
223 } else if (StringPieceUtils::StartsWithIgnoreCase(uri, "https://")) { | |
224 uri.remove_prefix(8); | |
225 } else if (StringPieceUtils::StartsWithIgnoreCase(uri, "http://")) { | |
226 uri.remove_prefix(7); | |
227 } | |
228 return host.as_string() + uri.as_string(); | |
229 } | |
230 | |
231 } // namespace tools | |
232 } // namespace net | |
OLD | NEW |