TS-2426: add the xdebug plugin The xdebug plugin scans X-Debug header values and emits the requested debug information. The plugin is named after the X- prefix of the headers that is scans and emits.
The values we scan for are names of debug headers. The first header to be supported is X-Cache-Key, which report the cache lookup URL used buy the request. the second header supported is the Via header, which causes a verbose Via header to be emitted for the request. Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/14667c65 Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/14667c65 Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/14667c65 Branch: refs/heads/5.0.x Commit: 14667c65ff72d6af5da9bec62c41548076093091 Parents: 5e256d2 Author: James Peach <jpe...@apache.org> Authored: Tue Dec 3 15:03:36 2013 -0800 Committer: James Peach <jpe...@apache.org> Committed: Tue Dec 10 09:26:51 2013 -0800 ---------------------------------------------------------------------- CHANGES | 3 + configure.ac | 1 + plugins/experimental/Makefile.am | 23 +-- plugins/experimental/xdebug/xdebug.cc | 217 +++++++++++++++++++++++++++++ 4 files changed, 234 insertions(+), 10 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/trafficserver/blob/14667c65/CHANGES ---------------------------------------------------------------------- diff --git a/CHANGES b/CHANGES index 49488e9..30afc6c 100644 --- a/CHANGES +++ b/CHANGES @@ -1,6 +1,9 @@ -*- coding: utf-8 -*- Changes with Apache Traffic Server 4.2.0 + + *) [TS-2426] Add a new plugin, xdebug, for cache debugging using HTTP headers. + *) [TS-2077] Remove pipeline configurations, they were no-op's anyways. We still support pipelining (we always do), it's just not treated specially (or optimized). http://git-wip-us.apache.org/repos/asf/trafficserver/blob/14667c65/configure.ac ---------------------------------------------------------------------- diff --git a/configure.ac b/configure.ac index fd41a1b..c16f89f 100644 --- a/configure.ac +++ b/configure.ac @@ -1935,6 +1935,7 @@ AC_CONFIG_FILES([ plugins/experimental/tcp_info/Makefile plugins/experimental/healthchecks/Makefile plugins/experimental/remap_stats/Makefile + plugins/experimental/xdebug/Makefile plugins/gzip/Makefile plugins/libloader/Makefile plugins/header_filter/Makefile http://git-wip-us.apache.org/repos/asf/trafficserver/blob/14667c65/plugins/experimental/Makefile.am ---------------------------------------------------------------------- diff --git a/plugins/experimental/Makefile.am b/plugins/experimental/Makefile.am index b0e4d98..3d5a453 100644 --- a/plugins/experimental/Makefile.am +++ b/plugins/experimental/Makefile.am @@ -15,19 +15,22 @@ # limitations under the License. if BUILD_EXPERIMENTAL_PLUGINS + SUBDIRS = \ - lua \ - ts_lua \ + authproxy \ buffer_upload \ - esi \ - rfc5861 \ - tcp_info \ - custom_redirect \ - metalink \ - spdy \ channel_stats \ - authproxy \ + custom_redirect \ + esi \ geoip_acl \ healthchecks \ - remap_stats + lua \ + metalink \ + remap_stats \ + rfc5861 \ + spdy \ + tcp_info \ + ts_lua \ + xdebug + endif http://git-wip-us.apache.org/repos/asf/trafficserver/blob/14667c65/plugins/experimental/xdebug/xdebug.cc ---------------------------------------------------------------------- diff --git a/plugins/experimental/xdebug/xdebug.cc b/plugins/experimental/xdebug/xdebug.cc new file mode 100644 index 0000000..e33ffa3 --- /dev/null +++ b/plugins/experimental/xdebug/xdebug.cc @@ -0,0 +1,217 @@ +/* + * 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 <ts/ts.h> +#include <stdlib.h> +#include <strings.h> + +// The name of the debug request header. This should probably be configurable. +#define X_DEBUG_HEADER "X-Debug" + +#define XHEADER_X_CACHE_KEY 0x0004u + +static int XArgIndex = 0; +static TSCont XInjectHeadersCont = NULL; + +// Return the length of a string literal. +template <int N> unsigned +lengthof(const char (&str)[N]) { + return N - 1; +} + +static TSMLoc +FindOrMakeHdrField(TSMBuffer buffer, TSMLoc hdr, const char * name, unsigned len) +{ + TSMLoc field; + + field = TSMimeHdrFieldFind(buffer, hdr, name, len); + if (field == TS_NULL_MLOC) { + if (TSMimeHdrFieldCreateNamed(buffer, hdr, name, len, &field) == TS_SUCCESS) { + TSReleaseAssert(TSMimeHdrFieldAppend(buffer, hdr, field) == TS_SUCCESS); + } + } + + return field; +} + +static void +InjectCacheKeyHeader(TSHttpTxn txn, TSMBuffer buffer, TSMLoc hdr) +{ + TSMLoc url = TS_NULL_MLOC; + TSMLoc dst = TS_NULL_MLOC; + + struct { char * ptr; int len; } strval = { NULL, 0 }; + + TSDebug("xdebug", "attempting to inject X-Cache-Key header"); + + if (TSUrlCreate(buffer, &url) != TS_SUCCESS) { + goto done; + } + + if (TSHttpTxnCacheLookupUrlGet(txn, buffer, url) != TS_SUCCESS) { + goto done; + } + + strval.ptr = TSUrlStringGet(buffer, url, &strval.len); + if (strval.ptr == NULL || strval.len == 0) { + goto done; + } + + // Create a new response header field. + dst = FindOrMakeHdrField(buffer, hdr, "X-Cache-Key", lengthof("X-Cache-Key")); + if (dst == TS_NULL_MLOC) { + goto done; + } + + // Now copy the cache lookup URL into the response header. + TSReleaseAssert( + TSMimeHdrFieldValueStringInsert(buffer, hdr, dst, 0 /* idx */, strval.ptr, strval.len) == TS_SUCCESS + ); + +done: + if (dst != TS_NULL_MLOC) { + TSHandleMLocRelease(buffer, hdr, dst); + } + + if (url != TS_NULL_MLOC) { + TSHandleMLocRelease(buffer, TS_NULL_MLOC, url); + } + + TSfree(strval.ptr); +} + +static int +XInjectResponseHeaders(TSCont contp, TSEvent event, void * edata) +{ + TSHttpTxn txn = (TSHttpTxn)edata; + intptr_t xheaders = 0; + TSMBuffer buffer; + TSMLoc hdr; + + TSReleaseAssert(event == TS_EVENT_HTTP_SEND_RESPONSE_HDR); + + xheaders = (intptr_t)TSHttpTxnArgGet(txn, XArgIndex); + if (xheaders == 0) { + goto done; + } + + if (TSHttpTxnClientRespGet(txn, &buffer, &hdr) == TS_ERROR) { + goto done; + } + + if (xheaders & XHEADER_X_CACHE_KEY) { + InjectCacheKeyHeader(txn, buffer, hdr); + } + +done: + TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE); + return TS_EVENT_NONE; +} + +// Scan the client request headers and determine which debug headers they +// want in the response. +static int +XScanRequestHeaders(TSCont contp, TSEvent event, void * edata) +{ + TSHttpTxn txn = (TSHttpTxn)edata; + intptr_t xheaders = 0; + TSMLoc field, next; + TSMBuffer buffer; + TSMLoc hdr; + + TSReleaseAssert(event == TS_EVENT_HTTP_READ_REQUEST_HDR); + + if (TSHttpTxnClientReqGet(txn, &buffer, &hdr) == TS_ERROR) { + goto done; + } + + TSDebug("xdebug", "scanning for %s header values", X_DEBUG_HEADER); + + // Walk the X-Debug header values and determine what to inject into the response. + field = TSMimeHdrFieldFind(buffer, hdr, X_DEBUG_HEADER, lengthof(X_DEBUG_HEADER)); + while (field != TS_NULL_MLOC) { + int count = TSMimeHdrFieldValuesCount(buffer, hdr, field); + + for (int i = 0; i < count; ++i) { + const char * value; + int vsize; + + value = TSMimeHdrFieldValueStringGet(buffer, hdr, field, i, &vsize); + if (value == NULL || vsize == 0) { + continue; + } + + if (strncasecmp("x-cache-key", value, vsize) == 0) { + xheaders |= XHEADER_X_CACHE_KEY; + } else if (strncasecmp("via", value, vsize) == 0) { + // If the client requests the Via header, enable verbose Via debugging for this transaction. + TSHttpTxnConfigIntSet(txn, TS_CONFIG_HTTP_INSERT_RESPONSE_VIA_STR, 3); + } else { + TSDebug("xdebug", "ignoring unrecognized debug tag '%.*s'", vsize, value); + } + } + + // Get the next duplicate. + next = TSMimeHdrFieldNextDup(buffer, hdr, field); + + // Destroy the current field that we have. We don't want this to go through and potentially confuse the origin. + TSMimeHdrFieldRemove(buffer, hdr, field); + TSMimeHdrFieldDestroy(buffer, hdr, field); + + // Now release our reference. + TSHandleMLocRelease(buffer, hdr, field); + + // And go to the next field. + field = next; + } + + if (xheaders) { + TSHttpTxnHookAdd(txn, TS_HTTP_SEND_RESPONSE_HDR_HOOK, XInjectHeadersCont); + TSHttpTxnArgSet(txn, XArgIndex, (void *)xheaders); + } + +done: + TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE); + return TS_EVENT_NONE; +} + +void +TSPluginInit(int argc, const char *argv[]) +{ + TSPluginRegistrationInfo info; + + info.plugin_name = (char *)"xdebug"; + info.vendor_name = (char *)"Apache Software Foundation"; + info.support_email = (char *)"d...@trafficserver.apache.org"; + + if (TSPluginRegister(TS_SDK_VERSION_3_0, &info) != TS_SUCCESS) { + TSError("xdebug plugin registration failed"); + } + + TSReleaseAssert( + TSHttpArgIndexReserve("xdebug", "xdebug header requests" , &XArgIndex) == TS_SUCCESS + ); + + TSReleaseAssert( + XInjectHeadersCont = TSContCreate(XInjectResponseHeaders, NULL) + ); + + TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, TSContCreate(XScanRequestHeaders, NULL)); +} + +// vim: set ts=2 sw=2 et :