TS-2554 New plugin to performance background fetch on Range responses
Project: http://git-wip-us.apache.org/repos/asf/trafficserver/repo Commit: http://git-wip-us.apache.org/repos/asf/trafficserver/commit/07ebb012 Tree: http://git-wip-us.apache.org/repos/asf/trafficserver/tree/07ebb012 Diff: http://git-wip-us.apache.org/repos/asf/trafficserver/diff/07ebb012 Branch: refs/heads/master Commit: 07ebb0125de8e7ea2827239c19a459c0f47bea4a Parents: 0eb46cf Author: Leif Hedstrom <[email protected]> Authored: Mon Mar 24 16:19:16 2014 -0600 Committer: Leif Hedstrom <[email protected]> Committed: Thu Mar 27 11:10:18 2014 -0600 ---------------------------------------------------------------------- configure.ac | 1 + doc/reference/plugins/background_fetch.en.rst | 73 +++ doc/reference/plugins/index.en.rst | 1 + plugins/experimental/Makefile.am | 5 +- .../experimental/background_fetch/Makefile.am | 21 + .../background_fetch/background_fetch.cc | 521 +++++++++++++++++++ 6 files changed, 620 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/trafficserver/blob/07ebb012/configure.ac ---------------------------------------------------------------------- diff --git a/configure.ac b/configure.ac index 6883cb6..79da351 100644 --- a/configure.ac +++ b/configure.ac @@ -1978,6 +1978,7 @@ AC_CONFIG_FILES([ plugins/stats_over_http/Makefile plugins/experimental/Makefile plugins/experimental/authproxy/Makefile + plugins/experimental/background_fetch/Makefile plugins/experimental/balancer/Makefile plugins/experimental/buffer_upload/Makefile plugins/experimental/channel_stats/Makefile http://git-wip-us.apache.org/repos/asf/trafficserver/blob/07ebb012/doc/reference/plugins/background_fetch.en.rst ---------------------------------------------------------------------- diff --git a/doc/reference/plugins/background_fetch.en.rst b/doc/reference/plugins/background_fetch.en.rst new file mode 100644 index 0000000..3df7d3c --- /dev/null +++ b/doc/reference/plugins/background_fetch.en.rst @@ -0,0 +1,73 @@ +.. _background-fetch-plugin: + +Background Fetch Plugin +*********************** + +.. 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 is a plugin for Apache Traffic Server that allows you to proactively +fetch content from Origin in a way that it will fill the object into +cache. This is particularly useful when all (or most) of your client requests +are of the byte-Range type. The underlying problem being that Traffic Server +is not able to cache request / responses with byte ranges. + +Using the plugin +---------------- + +This plugin currently only functions as a global plugin, and it takes not +arguments or parameters. In :file:`plugin.config`, simply add:: + + background_fetch.so + + +Functionality +------------- + +Examining the responses from origin, we decide to trigger a background fetch +of the original (Client) request under these conditions: + +- The request is a ``GET`` request (we only support these right now) +- The response is a ``206`` response +- The original client request, and the Origin server response, is clearly + indicating that the response is cacheable. This uses the new API + c:func:`TSHttpTxnIsCacheable()`, which also implies honoring current + Traffic Server configurations. + + +Once deemed a good candidate to performance a background fetch, we'll replay +the original client request through the Traffic Server proxy again, except +this time eliminating the ``Range`` header. This is transparent to the +original client request, which continues as normal. + +Only one background fetch per URL is ever performed, making sure we do not +accidentally put pressure on the origin servers. + + + +Future additions +---------------- + +The infrastructure is in place for providing global and per-remap +configurations. This could include: + +- Limiting the background fetches to certain Content-Types +- Limiting the background fetches to content of certain sizes + + +None of this is currently not implemented. http://git-wip-us.apache.org/repos/asf/trafficserver/blob/07ebb012/doc/reference/plugins/index.en.rst ---------------------------------------------------------------------- diff --git a/doc/reference/plugins/index.en.rst b/doc/reference/plugins/index.en.rst index 275411d..669b8d2 100644 --- a/doc/reference/plugins/index.en.rst +++ b/doc/reference/plugins/index.en.rst @@ -63,6 +63,7 @@ directory of the Apache Traffic Server source tree. Exmperimental plugins can be :maxdepth: 1 authproxy.en + background_fetch.en balancer.en buffer_upload.en combo_handler.en http://git-wip-us.apache.org/repos/asf/trafficserver/blob/07ebb012/plugins/experimental/Makefile.am ---------------------------------------------------------------------- diff --git a/plugins/experimental/Makefile.am b/plugins/experimental/Makefile.am index d961002..a2c5a37 100644 --- a/plugins/experimental/Makefile.am +++ b/plugins/experimental/Makefile.am @@ -18,16 +18,17 @@ if BUILD_EXPERIMENTAL_PLUGINS SUBDIRS = \ authproxy \ + background_fetch \ balancer \ - escalate \ buffer_upload \ channel_stats \ custom_redirect \ + escalate \ esi \ geoip_acl \ healthchecks \ - lua \ hipes \ + lua \ metalink \ remap_stats \ rfc5861 \ http://git-wip-us.apache.org/repos/asf/trafficserver/blob/07ebb012/plugins/experimental/background_fetch/Makefile.am ---------------------------------------------------------------------- diff --git a/plugins/experimental/background_fetch/Makefile.am b/plugins/experimental/background_fetch/Makefile.am new file mode 100644 index 0000000..fef8d44 --- /dev/null +++ b/plugins/experimental/background_fetch/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 = background_fetch.la +background_fetch_la_SOURCES = background_fetch.cc +background_fetch_la_LDFLAGS = $(TS_PLUGIN_LDFLAGS) http://git-wip-us.apache.org/repos/asf/trafficserver/blob/07ebb012/plugins/experimental/background_fetch/background_fetch.cc ---------------------------------------------------------------------- diff --git a/plugins/experimental/background_fetch/background_fetch.cc b/plugins/experimental/background_fetch/background_fetch.cc new file mode 100644 index 0000000..dc895f3 --- /dev/null +++ b/plugins/experimental/background_fetch/background_fetch.cc @@ -0,0 +1,521 @@ +/** @file + + Plugin to perform background fetches of certain content that would + otherwise not be cached. For example, Range: requests / responses. + + @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. + +*/ + + + +#include <stdio.h> +#include <string.h> +#include <string> + +#include <netinet/in.h> +#include <arpa/inet.h> + +#include "ts/ts.h" +#include "ts/remap.h" +#include "ink_defs.h" + + +// Some wonkiness around compiler version and the unordered map (hash) +#if HAVE_UNORDERED_MAP +# include <unordered_map> + typedef std::unordered_map<std::string, bool> OutstandingRequests; +#else +# include <map> + typedef std::map<std::string, bool> OutstandingRequests; +#endif + +// Constants +const char PLUGIN_NAME[] = "background_fetch"; + + +/////////////////////////////////////////////////////////////////////////// +// Remove a header (fully) from an TSMLoc / TSMBuffer. Return the number +// of fields (header values) we removed. +int +remove_header(TSMBuffer bufp, TSMLoc hdr_loc, const char* header, int len) +{ + TSMLoc field = TSMimeHdrFieldFind(bufp, hdr_loc, header, len); + int c = 0; + + while (field) { + ++c; + TSMLoc tmp = TSMimeHdrFieldNextDup(bufp, hdr_loc, field); + + TSMimeHdrFieldDestroy(bufp, hdr_loc, field); + TSHandleMLocRelease(bufp, hdr_loc, field); + field = tmp; + } + + return c; +} + +/////////////////////////////////////////////////////////////////////////// +// 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. +bool +set_header(TSMBuffer bufp, TSMLoc hdr_loc, const char* header, int len, const char* val, int val_len) +{ + if (!bufp || !hdr_loc || !header || len <= 0 || !val || val_len <= 0) { + return false; + } + + 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; + } + } + + return ret; +} + + +/////////////////////////////////////////////////////////////////////////// +// Dump a header on stderr, useful together with TSDebug(). +void +dump_headers(TSMBuffer bufp, TSMLoc hdr_loc) +{ + TSIOBuffer output_buffer; + TSIOBufferReader reader; + TSIOBufferBlock block; + const char* block_start; + int64_t block_avail; + + output_buffer = TSIOBufferCreate(); + reader = TSIOBufferReaderAlloc(output_buffer); + + /* This will print just MIMEFields and not the http request line */ + TSMimeHdrPrint(bufp, hdr_loc, output_buffer); + + /* We need to loop over all the buffer blocks, there can be more than 1 */ + block = TSIOBufferReaderStart(reader); + do { + block_start = TSIOBufferBlockReadStart(block, reader, &block_avail); + fwrite(block_start, block_avail, 1, stderr); + TSIOBufferReaderConsume(reader, block_avail); + block = TSIOBufferReaderStart(reader); + } while (block && block_avail != 0); + + /* Free up the TSIOBuffer that we used to print out the header */ + TSIOBufferReaderFree(reader); + TSIOBufferDestroy(output_buffer); +} + + +/////////////////////////////////////////////////////////////////////////// +// Struct to hold configurations and state. This can be global, or per +// remap rule. This also holds the list of currently outstanding URLs, +// such that we can avoid sending more than one background fill per URL at +// any given time. +class BGFetchConfig { +public: + BGFetchConfig() + { + _lock = TSMutexCreate(); + } + + ~BGFetchConfig() + { + // ToDo: Destroy mutex ? TS-1432 + } + + bool acquire(const std::string &url) + { + bool ret; + + TSMutexLock(_lock); + if (_urls.end() == _urls.find(url)) { + _urls[url] = true; + ret = true; + } else { + ret = false; + } + TSMutexUnlock(_lock); + + return ret; + } + + bool release(const std::string &url) + { + bool ret; + + TSMutexLock(_lock); + if (_urls.end() == _urls.find(url)) { + ret = false; + } else { + _urls.erase(url); + ret = true; + } + TSMutexUnlock(_lock); + + return ret; + } + +private: + OutstandingRequests _urls; + TSMutex _lock; +}; + +BGFetchConfig gConfig; + +////////////////////////////////////////////////////////////////////////////// +// Hold and manage some state for the background fetch continuation +// This is necessary, because the TXN is likely to not be available +// during the time we fetch from origin. +static int bg_fetch_cont(TSCont contp, TSEvent event, void* edata); + +struct BGFetchData +{ + BGFetchData(BGFetchConfig* cfg=&gConfig) + : hdr_loc(TS_NULL_MLOC), url_loc(TS_NULL_MLOC), _config(cfg) + { + mbuf = TSMBufferCreate(); + } + + ~BGFetchData() + { + release_url(); + + TSHandleMLocRelease(mbuf, TS_NULL_MLOC, hdr_loc); + TSHandleMLocRelease(mbuf, TS_NULL_MLOC, url_loc); + + TSMBufferDestroy(mbuf); + + // If we got schedule, also clean that up + if (_cont) { + TSContDestroy(_cont); + + TSIOBufferReaderFree(req_io_buf_reader); + TSIOBufferDestroy(req_io_buf); + TSIOBufferReaderFree(resp_io_buf_reader); + TSIOBufferDestroy(resp_io_buf); + } + } + + bool acquire_url() const { return _config->acquire(_url); } + bool release_url() const { return _config->release(_url); } + + const char* get_url() const { return _url.c_str(); } + + bool initialize(TSMBuffer request, TSMLoc req_hdr, TSHttpTxn txnp); + void schedule(); + + TSMBuffer mbuf; + TSMLoc hdr_loc; + TSMLoc url_loc; + struct sockaddr_storage client_ip; + + // This is for the actual background fetch / NetVC + TSVConn vc; + TSIOBuffer req_io_buf, resp_io_buf; + TSIOBufferReader req_io_buf_reader, resp_io_buf_reader; + TSVIO r_vio, w_vio; + +private: + std::string _url; + TSCont _cont; + BGFetchConfig* _config; +}; + + +// This sets up the data and continuation properly, this is done outside +// of the CTor, since this can actually fail. If we fail, the data is +// useless, and should be delete'd. +// +// This needs the txnp temporarily, so it can copy the pristine request +// URL. The txnp is not used once initialize() returns. +// +// Upon succesful completion, the struct should be ready to start a +// background fetch. +bool +BGFetchData::initialize(TSMBuffer request, TSMLoc req_hdr, TSHttpTxn txnp) +{ + TSReleaseAssert(TS_NULL_MLOC == hdr_loc); + TSReleaseAssert(TS_NULL_MLOC == url_loc); + struct sockaddr const* ip = TSHttpTxnClientAddrGet(txnp); + + if (ip) { + if (ip->sa_family == AF_INET) { + memcpy(&client_ip, ip, sizeof(sockaddr_in)); + } else if (ip->sa_family == AF_INET6) { + memcpy(&client_ip, ip, sizeof(sockaddr_in6)); + } else { + TSError("%s: Unknown address family %d", PLUGIN_NAME, ip->sa_family); + } + } else { + TSError("%s: failed to get client host info", PLUGIN_NAME); + return false; + } + + hdr_loc = TSHttpHdrCreate(mbuf); + if (TS_SUCCESS == TSHttpHdrCopy(mbuf, hdr_loc, request, req_hdr)) { + TSMLoc purl; + int len; + + // Now copy the pristine request URL into our MBuf + if ((TS_SUCCESS == TSHttpTxnPristineUrlGet(txnp, &request, &purl)) && + (TS_SUCCESS == TSUrlClone(mbuf, request, purl, &url_loc))) { + char* url = TSUrlStringGet(mbuf, url_loc, &len); + + _url.append(url, len); // Save away the URL for later use when acquiring lock + TSfree(static_cast<void*>(url)); + + if (TS_SUCCESS == TSHttpHdrUrlSet(mbuf, hdr_loc, url_loc)) { + // Make sure we have the correct Host: header for this request. + const char *hostp = TSUrlHostGet(mbuf, url_loc, &len); + + if (set_header(mbuf, hdr_loc, TS_MIME_FIELD_HOST, TS_MIME_LEN_HOST, hostp, len)) { + TSDebug(PLUGIN_NAME, "Set header Host: %.*s", len, hostp); + } + + // Next, remove any Range: headers from our request. + if (remove_header(mbuf, hdr_loc, TS_MIME_FIELD_RANGE, TS_MIME_LEN_RANGE) > 0) { + TSDebug(PLUGIN_NAME, "Removed the Range: header from request"); + } + + return true; + } + } + } + + // Something failed. + return false; +} + + +// Create, setup and schedule the background fetch continuation. +void +BGFetchData::schedule() +{ + // Setup the continuation + _cont = TSContCreate(bg_fetch_cont, NULL); + TSContDataSet(_cont, static_cast<void*>(this)); + + // Initialize the VIO stuff (for the fetch) + req_io_buf = TSIOBufferCreate(); + req_io_buf_reader = TSIOBufferReaderAlloc(req_io_buf); + resp_io_buf = TSIOBufferCreate(); + resp_io_buf_reader = TSIOBufferReaderAlloc(resp_io_buf); + + // Schedule + TSContSchedule(_cont, 0, TS_THREAD_POOL_NET); +} + + +////////////////////////////////////////////////////////////////////////////// +// Continuation to perform a background fill of a URL. This is pretty +// expensive (memory allocations etc.), we could eliminate maybe the +// std::string, but I think it's fine for now. +static int +bg_fetch_cont(TSCont contp, TSEvent event, void* /* edata ATS_UNUSED */) +{ + BGFetchData* data = static_cast<BGFetchData*>(TSContDataGet(contp)); + int64_t avail; + + switch (event) { + case TS_EVENT_IMMEDIATE: + case TS_EVENT_TIMEOUT: + // Debug info for this particular bg fetch (put all debug in here please) + if (TSIsDebugTagSet(PLUGIN_NAME)) { + char buf[INET6_ADDRSTRLEN]; + const sockaddr* sockaddress = (const sockaddr*)&data->client_ip; + + switch (sockaddress->sa_family) { + case AF_INET: + inet_ntop(AF_INET, &(((struct sockaddr_in *) sockaddress)->sin_addr), buf, INET_ADDRSTRLEN); + TSDebug(PLUGIN_NAME, "Client IPv4 = %s", buf); + break; + case AF_INET6: + inet_ntop(AF_INET6, &(((struct sockaddr_in6 *) sockaddress)->sin6_addr), buf, INET6_ADDRSTRLEN); + TSDebug(PLUGIN_NAME, "Client IPv6 = %s", buf); + break; + default: + TSError("%s: Unknown address family %d", PLUGIN_NAME, sockaddress->sa_family); + break; + } + TSDebug(PLUGIN_NAME, "Starting bg fetch on: %s", data->get_url()); + dump_headers(data->mbuf, data->hdr_loc); + } + + // Setup the NetVC for background fetch + if ((data->vc = TSHttpConnect((sockaddr*)&data->client_ip)) != NULL) { + TSHttpHdrPrint(data->mbuf, data->hdr_loc, data->req_io_buf); + // We never send a body with the request. ToDo: Do we ever need to support that ? + TSIOBufferWrite(data->req_io_buf, "\r\n", 2); + + data->r_vio = TSVConnRead(data->vc, contp, data->resp_io_buf, INT64_MAX); + data->w_vio = TSVConnWrite(data->vc, contp, data->req_io_buf_reader, TSIOBufferReaderAvail(data->req_io_buf_reader)); + } else { + delete data; + TSError("%s: failed to connect to internal process, major malfunction", PLUGIN_NAME); + } + + case TS_EVENT_VCONN_WRITE_COMPLETE: + TSDebug(PLUGIN_NAME, "Write Complete"); + break; + + case TS_EVENT_VCONN_READ_READY: + avail = TSIOBufferReaderAvail(data->resp_io_buf_reader); + TSIOBufferReaderConsume(data->resp_io_buf_reader, avail); + TSVIONDoneSet(data->r_vio, TSVIONDoneGet(data->r_vio) + avail); + TSVIOReenable(data->r_vio); + break; + + case TS_EVENT_VCONN_READ_COMPLETE: + case TS_EVENT_VCONN_EOS: + case TS_EVENT_VCONN_INACTIVITY_TIMEOUT: + if (event == TS_EVENT_VCONN_INACTIVITY_TIMEOUT) { + TSDebug(PLUGIN_NAME, "Encountered Inactivity Timeout"); + TSVConnAbort(data->vc, TS_VC_CLOSE_ABORT); + } else { + TSVConnClose(data->vc); + } + + // ToDo: Is this really necessary to do here for all 3 cases? + TSDebug(PLUGIN_NAME, "Closing down background transaction, event=%d", event); + avail = TSIOBufferReaderAvail(data->resp_io_buf_reader); + TSIOBufferReaderConsume(data->resp_io_buf_reader, avail); + TSVIONDoneSet(data->r_vio, TSVIONDoneGet(data->r_vio) + avail); + + // Release and Cleanup + delete data; + break; + + default: + TSDebug(PLUGIN_NAME, "Unhandled event: %d", event); // ToDo: use new API in v5.0.0 + break; + } + + return 0; +} + +////////////////////////////////////////////////////////////////////////////// +// Main "plugin". +// +static int +cont_handle_response(TSCont /* contp ATS_UNUSED */, TSEvent /* event ATS_UNUSED */, void* edata) +{ + // ToDo: If we want to support per-remap configurations, we have to pass along the data here + TSHttpTxn txnp = static_cast<TSHttpTxn>(edata); + + TSDebug(PLUGIN_NAME, "Testing: request is internal?"); + if (TSHttpIsInternalRequest(txnp) != TS_SUCCESS) { + TSMBuffer request; + TSMLoc req_hdr; + + if (TS_SUCCESS == TSHttpTxnClientReqGet(txnp, &request, &req_hdr)) { + int method_len; + const char* method = TSHttpHdrMethodGet(request, req_hdr, &method_len); + + // Make sure it's not an internal request first, and then examine the Origin server response + TSDebug(PLUGIN_NAME, "Testing: request is a GET?"); + if (TS_HTTP_METHOD_GET == method) { + TSMBuffer response; + TSMLoc resp_hdr; + + if (TS_SUCCESS == TSHttpTxnServerRespGet(txnp, &response, &resp_hdr)) { + // ToDo: Check the MIME type first, to see if it's a type we care about. + // ToDo: Such MIME types should probably be per remap rule. + + // Only deal with 206 responses on a GET request (partial content), anything else is irrelevant + TSDebug(PLUGIN_NAME, "Testing: response is 206?"); + if (TS_HTTP_STATUS_PARTIAL_CONTENT == TSHttpHdrStatusGet(response, resp_hdr)) { + // Temporarily change the response status to 200 OK, so we can reevaluate cacheability. + TSHttpHdrStatusSet(response, resp_hdr, TS_HTTP_STATUS_OK); + bool cacheable = TSHttpTxnIsCacheable(txnp, NULL, response); + TSHttpHdrStatusSet(response, resp_hdr, TS_HTTP_STATUS_PARTIAL_CONTENT); + + TSDebug(PLUGIN_NAME, "Testing: request / response is cacheable?"); + if (cacheable) { + BGFetchData* data = new BGFetchData(); + + // Initialize the data structure (can fail) and acquire a privileged lock on the URL + if (data->initialize(request, req_hdr, txnp) && data->acquire_url()) { + // We schedule this in about 200ms, that gives another request / response + // a chance to start before us. + data->schedule(); + } else { + delete data; + } + } + } + // Release the response MLoc + TSHandleMLocRelease(response, TS_NULL_MLOC, resp_hdr); + } + } + // Release the request MLoc + TSHandleMLocRelease(request, TS_NULL_MLOC, req_hdr); + } + } + + // Reenable and continue with the state machine. + TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); + return 0; +} + + +/////////////////////////////////////////////////////////////////////////// +// Setup global hooks +void +TSPluginInit(int /* argc ATS_UNUSED */, const char* /* argv ATS_UNUSED */[]) +{ + TSPluginRegistrationInfo info; + + info.plugin_name = (char*)PLUGIN_NAME; + info.vendor_name = (char*)"Apache Software Foundation"; + info.support_email = (char*)"[email protected]"; + + if (TS_SUCCESS != TSPluginRegister(TS_SDK_VERSION_3_0 , &info)) { + TSError("%s: plugin registration failed.\n", PLUGIN_NAME); + } + + TSDebug(PLUGIN_NAME, "Initialized"); + TSHttpHookAdd(TS_HTTP_READ_RESPONSE_HDR_HOOK, TSContCreate(cont_handle_response, NULL)); +}
