You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by su...@apache.org on 2015/06/09 01:50:43 UTC

trafficserver git commit: [TS-3662]: Caching range requests plugin.

Repository: trafficserver
Updated Branches:
  refs/heads/master c304f2c08 -> f35b7e060


[TS-3662]: Caching range requests plugin.


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

Branch: refs/heads/master
Commit: f35b7e0609b9def5b3beab26b8c9903e28180a4d
Parents: c304f2c
Author: John Rushford <Jo...@cable.comcast.com>
Authored: Mon Jun 8 23:50:19 2015 +0000
Committer: Sudheer Vinukonda <su...@yahoo-inc.com>
Committed: Mon Jun 8 23:50:19 2015 +0000

----------------------------------------------------------------------
 .../cache_range_requests/Makefile.am            |  21 +
 .../experimental/cache_range_requests/README    |  36 ++
 .../cache_range_requests.cc                     | 428 +++++++++++++++++++
 3 files changed, 485 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/trafficserver/blob/f35b7e06/plugins/experimental/cache_range_requests/Makefile.am
----------------------------------------------------------------------
diff --git a/plugins/experimental/cache_range_requests/Makefile.am b/plugins/experimental/cache_range_requests/Makefile.am
new file mode 100644
index 0000000..5a27cac
--- /dev/null
+++ b/plugins/experimental/cache_range_requests/Makefile.am
@@ -0,0 +1,21 @@
+#  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.
+
+include $(top_srcdir)/build/plugins.mk
+
+pkglib_LTLIBRARIES = cache_range_requests.la
+cache_range_requests_la_SOURCES = cache_range_requests.cc
+cache_range_requests_la_LDFLAGS = $(TS_PLUGIN_LDFLAGS)

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/f35b7e06/plugins/experimental/cache_range_requests/README
----------------------------------------------------------------------
diff --git a/plugins/experimental/cache_range_requests/README b/plugins/experimental/cache_range_requests/README
new file mode 100644
index 0000000..faddeb5
--- /dev/null
+++ b/plugins/experimental/cache_range_requests/README
@@ -0,0 +1,36 @@
+
+Thousands of range requests for a very large object in the traffic server cache
+are likely to increase system load averages due to I/O wait as objects are stored
+on a single stripe or disk drive.
+
+This plugin allows you to remap individual range requests so that they are stored
+as individual objects in the ATS cache when subsequent range requests are likely
+to use the same range.  This spreads range requests over multiple stripes thereby
+reducing I/O wait and system load averages.
+
+This plugin reads the range request header byte range value and then creates a
+new cache key url using the original request url with the range value appended
+to it.  The range header is removed where appropriate from the requests and the
+origin server response code is changed from a 206 to a 200 to insure that the
+object is written to cache using the new cache key url.  The response code sent 
+to the client will be changed back to a 206 and all requests to the origin server 
+will contain the range header so that the correct response is received.
+
+Installation:
+
+    make
+    sudo make install
+
+If you don't have the traffic server binaries in your path, then you will need
+to specify the path to tsxs manually:
+
+    make TSXS=/opt/trafficserver/bin/tsxs
+    sudo make TSXS=/opt/trafficserver/bin/tsxs install
+
+Configuration:
+
+    Add @plugin=cache_range_requests.so to your remap.config rules.
+
+    Or for a global plugin where all range requests are processed,
+    Add cache_range_requests.so to the plugin.config
+

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/f35b7e06/plugins/experimental/cache_range_requests/cache_range_requests.cc
----------------------------------------------------------------------
diff --git a/plugins/experimental/cache_range_requests/cache_range_requests.cc b/plugins/experimental/cache_range_requests/cache_range_requests.cc
new file mode 100644
index 0000000..0967ff0
--- /dev/null
+++ b/plugins/experimental/cache_range_requests/cache_range_requests.cc
@@ -0,0 +1,428 @@
+/*
+ * 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 looks for range requests and then creates a new
+ * cache key url so that each individual range requests is written
+ * to the cache as a individual object so that subsequent range
+ * requests are read accross different disk drives reducing I/O
+ * wait and load averages when there are large numbers of range
+ * requests.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include "ts/ts.h"
+#include "ts/remap.h"
+
+#define PLUGIN_NAME "cache_range_requests"
+
+struct txndata {
+  char *range_value;
+  char *request_url;
+};
+
+static void handle_read_request_header(TSCont, TSEvent, void *);
+static void range_header_check(TSHttpTxn txnp);
+static void handle_send_origin_request(TSCont, TSHttpTxn, struct txndata *);
+static void handle_client_send_response(TSHttpTxn);
+static void handle_server_read_response(TSHttpTxn, struct txndata *);
+static int remove_header(TSMBuffer, TSMLoc, const char *, int);
+static bool set_header(TSMBuffer, TSMLoc, const char *, int, const char *, int);
+static void transaction_handler(TSCont, TSEvent, void *);
+
+/**
+ * Entry point when used as a global plugin.
+ *
+ */
+static void
+handle_read_request_header(TSCont txn_contp, TSEvent event, void *edata)
+{
+  TSHttpTxn txnp = static_cast<TSHttpTxn>(edata);
+
+  TSDebug(PLUGIN_NAME, "Starting handle_read_request_header()");
+
+  range_header_check(txnp);
+
+  TSDebug(PLUGIN_NAME, "End of handle_read_request_header()");
+  TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+}
+
+/**
+ * Reads the client request header and if this is a range request:
+ *
+ * 1. creates a new cache key url using the range request information.
+ * 2. Saves the range request information and then removes the range
+ *    header so that the response retrieved from the origin will
+ *    be written to cache.
+ * 3. Schedules TS_HTTP_SEND_REQUEST_HDR_HOOK, TS_HTTP_SEND_RESPONSE_HDR_HOOK,
+ *    and TS_HTTP_TXN_CLOSE_HOOK for further processing.
+ */
+static void
+range_header_check(TSHttpTxn txnp)
+{
+  char cache_key_url[8192] = {0};
+  char *req_url;
+  int length, url_length;
+  struct txndata *txn_state;
+  TSMBuffer hdr_bufp;
+  TSMLoc req_hdrs = NULL;
+  TSMLoc loc = NULL;
+  TSCont txn_contp;
+
+  if (TS_SUCCESS == TSHttpTxnClientReqGet(txnp, &hdr_bufp, &req_hdrs)) {
+    loc = TSMimeHdrFieldFind(hdr_bufp, req_hdrs, "Range", -1);
+    if (TS_NULL_MLOC != loc) {
+      const char *hdr_value = TSMimeHdrFieldValueStringGet(hdr_bufp, req_hdrs, loc, 0, &length);
+      if (!hdr_value || length <= 0) {
+        TSDebug(PLUGIN_NAME, "range_header_check(): Not a range request.");
+      } else {
+        if (NULL == (txn_contp = TSContCreate((TSEventFunc)transaction_handler, NULL))) {
+          TSError("[%s] TSContCreate(): failed to create the transaction handler continuation.", PLUGIN_NAME);
+        } else {
+          txn_state = (struct txndata *)TSmalloc(sizeof(struct txndata));
+          txn_state->range_value = TSstrndup(hdr_value, length);
+          TSDebug(PLUGIN_NAME, "length = %d, txn_state->range_value = %s", length, txn_state->range_value);
+          txn_state->range_value[length] = '\0'; // workaround for bug in core
+
+          req_url = TSHttpTxnEffectiveUrlStringGet(txnp, &url_length);
+          snprintf(cache_key_url, 8192, "%s-%s", req_url, txn_state->range_value);
+          TSDebug(PLUGIN_NAME, "Rewriting cache URL for %s to %s", req_url, cache_key_url);
+          txn_state->request_url = (char *)TSmalloc(url_length + 1);
+          strncpy(txn_state->request_url, req_url, url_length);
+          txn_state->request_url[url_length] = 0;
+
+          // set the cache key.
+          if (TS_SUCCESS != TSCacheUrlSet(txnp, cache_key_url, strlen(cache_key_url))) {
+            TSDebug(PLUGIN_NAME, "TSCacheUrlSet(): failed to change the cache url to %s.", cache_key_url);
+          }
+          // remove the range request header.
+          if (remove_header(hdr_bufp, req_hdrs, TS_MIME_FIELD_RANGE, TS_MIME_LEN_RANGE) > 0) {
+            TSDebug(PLUGIN_NAME, "Removed the Range: header from request");
+          }
+
+          TSContDataSet(txn_contp, txn_state);
+          TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_REQUEST_HDR_HOOK, txn_contp);
+          TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_RESPONSE_HDR_HOOK, txn_contp);
+          TSHttpTxnHookAdd(txnp, TS_HTTP_TXN_CLOSE_HOOK, txn_contp);
+          TSDebug(PLUGIN_NAME, "Added TS_HTTP_SEND_REQUEST_HDR_HOOK, TS_HTTP_SEND_RESPONSE_HDR_HOOK, and TS_HTTP_TXN_CLOSE_HOOK");
+        }
+      }
+      TSHandleMLocRelease(hdr_bufp, req_hdrs, loc);
+    } else {
+      TSDebug(PLUGIN_NAME, "range_header_check(): no range request header.");
+    }
+    TSHandleMLocRelease(hdr_bufp, req_hdrs, NULL);
+  } else {
+    TSDebug(PLUGIN_NAME, "range_header_check(): failed to retrieve the server request.");
+  }
+}
+
+/**
+ * Restores the range request header if the request must be
+ * satisfied from the origin and schedules the TS_READ_RESPONSE_HDR_HOOK.
+ */
+static void
+handle_send_origin_request(TSCont contp, TSHttpTxn txnp, struct txndata *txn_state)
+{
+  TSMBuffer hdr_bufp;
+  TSMLoc req_hdrs = NULL;
+
+  TSDebug(PLUGIN_NAME, "Starting handle_send_origin_request()");
+  if (TS_SUCCESS == TSHttpTxnServerReqGet(txnp, &hdr_bufp, &req_hdrs) && txn_state->range_value != NULL) {
+    if (set_header(hdr_bufp, req_hdrs, TS_MIME_FIELD_RANGE, TS_MIME_LEN_RANGE, txn_state->range_value,
+                   strlen(txn_state->range_value))) {
+      TSDebug(PLUGIN_NAME, "Added range header: %s", txn_state->range_value);
+      TSHttpTxnHookAdd(txnp, TS_HTTP_READ_RESPONSE_HDR_HOOK, contp);
+    }
+  }
+  TSHandleMLocRelease(hdr_bufp, req_hdrs, NULL);
+  TSDebug(PLUGIN_NAME, "End of handle_send_origin_request()");
+}
+
+/**
+ * Changes the response code back to a 206 Partial content before
+ * replying to the client that requested a range.
+ */
+static void
+handle_client_send_response(TSHttpTxn txnp)
+{
+  bool partial_content_reason = false;
+  char *p, *reason;
+  int length;
+  TSMBuffer response;
+  TSMLoc resp_hdr;
+
+  TSDebug(PLUGIN_NAME, "Starting handle_client_send_response ()");
+
+  TSReturnCode result = TSHttpTxnClientRespGet(txnp, &response, &resp_hdr);
+  TSDebug(PLUGIN_NAME, "result %d", result);
+  if (TS_SUCCESS == result) {
+    TSHttpStatus status = TSHttpHdrStatusGet(response, resp_hdr);
+    // a cached result will have a TS_HTTP_OK with a 'Partial Content' reason
+    if ((p = (char *)TSHttpHdrReasonGet(response, resp_hdr, &length)) != NULL) {
+      reason = TSstrndup(p, length + 1);
+      reason[length] = '\0';
+      if (strncasecmp(reason, "Partial Content", length) == 0) {
+        partial_content_reason = true;
+      }
+    }
+    TSDebug(PLUGIN_NAME, "status %d %s", status, reason);
+    if (TS_HTTP_STATUS_OK == status && partial_content_reason) {
+      TSDebug(PLUGIN_NAME, "handle_client_send_response (): Got TS_HTTP_STATUS_OK.");
+      TSHttpHdrStatusSet(response, resp_hdr, TS_HTTP_STATUS_PARTIAL_CONTENT);
+      TSDebug(PLUGIN_NAME, "handle_client_send_response (): Set response header to TS_HTTP_STATUS_PARTIAL_CONTENT.");
+    }
+  }
+  TSHandleMLocRelease(response, resp_hdr, NULL);
+  TSfree(reason);
+  TSDebug(PLUGIN_NAME, "End of handle_client_send_response ()");
+}
+
+/**
+ * After receiving a range request response from the origin, change
+ * the response code from a 206 Partial content to a 200 OK so that
+ * the response will be written to cache.
+ */
+static void
+handle_server_read_response(TSHttpTxn txnp, struct txndata *txn_state)
+{
+  TSMBuffer response;
+  TSMLoc resp_hdr;
+  TSHttpStatus status;
+
+  TSDebug(PLUGIN_NAME, "Starting handle_server_read_response ()");
+
+  if (TS_SUCCESS == TSHttpTxnServerRespGet(txnp, &response, &resp_hdr)) {
+    status = TSHttpHdrStatusGet(response, resp_hdr);
+    if (TS_HTTP_STATUS_PARTIAL_CONTENT == status) {
+      TSDebug(PLUGIN_NAME, "handle_server_read_response (): Got TS_HTTP_STATUS_PARTIAL_CONTENT.");
+      TSHttpHdrStatusSet(response, resp_hdr, TS_HTTP_STATUS_OK);
+      TSDebug(PLUGIN_NAME, "handle_server_read_response (): Set response header to TS_HTTP_STATUS_OK.");
+      bool cacheable = TSHttpTxnIsCacheable(txnp, NULL, response);
+      TSDebug(PLUGIN_NAME, "handle_server_read_response (): range is cacheable: %d", cacheable);
+    } else if (TS_HTTP_STATUS_OK == status) {
+      TSDebug(PLUGIN_NAME, "The origin does not support range requests, attempting to disable cache write.");
+      if (TS_SUCCESS == TSHttpTxnServerRespNoStoreSet(txnp, 1)) {
+        TSDebug(PLUGIN_NAME, "Cache write has been disabled for this transaction.");
+      } else {
+        TSDebug(PLUGIN_NAME, "Unable to disable cache write for this transaction.");
+      }
+    }
+  }
+  TSHandleMLocRelease(response, resp_hdr, NULL);
+  TSDebug(PLUGIN_NAME, "End of handle_server_read_response ()");
+}
+
+/**
+ * Remove a header (fully) from an TSMLoc / TSMBuffer. Return the number
+ * of fields (header values) we removed.
+ *
+ * From background_fetch.cc
+ */
+static int
+remove_header(TSMBuffer bufp, TSMLoc hdr_loc, const char *header, int len)
+{
+  TSMLoc field = TSMimeHdrFieldFind(bufp, hdr_loc, header, len);
+  int cnt = 0;
+
+  TSDebug(PLUGIN_NAME, "Starting remove_header ()");
+
+  while (field) {
+    TSMLoc tmp = TSMimeHdrFieldNextDup(bufp, hdr_loc, field);
+
+    ++cnt;
+    TSMimeHdrFieldDestroy(bufp, hdr_loc, field);
+    TSHandleMLocRelease(bufp, hdr_loc, field);
+    field = tmp;
+  }
+
+  TSDebug(PLUGIN_NAME, "End of remove_header ()");
+  return cnt;
+}
+
+/**
+ * Set a header to a specific value. This will avoid going to through a
+ * remove / add sequence in case of an existing header.
+ * but clean.
+ *
+ * From background_fetch.cc
+ */
+static bool
+set_header(TSMBuffer bufp, TSMLoc hdr_loc, const char *header, int len, const char *val, int val_len)
+{
+  TSDebug(PLUGIN_NAME, "Starting set_header ()");
+
+  if (!bufp || !hdr_loc || !header || len <= 0 || !val || val_len <= 0) {
+    return false;
+  }
+
+  TSDebug(PLUGIN_NAME, "Starting set_header(): header = %s, len = %d, val = %s, val_len = %d\n", header, len, val, val_len);
+  bool ret = false;
+  TSMLoc field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, header, len);
+
+  if (!field_loc) {
+    // No existing header, so create one
+    if (TS_SUCCESS == TSMimeHdrFieldCreateNamed(bufp, hdr_loc, header, len, &field_loc)) {
+      if (TS_SUCCESS == TSMimeHdrFieldValueStringSet(bufp, hdr_loc, field_loc, -1, val, val_len)) {
+        TSMimeHdrFieldAppend(bufp, hdr_loc, field_loc);
+        ret = true;
+      }
+      TSHandleMLocRelease(bufp, hdr_loc, field_loc);
+    }
+  } else {
+    TSMLoc tmp = NULL;
+    bool first = true;
+
+    while (field_loc) {
+      if (first) {
+        first = false;
+        if (TS_SUCCESS == TSMimeHdrFieldValueStringSet(bufp, hdr_loc, field_loc, -1, val, val_len)) {
+          ret = true;
+        }
+      } else {
+        TSMimeHdrFieldDestroy(bufp, hdr_loc, field_loc);
+      }
+      tmp = TSMimeHdrFieldNextDup(bufp, hdr_loc, field_loc);
+      TSHandleMLocRelease(bufp, hdr_loc, field_loc);
+      field_loc = tmp;
+    }
+  }
+
+  TSDebug(PLUGIN_NAME, "End of set_header ()");
+  return ret;
+}
+
+/**
+ * Remap initialization.
+ */
+TSReturnCode
+TSRemapInit(TSRemapInterface *api_info, char *errbuf, int errbuf_size)
+{
+  TSDebug(PLUGIN_NAME, "cache_range_requests remap init");
+  if (!api_info) {
+    strncpy(errbuf, "[tsremap_init] - Invalid TSRemapInterface argument", errbuf_size - 1);
+    return TS_ERROR;
+  }
+
+  if (api_info->tsremap_version < TSREMAP_VERSION) {
+    snprintf(errbuf, errbuf_size - 1, "[TSRemapInit] - Incorrect API version %ld.%ld", api_info->tsremap_version >> 16,
+             (api_info->tsremap_version & 0xffff));
+    return TS_ERROR;
+  }
+
+  TSDebug(PLUGIN_NAME, "cache_range_requests remap is successfully initialized");
+  return TS_SUCCESS;
+}
+
+/**
+ * not used.
+ */
+TSReturnCode
+TSRemapNewInstance(int argc, char *argv[], void **ih, char * /*errbuf */, int /* errbuf_size */)
+{
+  TSDebug(PLUGIN_NAME, "TSRemapNewInstance ()");
+
+  return TS_SUCCESS;
+}
+
+/**
+ * not used.
+ */
+void
+TSRemapDeleteInstance(void *ih)
+{
+  TSDebug(PLUGIN_NAME, "TSRemapDeleteInstance ()");
+}
+
+/**
+ * Remap entry point.
+ */
+TSRemapStatus
+TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo * /* rri */)
+{
+  TSDebug(PLUGIN_NAME, "TSRemapDoRemap()");
+
+  range_header_check(txnp);
+  return TSREMAP_NO_REMAP;
+}
+
+/**
+ * Global plugin initialization.
+ */
+void
+TSPluginInit(int argc, const char *argv[])
+{
+  TSPluginRegistrationInfo info;
+  TSCont txnp_cont;
+
+  TSDebug(PLUGIN_NAME, "Starting TSPluginInit()");
+
+  info.plugin_name = (char *)PLUGIN_NAME;
+  info.vendor_name = (char *)"Comcast";
+  info.support_email = (char *)"John_Rushford@cable.comcast.com";
+
+  if (TSPluginRegister(TS_SDK_VERSION_3_0, &info) != TS_SUCCESS) {
+    TSError("[%s] TSPluginInit(): Plugin registration failed.\n", PLUGIN_NAME);
+    TSError("[%s] Unable to initialize plugin (disabled).\n", PLUGIN_NAME);
+    return;
+  }
+
+  if (NULL == (txnp_cont = TSContCreate((TSEventFunc)handle_read_request_header, NULL))) {
+    TSError("[%s] TSContCreate(): failed to create the transaction continuation handler.", PLUGIN_NAME);
+    return;
+  } else {
+    TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, txnp_cont);
+  }
+}
+
+/**
+ * Transaction event handler.
+ */
+static void
+transaction_handler(TSCont contp, TSEvent event, void *edata)
+{
+  TSHttpTxn txnp = static_cast<TSHttpTxn>(edata);
+  struct txndata *txn_state = (struct txndata *)TSContDataGet(contp);
+
+  TSDebug(PLUGIN_NAME, "Starting transaction_handler()");
+  switch (event) {
+  case TS_EVENT_HTTP_READ_RESPONSE_HDR:
+    handle_server_read_response(txnp, txn_state);
+    break;
+  case TS_EVENT_HTTP_SEND_REQUEST_HDR:
+    handle_send_origin_request(contp, txnp, txn_state);
+    break;
+  case TS_EVENT_HTTP_SEND_RESPONSE_HDR:
+    handle_client_send_response(txnp);
+    break;
+  case TS_EVENT_HTTP_TXN_CLOSE:
+    TSDebug(PLUGIN_NAME, "Starting handle_transaction_close().");
+    TSfree(txn_state);
+    TSfree(txn_state->range_value);
+    TSfree(txn_state->request_url);
+    TSContDestroy(contp);
+    break;
+  default:
+    TSAssert(!"Unexpected event");
+    break;
+  }
+  TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
+  TSDebug(PLUGIN_NAME, "End of transaction_handler()");
+}