"Fossies" - the Fresh Open Source Software Archive 
As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style:
standard) with prefixed line numbers and
code folding option.
Alternatively you can here
view or
download the uninterpreted source code file.
For more information about "admin_site.cc" see the
Fossies "Dox" file reference documentation and the latest
Fossies "Diffs" side-by-side code changes report:
1.13.35.2_vs_1.14.36.1.
1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20
21 #include "pagespeed/system/admin_site.h"
22
23 #include <cstddef>
24 #include <memory>
25 #include <set>
26 #include <vector>
27
28 #include "net/instaweb/http/public/async_fetch.h"
29 #include "net/instaweb/http/public/http_cache.h"
30 #include "net/instaweb/rewriter/public/rewrite_options.h"
31 #include "net/instaweb/rewriter/public/rewrite_query.h"
32 #include "net/instaweb/rewriter/public/server_context.h"
33 #include "net/instaweb/util/public/property_cache.h"
34 #include "net/instaweb/util/public/property_store.h"
35 #include "strings/stringpiece_utils.h"
36 #include "pagespeed/kernel/base/cache_interface.h"
37 #include "pagespeed/kernel/base/callback.h"
38 #include "pagespeed/kernel/base/message_handler.h"
39 #include "pagespeed/kernel/base/statistics.h"
40 #include "pagespeed/kernel/base/string.h"
41 #include "pagespeed/kernel/base/string_util.h"
42 #include "pagespeed/kernel/base/string_writer.h"
43 #include "pagespeed/kernel/base/timer.h"
44 #include "pagespeed/kernel/cache/purge_context.h"
45 #include "pagespeed/kernel/html/html_keywords.h"
46 #include "pagespeed/kernel/http/content_type.h"
47 #include "pagespeed/kernel/http/google_url.h"
48 #include "pagespeed/kernel/http/http_names.h"
49 #include "pagespeed/kernel/http/query_params.h"
50 #include "pagespeed/kernel/http/request_headers.h"
51 #include "pagespeed/kernel/http/response_headers.h"
52 #include "pagespeed/kernel/util/statistics_logger.h"
53 #include "pagespeed/system/system_cache_path.h"
54 #include "pagespeed/system/system_caches.h"
55 #include "pagespeed/system/system_rewrite_options.h"
56
57 namespace net_instaweb {
58
59 // Generated from JS, CSS, and HTML source via net/instaweb/js/data_to_c.cc.
60 extern const char* CSS_admin_site_css;
61 extern const char* CSS_caches_css;
62 extern const char* CSS_console_css;
63 extern const char* CSS_graphs_css;
64 extern const char* CSS_statistics_css;
65 extern const char* JS_caches_js;
66 extern const char* JS_caches_js_opt;
67 extern const char* JS_console_js;
68 extern const char* JS_console_js_opt;
69 extern const char* JS_graphs_js;
70 extern const char* JS_graphs_js_opt;
71 extern const char* JS_messages_js;
72 extern const char* JS_messages_js_opt;
73 extern const char* JS_statistics_js;
74 extern const char* JS_statistics_js_opt;
75
76 namespace {
77
78 struct Tab {
79 const char* label;
80 const char* title;
81 const char* admin_link; // relative from /pagespeed_admin/
82 const char* statistics_link; // relative from /mod_pagespeed_statistics
83 const char* space; // html for inter-link spacing.
84 };
85
86 const char kShortBreak[] = " ";
87 const char kLongBreak[] = " ";
88
89 // TODO(jmarantz): disable or recolor links to pages that are not available
90 // based on the current config.
91 const Tab kTabs[] = {
92 {"Statistics", "Statistics", "statistics", "?", kLongBreak},
93 {"Configuration", "Configuration", "config", "?config", kShortBreak},
94 {"Histograms", "Histograms", "histograms", "?histograms", kLongBreak},
95 {"Caches", "Caches", "cache", "?cache", kLongBreak},
96 {"Console", "Console", "console", NULL, kLongBreak},
97 {"Message History", "Message History", "message_history", NULL, kLongBreak},
98 {"Graphs", "Graphs", "graphs", NULL, kLongBreak},
99 };
100
101 // Controls the generation of an HTML Admin page. Constructing it
102 // establishes the content-type as HTML and response code 200, and
103 // puts in a banner with links to all the admin pages, ready for
104 // appending more <body> elements. Destructing AdminHtml closes the
105 // body and completes the fetch.
106 class AdminHtml {
107 public:
108 AdminHtml(StringPiece current_link, StringPiece head_extra,
109 AdminSite::AdminSource source, Timer* timer,
110 AsyncFetch* fetch, MessageHandler* handler)
111 : fetch_(fetch),
112 handler_(handler) {
113 fetch->response_headers()->SetStatusAndReason(HttpStatus::kOK);
114 fetch->response_headers()->Add(HttpAttributes::kContentType, "text/html");
115
116 int64 now_ms = timer->NowMs();
117 fetch->response_headers()->SetLastModified(now_ms);
118
119 // Let PageSpeed dynamically minify the html, css, and javasript
120 // generated by the admin page, to the extent it's not done
121 // already by the tools. Note, this does mean that viewing the
122 // statistics and histograms pages will affect the statistics and
123 // histograms. If we decide this is too annoying, then we can
124 // instead procedurally minify the css/js and leave the html
125 // alone.
126 //
127 // Note that we at least turn off add_instrumenation here by explicitly
128 // giving a filter list without "+" or "-".
129 fetch->response_headers()->Add(
130 RewriteQuery::kPageSpeedFilters,
131 "rewrite_css,rewrite_javascript,collapse_whitespace");
132
133 // Generate some navigational links to help our users get to other
134 // admin pages.
135 fetch->Write("<!DOCTYPE html>\n<html><head>", handler_);
136 fetch->Write(StrCat("<style>", CSS_admin_site_css, "</style>"), handler_);
137
138 GoogleString buf;
139 for (int i = 0, n = arraysize(kTabs); i < n; ++i) {
140 const Tab& tab = kTabs[i];
141 const char* link = NULL;
142 switch (source) {
143 case AdminSite::kPageSpeedAdmin:
144 link = tab.admin_link;
145 break;
146 case AdminSite::kStatistics:
147 link = tab.statistics_link;
148 break;
149 case AdminSite::kOther:
150 link = NULL;
151 break;
152 }
153 if (link != NULL) {
154 StringPiece style;
155 if (tab.admin_link == current_link) {
156 style = " style='color:darkblue;text-decoration:underline;'";
157 fetch->Write(StrCat("<title>PageSpeed ", tab.title, "</title>"),
158 handler_);
159 }
160 StrAppend(&buf,
161 "<a href='", link, "'", style, ">", tab.label, "</a>",
162 tab.space);
163 }
164 }
165
166 fetch->Write(StrCat(head_extra, "</head>"), handler_);
167 fetch->Write(
168 StrCat("<body class='pagespeed-admin-body'>"
169 "<div class='pagespeed-admin-tabs'>\n"
170 "<b>Pagespeed Admin</b>", kLongBreak, "\n"),
171 handler_);
172 fetch->Write(buf, handler_);
173 fetch->Write("</div><hr/>\n", handler_);
174 fetch->Flush(handler_);
175 }
176
177 ~AdminHtml() {
178 fetch_->Write("</body></html>", handler_);
179 fetch_->Done(true);
180 }
181
182 private:
183 AsyncFetch* fetch_;
184 MessageHandler* handler_;
185 };
186
187 } // namespace
188
189 AdminSite::AdminSite(StaticAssetManager* static_asset_manager, Timer* timer,
190 MessageHandler* message_handler)
191 : message_handler_(message_handler),
192 static_asset_manager_(static_asset_manager),
193 timer_(timer) {
194 }
195
196 // Handler which serves PSOL console.
197 void AdminSite::ConsoleHandler(const SystemRewriteOptions& global_options,
198 const RewriteOptions& options,
199 AdminSource source,
200 const QueryParams& query_params,
201 AsyncFetch* fetch, Statistics* statistics) {
202 if (query_params.Has("json")) {
203 ConsoleJsonHandler(query_params, fetch, statistics);
204 return;
205 }
206
207 MessageHandler* handler = message_handler_;
208 bool statistics_enabled = global_options.statistics_enabled();
209 bool logging_enabled = global_options.statistics_logging_enabled();
210 bool log_dir_set = !global_options.log_dir().empty();
211
212 // TODO(jmarantz): change StaticAssetManager to take options by const ref.
213 // TODO(sligocki): Move static content to a data2cc library.
214 StringPiece console_js = options.Enabled(RewriteOptions::kDebug) ?
215 JS_console_js :
216 JS_console_js_opt;
217 // TODO(sligocki): Do we want to have a minified version of console CSS?
218 GoogleString head_markup = StrCat(
219 "<style>", CSS_console_css, "</style>\n");
220 AdminHtml admin_html("console", head_markup, source, timer_, fetch,
221 message_handler_);
222 if (statistics_enabled && logging_enabled && log_dir_set) {
223 fetch->Write("<div class='console_div' id='suggestions'>\n"
224 " <div class='console_div' id='pagespeed-graphs-container'>"
225 "</div>\n</div>\n"
226 "<script src='https://www.google.com/jsapi'></script>\n"
227 "<script>var pagespeedStatisticsUrl = '';</script>\n"
228 "<script>", handler);
229 // From the admin page, the console JSON is relative, so it can
230 // be set to ''. Formerly it was set to options.statistics_handler_path(),
231 // but there does not appear to be a disadvantage to always handling it
232 // from whatever URL served this console HTML.
233 //
234 // TODO(jmarantz): Change the JS to remove pagespeedStatisticsUrl.
235 fetch->Write(console_js, handler);
236 fetch->Write("google.setOnLoadCallback(pagespeed.startConsole);", handler);
237 fetch->Write("</script>\n", handler);
238 } else {
239 fetch->Write("<p>\n"
240 " Failed to load PageSpeed Console because:\n"
241 "</p>\n"
242 "<ul>\n", handler);
243 if (!statistics_enabled) {
244 fetch->Write(" <li>Statistics is not enabled.</li>\n",
245 handler);
246 }
247 if (!logging_enabled) {
248 fetch->Write(" <li>StatisticsLogging is not enabled."
249 "</li>\n", handler);
250 }
251 if (!log_dir_set) {
252 fetch->Write(" <li>LogDir is not set.</li>\n", handler);
253 }
254 fetch->Write("</ul>\n"
255 "<p>\n"
256 " In order to use the console you must configure these\n"
257 " options. See the <a href='https://developers.google.com/"
258 "speed/pagespeed/module/console'>console documentation</a>\n"
259 " for more details.\n"
260 "</p>\n", handler);
261 }
262 }
263
264 void AdminSite::StatisticsJsonHandler(AsyncFetch* fetch, Statistics* stats) {
265 fetch->response_headers()->SetStatusAndReason(HttpStatus::kOK);
266 fetch->response_headers()->Add(HttpAttributes::kContentType,
267 kContentTypeJson.mime_type());
268 stats->DumpJson(fetch, message_handler_);
269 fetch->Done(true);
270 }
271
272 void AdminSite::StatisticsHandler(const RewriteOptions& options,
273 AdminSource source, AsyncFetch* fetch,
274 Statistics* stats) {
275 GoogleString head_markup = StrCat(
276 "<style>", CSS_statistics_css, "</style>\n");
277 AdminHtml admin_html("statistics", head_markup, source, timer_, fetch,
278 message_handler_);
279 // We have to call Dump() here to write data to sources for
280 // system/system_test.sh: Line 79. We use JS to update the data in refreshes.
281 fetch->Write("<pre id='stat'>", message_handler_);
282 stats->Dump(fetch, message_handler_);
283 fetch->Write("</pre>\n", message_handler_);
284 StringPiece statistics_js = options.Enabled(RewriteOptions::kDebug) ?
285 JS_statistics_js :
286 JS_statistics_js_opt;
287 fetch->Write(StrCat("<script type='text/javascript'>", statistics_js,
288 "\npagespeed.Statistics.Start();</script>\n"),
289 message_handler_);
290 }
291
292 void AdminSite::GraphsHandler(const RewriteOptions& options,
293 AdminSource source,
294 const QueryParams& query_params,
295 AsyncFetch* fetch,
296 Statistics* statistics) {
297 if (query_params.Has("json")) {
298 ConsoleJsonHandler(query_params, fetch, statistics);
299 return;
300 }
301 GoogleString head_markup = StrCat(
302 "<style>", CSS_graphs_css, "</style>\n");
303 AdminHtml admin_html("graphs", head_markup, source, timer_, fetch,
304 message_handler_);
305 fetch->Write("<div id='cache_applied'>Loading Charts...</div>"
306 "<div id='cache_type'>Loading Charts...</div>"
307 "<div id='ipro'>Loading Charts...</div>"
308 "<div id='image_rewriting'>Loading Charts...</div>"
309 "<div id='realtime'>Loading Charts...</div>",
310 message_handler_);
311 fetch->Write("<script type='text/javascript' "
312 "src='https://www.google.com/jsapi'></script>",
313 message_handler_);
314 StringPiece graphs_js = options.Enabled(RewriteOptions::kDebug) ?
315 JS_graphs_js :
316 JS_graphs_js_opt;
317 fetch->Write(StrCat("<script type='text/javascript'>", graphs_js,
318 "\npagespeed.Graphs.Start();</script>\n"),
319 message_handler_);
320 }
321
322 void AdminSite::ConsoleJsonHandler(const QueryParams& params,
323 AsyncFetch* fetch, Statistics* statistics) {
324 StatisticsLogger* console_logger = statistics->console_logger();
325 if (console_logger == NULL) {
326 fetch->response_headers()->SetStatusAndReason(HttpStatus::kNotFound);
327 fetch->response_headers()->Add(HttpAttributes::kContentType, "text/plain");
328 fetch->Write(
329 "console_logger must be enabled to use '?json' query parameter.",
330 message_handler_);
331 } else {
332 fetch->response_headers()->SetStatusAndReason(HttpStatus::kOK);
333
334 // TODO(morlovich): It would be more secure to do:
335 // Content-Type: application/javascript; charset=utf-8
336 // instead of using a JSON one. There are probably a few other
337 // anti-sniffing headers we could add as well.
338 //
339 // Also, it would be good to start what we serve with )]}' and a newline,
340 // and updating the client js, to make completely sure the browser can't be
341 // tricked into running it.
342 fetch->response_headers()->Add(HttpAttributes::kContentType,
343 kContentTypeJson.mime_type());
344
345 std::set<GoogleString> var_titles;
346 // Default is to fetch data used on graphs page.
347 bool dump_for_graphs = true;
348
349 // Default values for start_time, end_time, and granularity_ms in case the
350 // query does not include these parameters.
351 int64 start_time = 0;
352 int64 end_time = timer_->NowMs();
353 // Granularity is the difference in ms between data points. If it is not
354 // specified by the query, the default value is 3000 ms, the same as the
355 // default logging granularity.
356 int64 granularity_ms = 3000;
357 for (int i = 0; i < params.size(); ++i) {
358 GoogleString value;
359 if (params.UnescapedValue(i, &value)) {
360 StringPiece name = params.name(i);
361 if (name =="start_time") {
362 StringToInt64(value, &start_time);
363 } else if (name == "end_time") {
364 StringToInt64(value, &end_time);
365 } else if (name == "var_titles") {
366 // Fetch specified data if users populate var_titles.
367 dump_for_graphs = false;
368 std::vector<StringPiece> variable_names;
369 SplitStringPieceToVector(value, ",", &variable_names, true);
370 for (size_t i = 0; i < variable_names.size(); ++i) {
371 var_titles.insert(variable_names[i].as_string());
372 }
373 } else if (name == "granularity") {
374 StringToInt64(value, &granularity_ms);
375 }
376 }
377 }
378 console_logger->DumpJSON(dump_for_graphs, var_titles, start_time, end_time,
379 granularity_ms, fetch, message_handler_);
380 }
381 fetch->Done(true);
382 }
383
384 void AdminSite::PrintHistograms(AdminSource source, AsyncFetch* fetch,
385 Statistics* stats) {
386 AdminHtml admin_html("histograms", "", source, timer_, fetch,
387 message_handler_);
388 stats->RenderHistograms(fetch, message_handler_);
389 }
390
391 namespace {
392
393 static const char kTableStart[] =
394 "<table class='pagespeed-caches-structure'>\n"
395 " <thead>\n"
396 " <tr>\n"
397 " <td>Cache</td><td>Detail</td><td>Structure</td>\n"
398 " </tr>\n"
399 " </thead>\n"
400 " <tbody>";
401
402 static const char kTableEnd[] =
403 " </tbody>\n"
404 "</table>";
405
406 // Takes a complicated descriptor like
407 // "HTTPCache(Fallback(small=Batcher(cache=Stats(parefix=memcached_async,"
408 // "cache=Async(AprMemCache)),parallelism=1,max=1000),large=Stats("
409 // "prefix=file_cache,cache=FileCache)))"
410 // and strips away the crap most users don't want to see, as they most
411 // likely did not configure it, and return
412 // "Async AprMemCache FileCache"
413 GoogleString HackCacheDescriptor(StringPiece name) {
414 GoogleString out;
415 // There's a lot of complicated syntax in the cache name giving the
416 // detailed hierarchical structure. This is really hard to read and
417 // overly cryptic; it's designed for unit tests. But let's extract
418 // a few keywords out of this to understand the main pointers.
419 static const char* kCacheKeywords[] = {
420 "Compressed", "Async", "SharedMemCache", "LRUCache", "AprMemCache",
421 "FileCache", "RedisCache"
422 };
423 const char* delim = "";
424 for (int i = 0, n = arraysize(kCacheKeywords); i < n; ++i) {
425 if (name.find(kCacheKeywords[i]) != StringPiece::npos) {
426 StrAppend(&out, delim, kCacheKeywords[i]);
427 delim = " ";
428 }
429 }
430 if (out.empty()) {
431 name.CopyToString(&out);
432 }
433 return out;
434 }
435
436 // Takes a complicated descriptor like
437 // "HTTPCache(Fallback(small=Batcher(cache=Stats(prefix=memcached_async,"
438 // "cache=Async(AprMemCache)),parallelism=1,max=1000),large=Stats("
439 // "prefix=file_cache,cache=FileCache)))"
440 // and injects HTML line-breaks and indentation based on the parent depth,
441 // yielding HTML that renders like this (with and <br/>)
442 // HTTPCache(
443 // Fallback(
444 // small=Batcher(
445 // cache=Stats(
446 // prefix=memcached_async,
447 // cache=Async(
448 // AprMemCache)),
449 // parallelism=1,
450 // max=1000),
451 // large=Stats(
452 // prefix=file_cache,
453 // cache=FileCache)))
454 GoogleString IndentCacheDescriptor(StringPiece name) {
455 GoogleString out, buf;
456 int depth = 0;
457 for (int i = 0, n = name.size(); i < n; ++i) {
458 StrAppend(&out, HtmlKeywords::Escape(name.substr(i, 1), &buf));
459 switch (name[i]) {
460 case '(':
461 ++depth;
462 FALLTHROUGH_INTENDED;
463 case ',':
464 out += "<br/>";
465 for (int j = 0; j < depth; ++j) {
466 out += " ";
467 }
468 break;
469 case ')':
470 --depth;
471 break;
472 }
473 }
474 return out;
475 }
476
477 GoogleString CacheInfoHtmlSnippet(StringPiece label, StringPiece descriptor) {
478 GoogleString out, escaped;
479 StrAppend(&out, "<tr style='vertical-align:top;'><td>", label,
480 "</td><td><input id='", label);
481 StrAppend(&out, "_toggle' type='checkbox' ",
482 "onclick=\"pagespeed.Caches.toggleDetail('", label,
483 "')\"/></td><td><code id='", label, "_summary'>");
484 StrAppend(&out, HtmlKeywords::Escape(HackCacheDescriptor(descriptor),
485 &escaped));
486 StrAppend(&out, "</code><code id='", label,
487 "_detail' style='display:none;'>");
488 StrAppend(&out, IndentCacheDescriptor(descriptor));
489 StrAppend(&out, "</code></td></tr>\n");
490 return out;
491 }
492
493 } // namespace
494
495 void AdminSite::PrintCaches(bool is_global, AdminSource source,
496 const GoogleUrl& stripped_gurl,
497 const QueryParams& query_params,
498 const RewriteOptions* options,
499 SystemCachePath* cache_path,
500 AsyncFetch* fetch, SystemCaches* system_caches,
501 CacheInterface* filesystem_metadata_cache,
502 HTTPCache* http_cache,
503 CacheInterface* metadata_cache,
504 PropertyCache* page_property_cache,
505 ServerContext* server_context) {
506 GoogleString url;
507 if ((source == kPageSpeedAdmin) &&
508 query_params.Lookup1Unescaped("url", &url)) {
509 // Delegate to ShowCacheHandler to get the cached value for that
510 // URL, which it may do asynchronously, so we cannot use the
511 // AdminHtml abstraction which closes the connection in its
512 // destructor.
513 GoogleString json_dummy;
514 ServerContext::Format format = ServerContext::kFormatAsHtml;
515 if (query_params.Lookup1Unescaped("json", &json_dummy)) {
516 format = ServerContext::kFormatAsJson;
517 }
518 GoogleString ua;
519 query_params.Lookup1Unescaped("user_agent", &ua);
520 server_context->ShowCacheHandler(
521 format, url, ua, query_params.Has("Delete"), fetch, options->Clone());
522 } else if ((source == kPageSpeedAdmin) &&
523 query_params.Lookup1Unescaped("new_set", &url)) {
524 ResponseHeaders* response_headers = fetch->response_headers();
525 response_headers->SetStatusAndReason(HttpStatus::kOK);
526 response_headers->Add(HttpAttributes::kContentType, "text/html");
527 fetch->Write(options->PurgeSetString(), message_handler_);
528 fetch->Done(true);
529 } else if ((source == kPageSpeedAdmin) &&
530 query_params.Lookup1Unescaped("purge", &url)) {
531 ResponseHeaders* response_headers = fetch->response_headers();
532 if (!options->enable_cache_purge()) {
533 response_headers->SetStatusAndReason(HttpStatus::kOK);
534 response_headers->Add(HttpAttributes::kContentType, "text/html");
535 // TODO(jmarantz): virtualize the formatting of this message so that
536 // it's correct in ngx_pagespeed and mod_pagespeed (and IISpeed etc).
537 fetch->Write("Purging not enabled: please add\n", message_handler_);
538 HtmlKeywords::WritePre(
539 StrCat(" ", server_context->FormatOption("EnableCachePurge", "on")),
540 "", fetch, message_handler_);
541 fetch->Write(
542 "\nto your configuration file. See the \n"
543 "<a href='https://developers.google.com/speed/pagespeed/module"
544 "/system#purge_cache'>documentation</a> "
545 "for more information about cache purging.",
546 message_handler_);
547 fetch->Done(true);
548 } else if (url == "*") {
549 PurgeHandler(url, cache_path, fetch);
550 } else if (url.empty()) {
551 response_headers->SetStatusAndReason(HttpStatus::kOK);
552 response_headers->Add(HttpAttributes::kContentType, "text/html");
553 fetch->Write("Empty URL", message_handler_);
554 fetch->Done(true);
555 } else {
556 GoogleUrl origin(stripped_gurl.Origin());
557 GoogleUrl resolved(origin, url);
558 if (!resolved.IsWebValid()) {
559 response_headers->SetStatusAndReason(HttpStatus::kOK);
560 response_headers->Add(HttpAttributes::kContentType, "text/html");
561 GoogleString escaped_url;
562 HtmlKeywords::Escape(url, &escaped_url);
563 fetch->Write(StrCat("Invalid URL: ", escaped_url), message_handler_);
564 fetch->Done(true);
565 } else {
566 PurgeHandler(resolved.Spec(), cache_path, fetch);
567 }
568 }
569 } else {
570 GoogleString head_markup = StrCat(
571 "<style>", CSS_caches_css, "</style>\n");
572 AdminHtml admin_html("cache", head_markup, source, timer_, fetch,
573 message_handler_);
574
575 fetch->Write("<div id='show_metadata'>", message_handler_);
576 // Present a small form to enter a URL.
577 if (source == kPageSpeedAdmin) {
578 const char* user_agent = fetch->request_headers()->Lookup1(
579 HttpAttributes::kUserAgent);
580 fetch->Write(ServerContext::ShowCacheForm(user_agent), message_handler_);
581 }
582 fetch->Write("</div>\n", message_handler_);
583 // Display configured cache information.
584 if (system_caches != NULL) {
585 int flags = SystemCaches::kDefaultStatFlags;
586 if (is_global) {
587 flags |= SystemCaches::kGlobalView;
588 }
589
590 // TODO(jmarantz): Consider whether it makes sense to disable
591 // either of these flags to limit the content when someone asks
592 // for info about the cache.
593 flags |= SystemCaches::kIncludeMemcached;
594 flags |= SystemCaches::kIncludeRedis;
595 fetch->Write("<div id='cache_struct'>",
596 message_handler_);
597 fetch->Write(kTableStart, message_handler_);
598 CacheInterface* fsmdc = filesystem_metadata_cache;
599 fetch->Write(StrCat(
600 CacheInfoHtmlSnippet("HTTP Cache", http_cache->Name()),
601 CacheInfoHtmlSnippet("Metadata Cache", metadata_cache->Name()),
602 CacheInfoHtmlSnippet("Property Cache",
603 page_property_cache->property_store()->Name()),
604 CacheInfoHtmlSnippet("FileSystem Metadata Cache",
605 (fsmdc == NULL) ? "none" : fsmdc->Name())),
606 message_handler_);
607 fetch->Write(kTableEnd, message_handler_);
608 fetch->Write("</div>", message_handler_);
609
610 fetch->Write("<div id='physical_cache'>",
611 message_handler_);
612 GoogleString backend_stats;
613 system_caches->PrintCacheStats(
614 static_cast<SystemCaches::StatFlags>(flags), &backend_stats);
615 if (!backend_stats.empty()) {
616 HtmlKeywords::WritePre(backend_stats, "", fetch, message_handler_);
617 }
618 fetch->Write("</div>", message_handler_);
619
620 fetch->Write("<div id='purge_cache'>",
621 message_handler_);
622 // Filled in by JS in caches.js: updatePurgeSet().
623 fetch->Write("<h3>Purge Set</h3>"
624 "<div id='purge_global'"
625 " class='pagespeed-caches-purge-global'></div>"
626 "<div id='purge_table'"
627 " class='pagespeed-caches-purge-table'></div>"
628 "</div>", // closes 'purge_cache'.
629 message_handler_);
630 }
631 StringPiece caches_js = options->Enabled(RewriteOptions::kDebug) ?
632 JS_caches_js :
633 JS_caches_js_opt;
634 // Practice what we preach: put the blocking JS in the tail.
635 // TODO(jmarantz): use static asset manager to compile & deliver JS
636 // externally.
637 fetch->Write(StrCat("<script type='text/javascript'>", caches_js,
638 "\npagespeed.Caches.Start();</script>\n"),
639 message_handler_);
640 }
641 }
642
643 void AdminSite::PrintConfig(
644 AdminSource source, AsyncFetch* fetch,
645 SystemRewriteOptions* global_system_rewrite_options) {
646 AdminHtml admin_html("config", "", source, timer_, fetch, message_handler_);
647 HtmlKeywords::WritePre(
648 global_system_rewrite_options->OptionsToString(), "",
649 fetch, message_handler_);
650 }
651
652 void AdminSite::MessageHistoryHandler(const RewriteOptions& options,
653 AdminSource source, AsyncFetch* fetch) {
654 // Request for page /mod_pagespeed_message.
655 GoogleString log;
656 StringWriter log_writer(&log);
657 AdminHtml admin_html("message_history", "", source, timer_, fetch,
658 message_handler_);
659 if (message_handler_->Dump(&log_writer)) {
660 fetch->Write("<div id='log'>", message_handler_);
661 // Write pre-tag and color messages.
662 StringPieceVector messages;
663 message_handler_->ParseMessageDumpIntoMessages(log, &messages);
664 for (int i = 0, size = messages.size(); i < size; ++i) {
665 if (messages[i].length() > 0) {
666 switch (message_handler_->GetMessageType(messages[i])) {
667 case kError: {
668 HtmlKeywords::WritePre(
669 message_handler_->ReformatMessage(messages[i]),
670 "color:red; margin:0;", fetch, message_handler_);
671 break;
672 }
673 case kWarning: {
674 HtmlKeywords::WritePre(
675 message_handler_->ReformatMessage(messages[i]),
676 "color:brown; margin:0;", fetch, message_handler_);
677 break;
678 }
679 case kFatal: {
680 HtmlKeywords::WritePre(
681 message_handler_->ReformatMessage(messages[i]),
682 "color:orange; margin:0;", fetch, message_handler_);
683 break;
684 }
685 default: {
686 HtmlKeywords::WritePre(
687 message_handler_->ReformatMessage(messages[i]),
688 "margin:0;", fetch, message_handler_);
689 }
690 }
691 }
692 }
693 fetch->Write("</div>\n", message_handler_);
694 StringPiece messages_js = options.Enabled(RewriteOptions::kDebug) ?
695 JS_messages_js :
696 JS_messages_js_opt;
697 fetch->Write(StrCat("<script type='text/javascript'>", messages_js,
698 "\npagespeed.Messages.Start();</script>\n"),
699 message_handler_);
700 } else {
701 fetch->Write("<p>Writing to mod_pagespeed_message failed. \n"
702 "Verify that MessageBufferSize is not set to 0 "
703 "in pagespeed.conf.</p>\n",
704 message_handler_);
705 }
706 }
707
708 void AdminSite::AdminPage(
709 bool is_global, const GoogleUrl& stripped_gurl,
710 const QueryParams& query_params, const RewriteOptions* options,
711 SystemCachePath* cache_path, AsyncFetch* fetch, SystemCaches* system_caches,
712 CacheInterface* filesystem_metadata_cache, HTTPCache* http_cache,
713 CacheInterface* metadata_cache, PropertyCache* page_property_cache,
714 ServerContext* server_context, Statistics* statistics, Statistics* stats,
715 SystemRewriteOptions* global_system_rewrite_options) {
716 // The handler is "pagespeed_admin", so we must dispatch off of
717 // the remainder of the URL. For
718 // "http://example.com/pagespeed_admin/foo?a=b" we want to pull out
719 // "foo".
720 //
721 // Note that the comments here referring to "/pagespeed_admin" reflect
722 // only the default admin path in Apache for fresh installs. In fact
723 // we can put the handler on any path, and this code should still work;
724 // all the paths here are specified relative to the incoming URL.
725 StringPiece path = stripped_gurl.PathSansQuery(); // "/pagespeed_admin/foo"
726 path = path.substr(1); // "pagespeed_admin/foo"
727
728 // If there are no slashes at all in the path, e.g. it's "pagespeed_admin",
729 // then the relative references to "config" etc will not work. We need
730 // to serve the admin pages on "/pagespeed_admin/". So if we got to this
731 // point and there are no slashes, then we can just redirect immediately
732 // by adding a slash.
733 //
734 // If the user has mapped the pagespeed_admin handler to a path with
735 // an embedded slash, say "pagespeed/myadmin", then it's hard to tell
736 // whether we should redirect, because we don't know what the the
737 // intended path is. In this case, we'll fall through to a leaf
738 // analysis on "myadmin", fail to find a match, and print a "Did You Mean"
739 // page. It's not as good as a redirect but since we can't tell an
740 // omitted slash from a typo it's the best we can do.
741 if (path.find('/') == StringPiece::npos) {
742 // If the URL is "/pagespeed_admin", then redirect to "/pagespeed_admin/" so
743 // that relative URL references will work.
744 ResponseHeaders* response_headers = fetch->response_headers();
745 response_headers->SetStatusAndReason(HttpStatus::kMovedPermanently);
746 GoogleString admin_with_slash = StrCat(stripped_gurl.AllExceptQuery(), "/");
747 response_headers->Add(HttpAttributes::kLocation, admin_with_slash);
748 response_headers->Add(HttpAttributes::kContentType, "text/html");
749 GoogleString escaped_url;
750 HtmlKeywords::Escape(admin_with_slash, &escaped_url);
751 fetch->Write(StrCat("Redirecting to URL ", escaped_url), message_handler_);
752 fetch->Done(true);
753 } else {
754 StringPiece leaf = stripped_gurl.LeafSansQuery();
755 if ((leaf == "statistics") || (leaf.empty())) {
756 StatisticsHandler(*options, kPageSpeedAdmin, fetch, stats);
757 } else if (leaf == "stats_json") {
758 StatisticsJsonHandler(fetch, stats);
759 } else if (leaf == "graphs") {
760 GraphsHandler(*options, kPageSpeedAdmin, query_params, fetch, statistics);
761 } else if (leaf == "config") {
762 PrintConfig(kPageSpeedAdmin, fetch, global_system_rewrite_options);
763 } else if (leaf == "console") {
764 // TODO(jmarantz): add vhost-local and aggregate message buffers.
765 ConsoleHandler(*global_system_rewrite_options, *options, kPageSpeedAdmin,
766 query_params, fetch, statistics);
767 } else if (leaf == "message_history") {
768 MessageHistoryHandler(*options, kPageSpeedAdmin, fetch);
769 } else if (leaf == "cache") {
770 PrintCaches(is_global, kPageSpeedAdmin, stripped_gurl, query_params,
771 options, cache_path, fetch, system_caches,
772 filesystem_metadata_cache, http_cache, metadata_cache,
773 page_property_cache, server_context);
774 } else if (leaf == "histograms") {
775 PrintHistograms(kPageSpeedAdmin, fetch, stats);
776 } else {
777 fetch->response_headers()->SetStatusAndReason(HttpStatus::kNotFound);
778 fetch->response_headers()->Add(HttpAttributes::kContentType, "text/html");
779 fetch->Write("Unknown admin page: ", message_handler_);
780 HtmlKeywords::WritePre(leaf, "", fetch, message_handler_);
781
782 // It's possible that the handler is installed on /a/b/c, and we
783 // are now reporting "unknown admin page: c". This is kind of a guess,
784 // but provide a nice link here to what might be the correct admin page.
785 //
786 // This is just a guess, so we don't want to redirect.
787 fetch->Write("<br/>Did you mean to visit: ", message_handler_);
788 GoogleString escaped_url;
789 HtmlKeywords::Escape(StrCat(stripped_gurl.AllExceptQuery(), "/"),
790 &escaped_url);
791 fetch->Write(StrCat("<a href='", escaped_url, "'>", escaped_url,
792 "</a>\n"),
793 message_handler_);
794 fetch->Done(true);
795 }
796 }
797 }
798
799 void AdminSite::StatisticsPage(
800 bool is_global, const QueryParams& query_params,
801 const RewriteOptions* options, AsyncFetch* fetch,
802 SystemCaches* system_caches, CacheInterface* filesystem_metadata_cache,
803 HTTPCache* http_cache, CacheInterface* metadata_cache,
804 PropertyCache* page_property_cache, ServerContext* server_context,
805 Statistics* statistics, Statistics* stats,
806 SystemRewriteOptions* global_system_rewrite_options) {
807 if (query_params.Has("json")) {
808 ConsoleJsonHandler(query_params, fetch, statistics);
809 } else if (query_params.Has("config")) {
810 PrintConfig(kStatistics, fetch, global_system_rewrite_options);
811 } else if (query_params.Has("histograms")) {
812 PrintHistograms(kStatistics, fetch, stats);
813 } else if (query_params.Has("graphs")) {
814 GraphsHandler(*options, kStatistics, query_params, fetch, statistics);
815 } else if (query_params.Has("cache")) {
816 GoogleUrl empty_url;
817 PrintCaches(is_global, kStatistics, empty_url, query_params,
818 options, NULL, // cache_path is reference from statistics page.
819 fetch, system_caches, filesystem_metadata_cache,
820 http_cache, metadata_cache, page_property_cache,
821 server_context);
822 } else {
823 StatisticsHandler(*options, kStatistics, fetch, stats);
824 }
825 }
826
827 namespace {
828
829 // Provides a Done(bool, StringPiece) entry point for use as a Purge
830 // callback. Translates the success into an Http status code for the
831 // AsyncFetch, sending any failure reason in the response body.
832 class PurgeFetchCallbackGasket {
833 public:
834 PurgeFetchCallbackGasket(AsyncFetch* fetch, MessageHandler* handler)
835 : fetch_(fetch),
836 message_handler_(handler) {
837 }
838 void Done(bool success, StringPiece reason) {
839 ResponseHeaders* headers = fetch_->response_headers();
840 headers->set_status_code(HttpStatus::kOK);
841 headers->Add(HttpAttributes::kContentType, "text/html");
842 // TODO(xqyin): Currently we may still return 'purge successful' even if
843 // the URL does not exist in our cache. Figure out how to solve this case
844 // while we don't want to search the whole cache which could be very large.
845 if (success) {
846 fetch_->Write("Purge successful", message_handler_);
847 } else {
848 GoogleString buf;
849 fetch_->Write(HtmlKeywords::Escape(reason, &buf), message_handler_);
850 fetch_->Write("\n", message_handler_);
851 fetch_->Write(HtmlKeywords::Escape(error_, &buf), message_handler_);
852 }
853 fetch_->Done(true);
854 delete this;
855 }
856
857 void set_error(StringPiece x) { x.CopyToString(&error_); }
858
859 private:
860 AsyncFetch* fetch_;
861 MessageHandler* message_handler_;
862 GoogleString error_;
863
864 DISALLOW_COPY_AND_ASSIGN(PurgeFetchCallbackGasket);
865 };
866
867 } // namespace
868
869 void AdminSite::PurgeHandler(StringPiece url, SystemCachePath* cache_path,
870 AsyncFetch* fetch) {
871 PurgeContext* purge_context = cache_path->purge_context();
872 int64 now_ms = timer_->NowMs();
873 PurgeFetchCallbackGasket* gasket =
874 new PurgeFetchCallbackGasket(fetch, message_handler_);
875 PurgeContext::PurgeCallback* callback = NewCallback(
876 gasket, &PurgeFetchCallbackGasket::Done);
877 if (strings::EndsWith(url, "*")) {
878 // If the url is "*" we'll just purge everything. Note that we will
879 // ignore any sub-paths in the expression. We can only purge the
880 // entire cache, or specific URLs, not general wildcards.
881 purge_context->SetCachePurgeGlobalTimestampMs(now_ms, callback);
882 } else {
883 purge_context->AddPurgeUrl(url, now_ms, callback);
884 }
885 }
886
887 } // namespace net_instaweb