Repository: trafficserver
Updated Branches:
  refs/heads/master f347b0ddd -> 0521f07be


[TS-2688] atscppapi: new exmaple plugin Boom


Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo
Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/0521f07b
Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/0521f07b
Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/0521f07b

Branch: refs/heads/master
Commit: 0521f07bea1cde23d39a6b7e657084e39d616e4b
Parents: f347b0d
Author: Omer Shapira <oshap...@linkedin.com>
Authored: Tue Apr 1 14:08:54 2014 -0700
Committer: Brian Geffon <bri...@apache.org>
Committed: Tue Apr 1 14:08:54 2014 -0700

----------------------------------------------------------------------
 CHANGES                                 |   3 +
 configure.ac                            |   1 +
 lib/atscppapi/examples/Makefile.am      |   1 +
 lib/atscppapi/examples/boom/Makefile.am |  28 ++
 lib/atscppapi/examples/boom/README.txt  |  72 +++++
 lib/atscppapi/examples/boom/boom.cc     | 417 +++++++++++++++++++++++++++
 6 files changed, 522 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/trafficserver/blob/0521f07b/CHANGES
----------------------------------------------------------------------
diff --git a/CHANGES b/CHANGES
index 97271c5..b4161d8 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,9 @@
                                                          -*- coding: utf-8 -*-
 Changes with Apache Traffic Server 5.0.0
 
+  *) [TS-2688] atscppapi: new example plugin Boom
+    Author: Omer Shapira <oshap...@linkedin.com> 
+
   *) [TS-2686] Change background_fetch to defer cacheability check until
    SEND_RESPONSE_HDR hook. This allows for other READ_REQUEST_HDR hooks to
    modify the response header before we evaluate.

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/0521f07b/configure.ac
----------------------------------------------------------------------
diff --git a/configure.ac b/configure.ac
index 79da351..504f357 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1937,6 +1937,7 @@ AC_CONFIG_FILES([
   lib/atscppapi/examples/clientredirect/Makefile
   lib/atscppapi/examples/clientrequest/Makefile
   lib/atscppapi/examples/customresponse/Makefile
+  lib/atscppapi/examples/boom/Makefile
   lib/atscppapi/examples/globalhook/Makefile
   lib/atscppapi/examples/gzip_transformation/Makefile
   lib/atscppapi/examples/helloworld/Makefile

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/0521f07b/lib/atscppapi/examples/Makefile.am
----------------------------------------------------------------------
diff --git a/lib/atscppapi/examples/Makefile.am 
b/lib/atscppapi/examples/Makefile.am
index 804a346..137539f 100644
--- a/lib/atscppapi/examples/Makefile.am
+++ b/lib/atscppapi/examples/Makefile.am
@@ -26,6 +26,7 @@ SUBDIRS = helloworld \
          null_transformation_plugin \
          post_buffer \
          logger_example \
+         boom \
          stat_example \
       remap_plugin \
          async_http_fetch \

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/0521f07b/lib/atscppapi/examples/boom/Makefile.am
----------------------------------------------------------------------
diff --git a/lib/atscppapi/examples/boom/Makefile.am 
b/lib/atscppapi/examples/boom/Makefile.am
new file mode 100644
index 0000000..882f6a7
--- /dev/null
+++ b/lib/atscppapi/examples/boom/Makefile.am
@@ -0,0 +1,28 @@
+#
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+AM_CPPFLAGS = -I$(top_srcdir)/lib/atscppapi/src/include -Wno-unused-variable
+target=boom.so
+pkglibdir = ${pkglibexecdir}
+pkglib_LTLIBRARIES = boom.la
+boom_la_SOURCES = boom.cc
+boom_la_LDFLAGS = -module -avoid-version -shared 
-L$(top_srcdir)/lib/atscppapi/src/ -latscppapi
+
+all:
+       ln -sf .libs/$(target)
+
+clean-local:
+       rm -f $(target)

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/0521f07b/lib/atscppapi/examples/boom/README.txt
----------------------------------------------------------------------
diff --git a/lib/atscppapi/examples/boom/README.txt 
b/lib/atscppapi/examples/boom/README.txt
new file mode 100644
index 0000000..84325ae
--- /dev/null
+++ b/lib/atscppapi/examples/boom/README.txt
@@ -0,0 +1,72 @@
+Boom is example of how to use a global plugin to attach transaction plugins to 
some of the transactions. 
+
+Boom is used to serve a custom page whenever the server responds with anything 
other than 200. 
+
+
+Usage:
+
+To use Boom, put the plugin into the plugins directory and add the following 
line to plugins.config file:
+
+
+/path/to/boom.so base_directory status_list
+
+
+base_directory_path points to folder that contains custom error pages. The 
error pages names correspond to the errors, in the following ways:
+
+NNN.html - error page that is specific to error NNN, e.g. "404.html" is 
specific to HTTP 404
+
+Nxx.html - error page that is generic to error family Nxx, e.g. "4xx.html" can 
be served for HTTP 401, 402, 403 and so on
+
+default.html - error page that is the most general fallback. 
+
+
+Custom page resolution order
+
+Boom proceeds from the most specific to the most generic custom error page. 
The resolution comprises 5 phases:
+
+Phase 1 - exact match is attempted
+Phase 2 - status family match is attempted
+Phase 3 - boom attempts to serve default.html file
+Phase 4 - boom serves a compiled in fallback message. 
+
+To illustrate the resolution, lets assume the following scenario:
+ 
+- plugins.config contains the line 
+
+/path/boom.so /path/errors 404,4xx,5xx
+
+- /path/errors contains files 
+
+  /path/errors/404.html
+  /path/errors/4xx.html
+  /path/errors/3xx.html
+  /path/errors/5xx.html
+
+Lets consider the following scenarios:
+
+HTTP 404 
+
+Phase 1 - exact match is attempted:
+Since status code 404 is specified in the status_list, AND 
/path/errors/404.html is present, boom will serve /path/errors/404.html
+
+HTTP 403 
+
+Phase 1 - exact match is attempted:
+Since status 403 is not specified in the error list, boom skips to error 
family match 
+
+Phase 2 - error family match is attempted:
+Since status wildcard 4xx is specified in the error list AND 
/path/errors/4xx.html is present, boom will serve /path/errors/4xx.html
+
+
+HTTP 500
+Phase 1 - exact match is attempted:
+Since status 500 is not specified in the errors_list, boom skips to error 
family match 
+
+Phase 2 - error family match is attempted:
+File /path/errors/5xx.html is present, however status wildcard 5xx is NOT 
specified. Therefore boom skips to general fallback match. 
+
+Phase 3 - general fallback match is attempted:
+Since file /path/errors/default.html is not present, boom skips to the 
compiled in fallback 
+
+Phase 4 - compiled in fallback. 
+boom responds with the compiled in message "<html><body><h1>Your network will 
be back soon</h1></body></html>" 

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/0521f07b/lib/atscppapi/examples/boom/boom.cc
----------------------------------------------------------------------
diff --git a/lib/atscppapi/examples/boom/boom.cc 
b/lib/atscppapi/examples/boom/boom.cc
new file mode 100644
index 0000000..d346d03
--- /dev/null
+++ b/lib/atscppapi/examples/boom/boom.cc
@@ -0,0 +1,417 @@
+/** @file
+
+  A brief file description
+
+  @section license License
+
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+ */
+
+/*
+ *   This plugin does one thing and one thing only it will
+ *   eat the origin error responses codes if instructed to do so.
+ *
+ *   boom.so error_page_path error_codes
+ *
+ *   Configuration is specified as two arguments in plugin.config
+ *   the first argument is the path to a folder containing the error
+ *   files, if you specify a error code such as 5xx or 4xx then it
+ *   will look for a file called 5xx.html or 4xx.html respectively, if it's not
+ *   found, then it will try to use default.html, if default.html is not found
+ *   the response will be the hard coded html string below.
+ *
+ *   You will specify a comma seperated list WITH NO SPACES!!!
+ *   of error codes to BOOM on, for example you can do:
+ *    3xx 4xx 5xx 6xx or you can specify individual error codes such as 501 
502 404, etc...
+ *   You would put 3xx,4xx,5xx,200 in your config argument REMEMBER NO 
SPACES!!!!
+ *
+ *   If you specify an individual error code, it's expected that there will be 
a file
+ *   in your error page folder with that error code, for example 404 would 
expect
+ *   a page called 404.html. Error codes will try to apply the more specific 
rule first
+ *   for example if you have a 404 and 4xx, the 404 will attempt to match 
first and would
+ *   serve the 404.html page instead of the 4xx.html page. Similiarly, a 403 
would
+ *   serve the 4xx.html page.
+ *
+ *   EXAMPLE:
+ *   boom.so /usr/local/boom 404,5xx
+ *
+ **/
+#include <map>
+#include <vector>
+#include <set>
+#include <string>
+#include <iostream>
+#include <sstream>
+#include <algorithm>
+#include <cstring>
+#include <fstream>
+#include <dirent.h>
+
+#include <atscppapi/Transaction.h>
+
+#include <atscppapi/GlobalPlugin.h>
+#include <atscppapi/TransactionPlugin.h>
+#include <atscppapi/PluginInit.h>
+#include <atscppapi/Headers.h>
+#include <atscppapi/Stat.h>
+#include <atscppapi/Logger.h>
+
+using namespace atscppapi;
+#define TAG "boom"
+
+namespace {
+
+/// Name for the Boom invocation counter
+const std::string BOOM_COUNTER = "BOOM_COUNTER";
+
+// Default file name for the error HTML page TBD when this is going to be used
+const std::string DEFAULT_ERROR_FILE = "default"; // default.html will be 
searched for
+
+// Default error response TBD when the default response will be used
+const std::string DEFAULT_ERROR_RESPONSE =
+    "<html><body><h1>This page will be back soon</h1></body></html>";
+
+// Default HTTP status code to use after booming
+const int DEFAULT_BOOM_HTTP_STATUS_CODE = 200;
+
+// Default HTTP status string to use after booming
+const std::string DEFAULT_BOOM_HTTP_STATUS = "OK (BOOM)";
+
+Stat boom_counter;
+}
+
+// Functor that decides whether the HTTP error can be rewritten or not.
+// Rewritable codes are: 2xx, 3xx, 4xx, 5xx and 6xx.
+// 1xx is NOT rewritable!
+class IsRewritableCode: public std::unary_function<std::string, bool> { // 
could probably be replaced with mem_ptr_fun()..
+private:
+  int current_code_;
+  std::string current_code_string_;
+public:
+  IsRewritableCode(int current_code) :
+      current_code_(current_code) {
+    std::ostringstream oss;
+    oss << current_code_;
+    current_code_string_ = oss.str();
+  }
+
+  bool operator()(const std::string &code) const {
+    TS_DEBUG(TAG, "Checking if %s matches code %s", 
current_code_string_.c_str(), code.c_str());
+    if (code == current_code_string_)
+      return true;
+    if (code == "2xx" && current_code_ >= 200 && current_code_ <= 299)
+      return true;
+    if (code == "3xx" && current_code_ >= 300 && current_code_ <= 399)
+      return true;
+    if (code == "4xx" && current_code_ >= 400 && current_code_ <= 499)
+      return true;
+    if (code == "5xx" && current_code_ >= 500 && current_code_ <= 599)
+      return true;
+    if (code == "6xx" && current_code_ >= 600 && current_code_ <= 699)
+      return true;
+
+    return false;
+  }
+};
+
+class BoomResponseRegistry {
+  // Boom error codes
+  std::set<std::string> error_codes_;
+
+  // Map of error codes to error responses
+  std::map<std::string, std::string> error_responses_;
+
+  // Base directory for the file name
+  std::string base_error_directory_;
+
+  // Global default response string
+  std::string global_response_string_;
+
+  // Convert HTTP status code to string
+  std::string code_from_status(int http_status);
+
+  // Convert HTTP status code to string
+  std::string generic_code_from_status(int http_status);
+
+public:
+
+  // Set a "catchall" global default response
+  void set_global_default_response(const std::string& global_default_response);
+
+  // Populate the registry lookup table with contents of files in
+  // the base directory
+  void populate_error_responses(const std::string& base_directory);
+
+  // Return custom response string for the custom code
+  // Lookup logic (using 404 as example)
+  //  1. Check for exact match (i.e. contents of "404.html")
+  //  2. Check for generic response match (i.e. contents of "4xx.html")
+  //  3. Check for default response (i.e. contents of "default.html")
+  //  4. Check for global default response (settable through 
"set_global_default_response" method)
+  //  5. If all else fails, return compiled in response code
+  const std::string& get_response_for_error_code(int http_status_code);
+
+  // Returns true iff either of the three conditions are true:
+  // 1. Exact match for the error is registered (e.g. "404.html" for HTTP 404)
+  // 2. Generic response match for the error is registered (e.g. "4xx.html" 
for HTTP 404)
+  // 3. Default response match is registered (e.g. "default.html" for HTTP 404)
+  // Return false otherwise
+  bool has_code_registered(int http_status_code);
+
+  // Register error codes
+  void register_error_codes(const std::vector<std::string>& error_codes);
+};
+
+
+void BoomResponseRegistry::register_error_codes(const 
std::vector<std::string>& error_codes)
+{
+  std::vector<std::string>::const_iterator i = error_codes.begin(), e = 
error_codes.end();
+  for (; i != e; ++i) {
+    TS_DEBUG(TAG, "Registering error code %s", (*i).c_str());
+    error_codes_.insert(*i);
+  }
+}
+// forward declaration
+bool get_file_contents(std::string fileName, std::string &contents);
+
+// Examine the error file directory and populate the error_response
+// map with the file contents.
+void BoomResponseRegistry::populate_error_responses(const std::string& 
base_directory) {
+  base_error_directory_ = base_directory;
+
+  // Make sure we have a trailing / after the base directory
+  if (!base_error_directory_.empty()
+      && base_error_directory_[base_error_directory_.length() - 1] != '/')
+    base_error_directory_.append("/"); // make sure we have a trailing /
+
+  // Iterate over files in the base directory.
+  // Filename (sans the .html suffix) becomes the entry to the
+  // registry lookup table
+  DIR *pDIR = NULL;
+  struct dirent *entry;
+
+  pDIR = opendir(base_error_directory_.c_str());
+  if (pDIR != NULL) {
+    while (true) {
+      entry = readdir(pDIR);
+      if (entry == NULL)
+        break;
+
+      if (strcmp(entry->d_name, ".") != 0 && strcmp(entry->d_name, "..") != 0) 
{
+        std::string file_name(entry->d_name, strlen(entry->d_name));
+        if (file_name.length() > 5 && file_name.substr(file_name.length() - 5, 
5) == ".html") {
+          // File is .html, load the file into the map...
+          std::string file_contents;
+          if (get_file_contents(base_error_directory_ + file_name, 
file_contents)) {
+            std::string error_code(file_name.substr(0, file_name.length() - 
5));
+            TS_DEBUG(TAG, "Adding response to error code %s from file %s", 
error_code.c_str(), file_name.c_str());
+            error_responses_[error_code] = file_contents;
+          }
+        }
+      }
+    }
+    closedir(pDIR);
+  }
+}
+
+void BoomResponseRegistry::set_global_default_response(const std::string& 
global_default_response) {
+  global_response_string_ = global_default_response;
+}
+
+const std::string& BoomResponseRegistry::get_response_for_error_code(int 
http_status_code) {
+  std::string code_str = code_from_status(http_status_code);
+
+  if (error_responses_.count(code_str))
+    return error_responses_[code_str];
+
+  std::string gen_code_str = generic_code_from_status(http_status_code);
+
+  if (error_responses_.count(gen_code_str))
+    return error_responses_[gen_code_str];
+
+  if (error_responses_.count(DEFAULT_ERROR_FILE))
+    return error_responses_[DEFAULT_ERROR_FILE];
+
+  return DEFAULT_ERROR_RESPONSE;
+}
+
+bool BoomResponseRegistry::has_code_registered(int http_status_code) {
+  // Only rewritable codes are allowed.
+  std::set<std::string>::iterator ii = std::find_if(error_codes_.begin(), 
error_codes_.end(), IsRewritableCode(http_status_code));
+  if (ii == error_codes_.end())
+    return false;
+  else
+    return true;
+}
+
+std::string BoomResponseRegistry::generic_code_from_status(int code) {
+  if (code >= 200 && code <= 299)
+    return "2xx";
+  else if (code >= 300 && code <= 399)
+    return "3xx";
+  else if (code >= 400 && code <= 499)
+    return "4xx";
+  else if (code >= 500 && code <= 599)
+    return "5xx";
+  else
+    return "default";
+}
+
+std::string BoomResponseRegistry::code_from_status(int code) {
+  std::ostringstream oss;
+  oss << code;
+  std::string code_str = oss.str();
+  return code_str;
+}
+
+// Transaction plugin that intercepts error and displays
+// a error page as configured
+class BoomTransactionPlugin: public TransactionPlugin {
+public:
+  BoomTransactionPlugin(Transaction &transaction, HttpStatus status, const 
std::string &reason,
+      const std::string &body) :
+      TransactionPlugin(transaction), status_(status), reason_(reason), 
body_(body) {
+    TransactionPlugin::registerHook(HOOK_SEND_RESPONSE_HEADERS);
+    TS_DEBUG(TAG, "Created BoomTransaction plugin for txn=%p, status=%d, 
reason=%s, body length=%d",
+        transaction.getAtsHandle(), status, reason.c_str(), 
static_cast<int>(body.length()));
+    transaction.error(body_); // Set the error body now, and change the status 
and reason later.
+  }
+
+  void handleSendResponseHeaders(Transaction &transaction) {
+    transaction.getClientResponse().setStatusCode(status_);
+    transaction.getClientResponse().setReasonPhrase(reason_);
+    transaction.resume();
+  }
+
+private:
+  HttpStatus status_;
+  std::string reason_;
+  std::string body_;
+};
+
+// Utility routine to split string by delimiter.
+void stringSplit(const std::string &in, char delim, std::vector<std::string> 
&res) {
+  std::istringstream ss(in);
+  std::string item;
+  while (std::getline(ss, item, delim)) {
+    res.push_back(item);
+  }
+}
+
+// Utility routine to read file contents into a string
+// @returns true if the file exists and has been successfully read
+bool get_file_contents(std::string fileName, std::string &contents) {
+  if (fileName.empty()) {
+    return false;
+  }
+
+  std::ifstream file(fileName.c_str());
+  if (!file.good()) {
+    return false;
+  }
+
+  size_t BUF_SIZE = 1024;
+  std::vector<char> buf(BUF_SIZE);
+
+  while (!file.eof()) {
+    file.read(&buf[0], BUF_SIZE);
+    if (file.gcount() > 0) {
+      contents.append(&buf[0], file.gcount());
+    }
+  }
+
+  return true;
+}
+
+class BoomGlobalPlugin: public atscppapi::GlobalPlugin {
+
+private:
+  BoomResponseRegistry *response_registry_;
+
+public:
+  BoomGlobalPlugin(BoomResponseRegistry* response_registry) :
+      response_registry_(response_registry) {
+    TS_DEBUG(TAG, "Creating BoomGlobalHook %p", this);
+    registerHook(HOOK_READ_RESPONSE_HEADERS);
+  }
+
+  // Upcall method that is called for every transaction.
+  void handleReadResponseHeaders(Transaction &transaction);
+
+private:
+  BoomGlobalPlugin();
+};
+
+void BoomGlobalPlugin::handleReadResponseHeaders(Transaction &transaction) {
+    // Get response status code from the transaction
+    HttpStatus http_status_code = 
transaction.getServerResponse().getStatusCode();
+
+    TS_DEBUG(TAG, "Checking if response with code %d is in the registry.", 
http_status_code);
+
+    // If the custom response for the error code is registered,
+    // attach the BoomTransactionPlugin to the transaction
+    if (response_registry_->has_code_registered(http_status_code)) {
+      // Get the original reason phrase string from the transaction
+      std::string http_reason_phrase = 
transaction.getServerResponse().getReasonPhrase();
+
+      TS_DEBUG(TAG, "Response has code %d which matches a registered code, 
TransactionPlugin will be created.", http_status_code);
+      // Increment the statistics counter
+      boom_counter.increment();
+
+      // Get custom response code from the registry
+      const std::string& custom_response = 
response_registry_->get_response_for_error_code(
+          http_status_code);
+
+      // Add the transaction plugin to the transaction
+      transaction.addPlugin(
+          new BoomTransactionPlugin(transaction, http_status_code, 
http_reason_phrase,
+              custom_response));
+      // No need to resume/error the transaction,
+      // as BoomTransactionPlugin will take care of terminating the transaction
+      return;
+    } else {
+      TS_DEBUG(TAG, "Code %d was not in the registry, transaction will be 
resumed", http_status_code);
+      transaction.resume();
+    }
+  }
+
+/*
+ * This is the plugin registration point
+ */
+void TSPluginInit(int argc, const char *argv[]) {
+
+  boom_counter.init(BOOM_COUNTER);
+  BoomResponseRegistry *pregistry = new BoomResponseRegistry();
+
+  // If base directory and list of codes are specified,
+  // create a custom registry and initialize Boom with it.
+  // Otherwise, run with default registry.
+  if (argc == 3) {
+    std::string base_directory(argv[1], strlen(argv[1]));
+    pregistry->populate_error_responses(base_directory);
+
+    std::string error_codes_argument(argv[2], strlen(argv[2]));
+    std::vector < std::string > error_codes;
+    stringSplit(error_codes_argument, ',', error_codes);
+    pregistry->register_error_codes(error_codes);
+  } else {
+    TS_ERROR(TAG, "Invalid number of command line arguments, using compile 
time defaults.");
+  }
+
+  BoomGlobalPlugin *pboom = new BoomGlobalPlugin(pregistry);
+}
+

Reply via email to