| /* |
| * 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 across different disk drives reducing I/O |
| * wait and load averages when there are large numbers of range |
| * requests. |
| */ |
| |
| #include "ts/ts.h" |
| #include "ts/remap.h" |
| |
| #include <cinttypes> |
| #include <cstdio> |
| #include <cstring> |
| #include <getopt.h> |
| #include <string> |
| #include <string_view> |
| |
| #define PLUGIN_NAME "cache_range_requests" |
| #define DEBUG_LOG(fmt, ...) TSDebug(PLUGIN_NAME, "[%s:%d] %s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) |
| #define ERROR_LOG(fmt, ...) TSError("[%s:%d] %s(): " fmt, __FILE__, __LINE__, __func__, ##__VA_ARGS__) |
| |
| namespace |
| { |
| typedef enum parent_select_mode { |
| PS_DEFAULT, // Default ATS parent selection mode |
| PS_CACHEKEY_URL, // Set parent selection url to cache_key url |
| } parent_select_mode_t; |
| |
| struct pluginconfig { |
| parent_select_mode_t ps_mode{PS_DEFAULT}; |
| bool consider_ims_header{false}; |
| bool modify_cache_key{true}; |
| }; |
| |
| struct txndata { |
| std::string range_value; |
| time_t ims_time{0}; |
| }; |
| |
| // Header for optional revalidation |
| constexpr std::string_view X_IMS_HEADER = {"X-Crr-Ims"}; |
| |
| // pluginconfig struct (global plugin only) |
| pluginconfig *gPluginConfig = {nullptr}; |
| |
| int handle_read_request_header(TSCont, TSEvent, void *); |
| void range_header_check(TSHttpTxn, pluginconfig *const); |
| void handle_send_origin_request(TSCont, TSHttpTxn, txndata *const); |
| void handle_client_send_response(TSHttpTxn, txndata *const); |
| void handle_server_read_response(TSHttpTxn, txndata *const); |
| int remove_header(TSMBuffer, TSMLoc, const char *, int); |
| bool set_header(TSMBuffer, TSMLoc, const char *, int, const char *, int); |
| int transaction_handler(TSCont, TSEvent, void *); |
| struct pluginconfig *create_pluginconfig(int argc, char *const argv[]); |
| void delete_pluginconfig(pluginconfig *const); |
| |
| /** |
| * Creates pluginconfig data structure |
| * Sets default parent url selection mode |
| * Walk plugin argument list and updates config |
| */ |
| pluginconfig * |
| create_pluginconfig(int argc, char *const argv[]) |
| { |
| DEBUG_LOG("Number of arguments: %d", argc); |
| for (int index = 0; index < argc; ++index) { |
| DEBUG_LOG("args[%d] = %s", index, argv[index]); |
| } |
| |
| pluginconfig *const pc = new pluginconfig; |
| |
| if (nullptr == pc) { |
| ERROR_LOG("Can't allocate pluginconfig"); |
| return nullptr; |
| } |
| |
| static const struct option longopts[] = { |
| {const_cast<char *>("ps-cachekey"), no_argument, nullptr, 'p'}, |
| {const_cast<char *>("consider-ims"), no_argument, nullptr, 'c'}, |
| {const_cast<char *>("no-modify-cachekey"), no_argument, nullptr, 'n'}, |
| {nullptr, 0, nullptr, 0}, |
| }; |
| |
| // getopt assumes args start at '1' |
| ++argc; |
| --argv; |
| |
| for (;;) { |
| int const opt = getopt_long(argc, argv, "", longopts, nullptr); |
| if (-1 == opt) { |
| break; |
| } |
| |
| switch (opt) { |
| case 'p': { |
| DEBUG_LOG("Plugin modifies parent selection key"); |
| pc->ps_mode = PS_CACHEKEY_URL; |
| } break; |
| case 'c': { |
| DEBUG_LOG("Plugin considers the '%.*s' header", (int)X_IMS_HEADER.size(), X_IMS_HEADER.data()); |
| pc->consider_ims_header = true; |
| } break; |
| case 'n': { |
| DEBUG_LOG("Plugin doesn't modify cache key"); |
| pc->modify_cache_key = false; |
| } break; |
| default: { |
| } break; |
| } |
| } |
| |
| // Backwards compatibility |
| if (optind < argc && 0 == strcmp("ps_mode:cache_key_url", argv[optind])) { |
| DEBUG_LOG("Plugin modifies parent selection key (deprecated)"); |
| pc->ps_mode = PS_CACHEKEY_URL; |
| } |
| |
| return pc; |
| } |
| |
| /** |
| * Destroy pluginconfig data structure. |
| */ |
| void |
| delete_pluginconfig(pluginconfig *const pc) |
| { |
| if (nullptr != pc) { |
| DEBUG_LOG("Delete struct pluginconfig"); |
| delete pc; |
| } |
| } |
| |
| /** |
| * Entry point when used as a global plugin. |
| */ |
| int |
| handle_read_request_header(TSCont txn_contp, TSEvent event, void *edata) |
| { |
| TSHttpTxn txnp = static_cast<TSHttpTxn>(edata); |
| |
| range_header_check(txnp, gPluginConfig); |
| |
| TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); |
| return 0; |
| } |
| |
| /** |
| * 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. |
| */ |
| void |
| range_header_check(TSHttpTxn txnp, pluginconfig *const pc) |
| { |
| char cache_key_url[8192] = {0}; |
| char *req_url; |
| int length, url_length, cache_key_url_length; |
| txndata *txn_state; |
| TSMBuffer hdr_buf; |
| TSMLoc hdr_loc = nullptr; |
| TSMLoc loc = nullptr; |
| TSCont txn_contp; |
| |
| if (TS_SUCCESS == TSHttpTxnClientReqGet(txnp, &hdr_buf, &hdr_loc)) { |
| loc = TSMimeHdrFieldFind(hdr_buf, hdr_loc, TS_MIME_FIELD_RANGE, TS_MIME_LEN_RANGE); |
| if (TS_NULL_MLOC != loc) { |
| const char *hdr_value = TSMimeHdrFieldValueStringGet(hdr_buf, hdr_loc, loc, 0, &length); |
| TSHandleMLocRelease(hdr_buf, hdr_loc, loc); |
| |
| if (!hdr_value || length <= 0) { |
| DEBUG_LOG("Not a range request."); |
| } else { |
| if (nullptr == (txn_contp = TSContCreate(static_cast<TSEventFunc>(transaction_handler), nullptr))) { |
| ERROR_LOG("failed to create the transaction handler continuation."); |
| } else { |
| txn_state = new txndata; |
| std::string &rv = txn_state->range_value; |
| rv.assign(hdr_value, length); |
| DEBUG_LOG("length: %d, txn_state->range_value: %s", length, rv.c_str()); |
| req_url = TSHttpTxnEffectiveUrlStringGet(txnp, &url_length); |
| cache_key_url_length = snprintf(cache_key_url, 8192, "%s-%s", req_url, rv.c_str()); |
| DEBUG_LOG("Rewriting cache URL for %s to %s", req_url, cache_key_url); |
| if (req_url != nullptr) { |
| TSfree(req_url); |
| } |
| |
| if (nullptr != pc) { |
| // set the cache key if configured to. |
| if (pc->modify_cache_key && TS_SUCCESS != TSCacheUrlSet(txnp, cache_key_url, cache_key_url_length)) { |
| ERROR_LOG("failed to change the cache url to %s.", cache_key_url); |
| ERROR_LOG("Disabling cache for this transaction to avoid cache poisoning."); |
| TSHttpTxnServerRespNoStoreSet(txnp, 1); |
| TSHttpTxnRespCacheableSet(txnp, 0); |
| TSHttpTxnReqCacheableSet(txnp, 0); |
| } |
| |
| // Optionally set the parent_selection_url to the cache_key url or path |
| if (PS_DEFAULT != pc->ps_mode) { |
| TSMLoc ps_loc = nullptr; |
| |
| if (PS_CACHEKEY_URL == pc->ps_mode) { |
| const char *start = cache_key_url; |
| const char *end = cache_key_url + cache_key_url_length; |
| if (TS_SUCCESS == TSUrlCreate(hdr_buf, &ps_loc) && |
| TS_PARSE_DONE == TSUrlParse(hdr_buf, ps_loc, &start, end) && // This should always succeed. |
| TS_SUCCESS == TSHttpTxnParentSelectionUrlSet(txnp, hdr_buf, ps_loc)) { |
| DEBUG_LOG("Set Parent Selection URL to cache_key_url: %s", cache_key_url); |
| TSHandleMLocRelease(hdr_buf, TS_NULL_MLOC, ps_loc); |
| } |
| } |
| } |
| |
| // optionally consider an X-CRR-IMS header |
| if (pc->consider_ims_header) { |
| TSMLoc const imsloc = TSMimeHdrFieldFind(hdr_buf, hdr_loc, X_IMS_HEADER.data(), X_IMS_HEADER.size()); |
| if (TS_NULL_MLOC != imsloc) { |
| time_t const itime = TSMimeHdrFieldValueDateGet(hdr_buf, hdr_loc, imsloc); |
| DEBUG_LOG("Servicing the '%.*s' header", (int)X_IMS_HEADER.size(), X_IMS_HEADER.data()); |
| TSHandleMLocRelease(hdr_buf, hdr_loc, imsloc); |
| if (0 < itime) { |
| txn_state->ims_time = itime; |
| } |
| } |
| } |
| } |
| |
| // remove the range request header. |
| if (remove_header(hdr_buf, hdr_loc, TS_MIME_FIELD_RANGE, TS_MIME_LEN_RANGE) > 0) { |
| DEBUG_LOG("Removed the Range: header from the 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); |
| DEBUG_LOG("Added TS_HTTP_SEND_REQUEST_HDR_HOOK, TS_HTTP_SEND_RESPONSE_HDR_HOOK, and TS_HTTP_TXN_CLOSE_HOOK"); |
| |
| if (0 < txn_state->ims_time) { |
| TSHttpTxnHookAdd(txnp, TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, txn_contp); |
| DEBUG_LOG("Also Added TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK"); |
| } |
| } |
| } |
| // TSHandleMLocRelease(hdr_buf, hdr_loc, loc); |
| } else { |
| DEBUG_LOG("no range request header."); |
| } |
| TSHandleMLocRelease(hdr_buf, TS_NULL_MLOC, hdr_loc); |
| } else { |
| DEBUG_LOG("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. |
| */ |
| void |
| handle_send_origin_request(TSCont contp, TSHttpTxn txnp, txndata *const txn_state) |
| { |
| TSMBuffer hdr_buf; |
| TSMLoc hdr_loc = nullptr; |
| |
| std::string const &rv = txn_state->range_value; |
| |
| if (TS_SUCCESS == TSHttpTxnServerReqGet(txnp, &hdr_buf, &hdr_loc) && !rv.empty()) { |
| if (set_header(hdr_buf, hdr_loc, TS_MIME_FIELD_RANGE, TS_MIME_LEN_RANGE, rv.data(), rv.length())) { |
| DEBUG_LOG("Added range header: %s", rv.c_str()); |
| TSHttpTxnHookAdd(txnp, TS_HTTP_READ_RESPONSE_HDR_HOOK, contp); |
| } |
| } |
| TSHandleMLocRelease(hdr_buf, TS_NULL_MLOC, hdr_loc); |
| } |
| |
| /** |
| * Changes the response code back to a 206 Partial content before |
| * replying to the client that requested a range. |
| */ |
| void |
| handle_client_send_response(TSHttpTxn txnp, txndata *const txn_state) |
| { |
| bool partial_content_reason = false; |
| char *p; |
| int length; |
| TSMBuffer resp_buf = nullptr; |
| TSMBuffer req_buf = nullptr; |
| TSMLoc resp_loc = nullptr; |
| TSMLoc req_loc = nullptr; |
| |
| TSReturnCode result = TSHttpTxnClientRespGet(txnp, &resp_buf, &resp_loc); |
| DEBUG_LOG("result: %d", result); |
| if (TS_SUCCESS == result) { |
| TSHttpStatus status = TSHttpHdrStatusGet(resp_buf, resp_loc); |
| // a cached result will have a TS_HTTP_OK with a 'Partial Content' reason |
| if ((p = const_cast<char *>(TSHttpHdrReasonGet(resp_buf, resp_loc, &length))) != nullptr) { |
| if ((length == 15) && (0 == strncasecmp(p, "Partial Content", length))) { |
| partial_content_reason = true; |
| } |
| } |
| DEBUG_LOG("%d %.*s", status, length, p); |
| if (TS_HTTP_STATUS_OK == status && partial_content_reason) { |
| DEBUG_LOG("Got TS_HTTP_STATUS_OK."); |
| TSHttpHdrStatusSet(resp_buf, resp_loc, TS_HTTP_STATUS_PARTIAL_CONTENT); |
| DEBUG_LOG("Set response header to TS_HTTP_STATUS_PARTIAL_CONTENT."); |
| } |
| } |
| std::string const &rv = txn_state->range_value; |
| // add the range request header back in so that range requests may be logged. |
| if (TS_SUCCESS == TSHttpTxnClientReqGet(txnp, &req_buf, &req_loc) && !rv.empty()) { |
| if (set_header(req_buf, req_loc, TS_MIME_FIELD_RANGE, TS_MIME_LEN_RANGE, rv.data(), rv.length())) { |
| DEBUG_LOG("added range header: %s", rv.c_str()); |
| } else { |
| DEBUG_LOG("set_header() failed."); |
| } |
| } else { |
| DEBUG_LOG("failed to get Request Headers"); |
| } |
| TSHandleMLocRelease(resp_buf, TS_NULL_MLOC, resp_loc); |
| TSHandleMLocRelease(req_buf, TS_NULL_MLOC, req_loc); |
| } |
| |
| /** |
| * 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. |
| */ |
| void |
| handle_server_read_response(TSHttpTxn txnp, txndata *const txn_state) |
| { |
| TSMBuffer resp_buf = nullptr; |
| TSMLoc resp_loc = nullptr; |
| TSHttpStatus status; |
| |
| if (TS_SUCCESS == TSHttpTxnServerRespGet(txnp, &resp_buf, &resp_loc)) { |
| status = TSHttpHdrStatusGet(resp_buf, resp_loc); |
| if (TS_HTTP_STATUS_PARTIAL_CONTENT == status) { |
| DEBUG_LOG("Got TS_HTTP_STATUS_PARTIAL_CONTENT."); |
| TSHttpHdrStatusSet(resp_buf, resp_loc, TS_HTTP_STATUS_OK); |
| DEBUG_LOG("Set response header to TS_HTTP_STATUS_OK."); |
| bool cacheable = TSHttpTxnIsCacheable(txnp, nullptr, resp_buf); |
| DEBUG_LOG("range is cacheable: %d", cacheable); |
| } else if (TS_HTTP_STATUS_OK == status) { |
| DEBUG_LOG("The origin does not support range requests, attempting to disable cache write."); |
| if (TS_SUCCESS == TSHttpTxnServerRespNoStoreSet(txnp, 1)) { |
| DEBUG_LOG("Cache write has been disabled for this transaction."); |
| } else { |
| DEBUG_LOG("Unable to disable cache write for this transaction."); |
| } |
| } |
| } |
| TSHandleMLocRelease(resp_buf, TS_NULL_MLOC, resp_loc); |
| } |
| |
| /** |
| * Remove a header (fully) from an TSMLoc / TSMBuffer. Return the number |
| * of fields (header values) we removed. |
| * |
| * From background_fetch.cc |
| */ |
| int |
| remove_header(TSMBuffer buf, TSMLoc hdr_loc, const char *header, int len) |
| { |
| TSMLoc field = TSMimeHdrFieldFind(buf, hdr_loc, header, len); |
| int cnt = 0; |
| |
| while (field) { |
| TSMLoc tmp = TSMimeHdrFieldNextDup(buf, hdr_loc, field); |
| |
| ++cnt; |
| TSMimeHdrFieldDestroy(buf, hdr_loc, field); |
| TSHandleMLocRelease(buf, hdr_loc, field); |
| field = tmp; |
| } |
| |
| 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 |
| */ |
| bool |
| set_header(TSMBuffer buf, TSMLoc hdr_loc, const char *header, int len, const char *val, int val_len) |
| { |
| if (!buf || !hdr_loc || !header || len <= 0 || !val || val_len <= 0) { |
| return false; |
| } |
| |
| DEBUG_LOG("header: %s, len: %d, val: %s, val_len: %d", header, len, val, val_len); |
| bool ret = false; |
| TSMLoc field_loc = TSMimeHdrFieldFind(buf, hdr_loc, header, len); |
| |
| if (!field_loc) { |
| // No existing header, so create one |
| if (TS_SUCCESS == TSMimeHdrFieldCreateNamed(buf, hdr_loc, header, len, &field_loc)) { |
| if (TS_SUCCESS == TSMimeHdrFieldValueStringSet(buf, hdr_loc, field_loc, -1, val, val_len)) { |
| TSMimeHdrFieldAppend(buf, hdr_loc, field_loc); |
| ret = true; |
| } |
| TSHandleMLocRelease(buf, hdr_loc, field_loc); |
| } |
| } else { |
| TSMLoc tmp = nullptr; |
| bool first = true; |
| |
| while (field_loc) { |
| if (first) { |
| first = false; |
| if (TS_SUCCESS == TSMimeHdrFieldValueStringSet(buf, hdr_loc, field_loc, -1, val, val_len)) { |
| ret = true; |
| } |
| } else { |
| TSMimeHdrFieldDestroy(buf, hdr_loc, field_loc); |
| } |
| tmp = TSMimeHdrFieldNextDup(buf, hdr_loc, field_loc); |
| TSHandleMLocRelease(buf, hdr_loc, field_loc); |
| field_loc = tmp; |
| } |
| } |
| |
| return ret; |
| } |
| |
| time_t |
| get_date_from_cached_hdr(TSHttpTxn txn) |
| { |
| TSMBuffer buf; |
| TSMLoc hdr_loc, date_loc; |
| time_t date = 0; |
| |
| if (TSHttpTxnCachedRespGet(txn, &buf, &hdr_loc) == TS_SUCCESS) { |
| date_loc = TSMimeHdrFieldFind(buf, hdr_loc, TS_MIME_FIELD_DATE, TS_MIME_LEN_DATE); |
| if (date_loc != TS_NULL_MLOC) { |
| date = TSMimeHdrFieldValueDateGet(buf, hdr_loc, date_loc); |
| TSHandleMLocRelease(buf, hdr_loc, date_loc); |
| } |
| TSHandleMLocRelease(buf, TS_NULL_MLOC, hdr_loc); |
| } |
| |
| return date; |
| } |
| |
| /** |
| * Handle a special IMS request. |
| */ |
| void |
| handle_cache_lookup_complete(TSHttpTxn txnp, txndata *const txn_state) |
| { |
| int cachestat; |
| if (TS_SUCCESS == TSHttpTxnCacheLookupStatusGet(txnp, &cachestat)) { |
| if (TS_CACHE_LOOKUP_HIT_FRESH == cachestat) { |
| time_t const ch_time = get_date_from_cached_hdr(txnp); |
| DEBUG_LOG("IMS Cached header time %jd vs IMS %jd", static_cast<intmax_t>(ch_time), |
| static_cast<intmax_t>(txn_state->ims_time)); |
| if (ch_time < txn_state->ims_time) { |
| TSHttpTxnCacheLookupStatusSet(txnp, TS_CACHE_LOOKUP_HIT_STALE); |
| if (TSIsDebugTagSet(PLUGIN_NAME)) { |
| int url_len = 0; |
| char *const req_url = TSHttpTxnEffectiveUrlStringGet(txnp, &url_len); |
| if (nullptr != req_url) { |
| std::string const &rv = txn_state->range_value; |
| DEBUG_LOG("Forced revalidate %.*s-%s", url_len, req_url, rv.c_str()); |
| |
| TSfree(req_url); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Transaction event handler. |
| */ |
| int |
| transaction_handler(TSCont contp, TSEvent event, void *edata) |
| { |
| TSHttpTxn txnp = static_cast<TSHttpTxn>(edata); |
| txndata *const txn_state = static_cast<txndata *>(TSContDataGet(contp)); |
| |
| 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, txn_state); |
| break; |
| case TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE: |
| handle_cache_lookup_complete(txnp, txn_state); |
| break; |
| case TS_EVENT_HTTP_TXN_CLOSE: |
| if (txn_state != nullptr) { |
| TSContDataSet(contp, nullptr); |
| delete txn_state; |
| } |
| TSContDestroy(contp); |
| break; |
| default: |
| TSAssert(!"Unexpected event"); |
| break; |
| } |
| TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); |
| return 0; |
| } |
| |
| } // namespace |
| |
| /** |
| * Remap initialization. |
| */ |
| TSReturnCode |
| TSRemapInit(TSRemapInterface *api_info, char *errbuf, int errbuf_size) |
| { |
| 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, "[TSRemapInit] - Incorrect API version %ld.%ld", api_info->tsremap_version >> 16, |
| (api_info->tsremap_version & 0xffff)); |
| return TS_ERROR; |
| } |
| |
| DEBUG_LOG("cache_range_requests remap is successfully initialized."); |
| return TS_SUCCESS; |
| } |
| |
| /** |
| * New Remap instance |
| */ |
| TSReturnCode |
| TSRemapNewInstance(int argc, char *argv[], void **ih, char * /*errbuf */, int /* errbuf_size */) |
| { |
| if (argc < 2) { |
| ERROR_LOG("Remap argument list should contain at least 2 params"); // Should never happen.. |
| return TS_ERROR; |
| } |
| |
| // Skip over the Remap params |
| char *const *plugin_argv = const_cast<char *const *>(argv); |
| |
| // Parse the argument list. |
| *ih = static_cast<void *>(create_pluginconfig(argc - 2, plugin_argv + 2)); |
| |
| if (*ih == nullptr) { |
| ERROR_LOG("Can't create pluginconfig"); |
| } |
| |
| return TS_SUCCESS; |
| } |
| |
| /** |
| * Delete Remap instance |
| */ |
| void |
| TSRemapDeleteInstance(void *ih) |
| { |
| pluginconfig *const pc = static_cast<pluginconfig *>(ih); |
| |
| if (nullptr != pc) { |
| delete_pluginconfig(pc); |
| } |
| } |
| |
| /** |
| * Remap entry point. |
| */ |
| TSRemapStatus |
| TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo * /* rri */) |
| { |
| pluginconfig *const pc = static_cast<pluginconfig *>(ih); |
| |
| range_header_check(txnp, pc); |
| |
| return TSREMAP_NO_REMAP; |
| } |
| |
| /** |
| * Global plugin initialization. |
| */ |
| void |
| TSPluginInit(int argc, const char *argv[]) |
| { |
| TSPluginRegistrationInfo info; |
| TSCont txnp_cont; |
| |
| info.plugin_name = (char *)PLUGIN_NAME; |
| info.vendor_name = (char *)"Comcast"; |
| info.support_email = (char *)"John_Rushford@cable.comcast.com"; |
| |
| if (TSPluginRegister(&info) != TS_SUCCESS) { |
| ERROR_LOG("Plugin registration failed.\n"); |
| ERROR_LOG("Unable to initialize plugin (disabled)."); |
| return; |
| } |
| |
| if (nullptr == gPluginConfig) { |
| if (1 < argc) { |
| char *const *plugin_argv = const_cast<char *const *>(argv); |
| gPluginConfig = create_pluginconfig(argc - 1, plugin_argv + 1); |
| } |
| } |
| |
| if (nullptr == (txnp_cont = TSContCreate(static_cast<TSEventFunc>(handle_read_request_header), nullptr))) { |
| ERROR_LOG("failed to create the transaction continuation handler."); |
| return; |
| } else { |
| TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, txnp_cont); |
| } |
| } |