| /** @file |
| |
| A simple remap plugin for ATS |
| |
| @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. |
| |
| @section description |
| Build this sample remap plugin using tsxs: |
| |
| $ tsxs -v -o remap.so remap.cc |
| |
| To install it: |
| # tsxs -i -o remap.so |
| */ |
| |
| #include <cstdio> |
| #include <cstdlib> |
| #include <cstring> |
| #include <cstdarg> |
| #include <cerrno> |
| #include <pthread.h> |
| #include <cstdint> |
| #include <atomic> |
| |
| #include "tscore/ink_defs.h" |
| #include "ts/ts.h" |
| #include "ts/remap.h" |
| |
| #define PLUGIN_NAME "remap" |
| |
| class remap_entry |
| { |
| public: |
| static remap_entry *active_list; |
| static pthread_mutex_t mutex; |
| remap_entry *next; |
| int argc; |
| char **argv; |
| remap_entry(int _argc, char *_argv[]); |
| ~remap_entry(); |
| static void add_to_list(remap_entry *re); |
| static void remove_from_list(remap_entry *re); |
| }; |
| |
| static int plugin_init_counter = 0; /* remap plugin initialization counter */ |
| static pthread_mutex_t remap_plugin_global_mutex; /* remap plugin global mutex */ |
| remap_entry *remap_entry::active_list = nullptr; |
| pthread_mutex_t remap_entry::mutex; /* remap_entry class mutex */ |
| |
| /* ----------------------- remap_entry::remap_entry ------------------------ */ |
| remap_entry::remap_entry(int _argc, char *_argv[]) : next(nullptr), argc(0), argv(nullptr) |
| { |
| int i; |
| |
| if (_argc > 0 && _argv && (argv = (char **)TSmalloc(sizeof(char *) * (_argc + 1))) != nullptr) { |
| argc = _argc; |
| for (i = 0; i < argc; i++) { |
| argv[i] = TSstrdup(_argv[i]); |
| } |
| argv[i] = nullptr; |
| } |
| } |
| |
| /* ---------------------- remap_entry::~remap_entry ------------------------ */ |
| remap_entry::~remap_entry() |
| { |
| int i; |
| |
| if (argc && argv) { |
| for (i = 0; i < argc; i++) { |
| TSfree(argv[i]); |
| } |
| TSfree(argv); |
| } |
| } |
| |
| /* --------------------- remap_entry::add_to_list -------------------------- */ |
| void |
| remap_entry::add_to_list(remap_entry *re) |
| { |
| if (likely(re && plugin_init_counter)) { |
| pthread_mutex_lock(&mutex); |
| re->next = active_list; |
| active_list = re; |
| pthread_mutex_unlock(&mutex); |
| } |
| } |
| |
| /* ------------------ remap_entry::remove_from_list ------------------------ */ |
| void |
| remap_entry::remove_from_list(remap_entry *re) |
| { |
| remap_entry **rre; |
| if (likely(re && plugin_init_counter)) { |
| pthread_mutex_lock(&mutex); |
| for (rre = &active_list; *rre; rre = &((*rre)->next)) { |
| if (re == *rre) { |
| *rre = re->next; |
| break; |
| } |
| } |
| pthread_mutex_unlock(&mutex); |
| } |
| } |
| |
| /* ----------------------- store_my_error_message -------------------------- */ |
| static TSReturnCode |
| store_my_error_message(TSReturnCode retcode, char *err_msg_buf, int buf_size, const char *fmt, ...) |
| { |
| if (likely(err_msg_buf && buf_size > 0 && fmt)) { |
| va_list ap; |
| va_start(ap, fmt); |
| err_msg_buf[0] = 0; |
| (void)vsnprintf(err_msg_buf, buf_size, fmt, ap); |
| err_msg_buf[buf_size - 1] = 0; |
| va_end(ap); |
| } |
| return retcode; /* error code here */ |
| } |
| |
| // Plugin initialization code. Called immediately after dlopen() Only once! |
| // Can perform internal initialization. For example, pthread_.... initialization. |
| /* ------------------------- TSRemapInit ---------------------------------- */ |
| TSReturnCode |
| TSRemapInit(TSRemapInterface *api_info, char *errbuf, int errbuf_size) |
| { |
| TSDebug(PLUGIN_NAME, "enter"); |
| |
| if (!plugin_init_counter) { |
| if (unlikely(!api_info)) { |
| return store_my_error_message(TS_ERROR, errbuf, errbuf_size, "Invalid TSRemapInterface argument"); |
| } |
| if (unlikely(api_info->size < sizeof(TSRemapInterface))) { |
| return store_my_error_message(TS_ERROR, errbuf, errbuf_size, |
| "Incorrect size of TSRemapInterface structure %d. Should be at least %d bytes", |
| (int)api_info->size, (int)sizeof(TSRemapInterface)); |
| } |
| if (unlikely(api_info->tsremap_version < TSREMAP_VERSION)) { |
| return store_my_error_message(TS_ERROR, errbuf, errbuf_size, "Incorrect API version %d.%d", (api_info->tsremap_version >> 16), |
| (api_info->tsremap_version & 0xffff)); |
| } |
| |
| if (pthread_mutex_init(&remap_plugin_global_mutex, nullptr) || |
| pthread_mutex_init(&remap_entry::mutex, nullptr)) { /* pthread_mutex_init - always returns 0. :) - impossible error */ |
| return store_my_error_message(TS_ERROR, errbuf, errbuf_size, "Mutex initialization error"); |
| } |
| plugin_init_counter++; |
| } |
| return TS_SUCCESS; /* success */ |
| } |
| |
| /* ------------------------ TSRemapNewInstance --------------------------- */ |
| // Plugin new instance for new remapping rule. |
| // This function can be called multiple times (depends on remap.config) |
| TSReturnCode |
| TSRemapNewInstance(int argc, char *argv[], void **ih, char *errbuf, int errbuf_size) |
| { |
| remap_entry *ri; |
| int i; |
| |
| TSDebug(PLUGIN_NAME, "enter"); // Debug output automatically includes the file, line #, and function. |
| |
| if (argc < 2) { |
| return store_my_error_message(TS_ERROR, errbuf, errbuf_size, "Incorrect number of arguments - %d", argc); |
| } |
| if (!argv || !ih) { |
| return store_my_error_message(TS_ERROR, errbuf, errbuf_size, "Invalid argument(s)"); |
| } |
| // print all arguments for this particular remapping |
| for (i = 0; i < argc; i++) { |
| TSDebug(PLUGIN_NAME, "[%s] - argv[%d] = \"%s\"\n", __func__, i, argv[i]); |
| } |
| |
| ri = new remap_entry(argc, argv); |
| |
| if (!ri) { |
| return store_my_error_message(TS_ERROR, errbuf, errbuf_size, "Can't create remap_entry class"); |
| } |
| |
| remap_entry::add_to_list(ri); |
| |
| *ih = ri; |
| |
| return TS_SUCCESS; |
| } |
| /* ---------------------- TSRemapDeleteInstance -------------------------- */ |
| void |
| TSRemapDeleteInstance(void *ih) |
| { |
| remap_entry *ri = (remap_entry *)ih; |
| |
| TSDebug(PLUGIN_NAME, "enter"); |
| |
| remap_entry::remove_from_list(ri); |
| |
| delete ri; |
| } |
| |
| static std::atomic<uint64_t> processing_counter; // sequential counter |
| static int arg_index; |
| |
| /* -------------------------- TSRemapDoRemap -------------------------------- */ |
| TSRemapStatus |
| TSRemapDoRemap(void *ih, TSHttpTxn rh, TSRemapRequestInfo *rri) |
| { |
| const char *temp; |
| const char *temp2; |
| int len, len2, port; |
| TSMLoc cfield; |
| uint64_t _processing_counter = processing_counter++; |
| |
| remap_entry *ri = (remap_entry *)ih; |
| TSDebug(PLUGIN_NAME, "enter"); |
| |
| if (!ri || !rri) { |
| return TSREMAP_NO_REMAP; /* TS must remap this request */ |
| } |
| |
| TSDebug(PLUGIN_NAME, "From: \"%s\" To: \"%s\"\n", ri->argv[0], ri->argv[1]); |
| |
| temp = TSUrlHostGet(rri->requestBufp, rri->requestUrl, &len); |
| TSDebug(PLUGIN_NAME, "Request Host(%d): \"%.*s\"\n", len, len, temp); |
| |
| temp = TSUrlHostGet(rri->requestBufp, rri->mapToUrl, &len); |
| TSDebug(PLUGIN_NAME, "Remap To Host: \"%.*s\"\n", len, temp); |
| |
| temp = TSUrlHostGet(rri->requestBufp, rri->mapFromUrl, &len); |
| TSDebug(PLUGIN_NAME, "Remap From Host: \"%.*s\"\n", len, temp); |
| |
| TSDebug(PLUGIN_NAME, "Request Port: %d\n", TSUrlPortGet(rri->requestBufp, rri->requestUrl)); |
| TSDebug(PLUGIN_NAME, "Remap From Port: %d\n", TSUrlPortGet(rri->requestBufp, rri->mapFromUrl)); |
| TSDebug(PLUGIN_NAME, "Remap To Port: %d\n", TSUrlPortGet(rri->requestBufp, rri->mapToUrl)); |
| |
| temp = TSUrlPathGet(rri->requestBufp, rri->requestUrl, &len); |
| TSDebug(PLUGIN_NAME, "Request Path: \"%.*s\"\n", len, temp); |
| |
| temp = TSUrlPathGet(rri->requestBufp, rri->mapFromUrl, &len); |
| TSDebug(PLUGIN_NAME, "Remap From Path: \"%.*s\"\n", len, temp); |
| |
| temp = TSUrlPathGet(rri->requestBufp, rri->mapToUrl, &len); |
| TSDebug(PLUGIN_NAME, "Remap To Path: \"%.*s\"\n", len, temp); |
| |
| // InkAPI usage case |
| const char *value; |
| |
| if ((cfield = TSMimeHdrFieldFind(rri->requestBufp, rri->requestHdrp, TS_MIME_FIELD_DATE, -1)) != TS_NULL_MLOC) { |
| TSDebug(PLUGIN_NAME, "We have \"Date\" header in request\n"); |
| value = TSMimeHdrFieldValueStringGet(rri->requestBufp, rri->requestHdrp, cfield, -1, nullptr); |
| TSDebug(PLUGIN_NAME, "Header value: %s\n", value); |
| } |
| if ((cfield = TSMimeHdrFieldFind(rri->requestBufp, rri->requestHdrp, "MyHeader", sizeof("MyHeader") - 1)) != TS_NULL_MLOC) { |
| TSDebug(PLUGIN_NAME, "We have \"MyHeader\" header in request\n"); |
| value = TSMimeHdrFieldValueStringGet(rri->requestBufp, rri->requestHdrp, cfield, -1, nullptr); |
| TSDebug(PLUGIN_NAME, "Header value: %s\n", value); |
| } |
| |
| // How to store plugin private arguments inside Traffic Server request processing block. |
| if (TSHttpTxnArgIndexReserve("remap_example", "Example remap plugin", &arg_index) == TS_SUCCESS) { |
| TSDebug(PLUGIN_NAME, "Save processing counter %" PRIu64 " inside request processing block\n", _processing_counter); |
| TSHttpTxnArgSet((TSHttpTxn)rh, arg_index, (void *)_processing_counter); // save counter |
| } |
| // How to cancel request processing and return error message to the client |
| // We will do it every other request |
| if (_processing_counter & 1) { |
| char *tmp = (char *)TSmalloc(256); |
| static int my_local_counter = 0; |
| |
| size_t len = snprintf(tmp, 255, "This is very small example of TS API usage!\nIteration %d!\nHTTP return code %d\n", |
| my_local_counter, TS_HTTP_STATUS_CONTINUE + my_local_counter); |
| TSHttpTxnStatusSet((TSHttpTxn)rh, (TSHttpStatus)((int)TS_HTTP_STATUS_CONTINUE + my_local_counter)); |
| TSHttpTxnErrorBodySet((TSHttpTxn)rh, tmp, len, nullptr); // Defaults to text/html |
| my_local_counter++; |
| } |
| // hardcoded case for remapping |
| // You need to check host and port if you are using the same plugin for multiple remapping rules |
| temp = TSUrlHostGet(rri->requestBufp, rri->requestUrl, &len); |
| temp2 = TSUrlPathGet(rri->requestBufp, rri->requestUrl, &len2); |
| port = TSUrlPortGet(rri->requestBufp, rri->requestUrl); |
| |
| if (len == 10 && !memcmp("flickr.com", temp, 10) && port == 80 && len2 >= 3 && !memcmp("47/", temp2, 3)) { |
| char new_path[8192]; |
| |
| // Ugly, but so is the rest of this "example" |
| if (len2 + 7 >= 8192) { |
| return TSREMAP_NO_REMAP; |
| } |
| |
| if (TSUrlPortSet(rri->requestBufp, rri->mapToUrl, TSUrlPortGet(rri->requestBufp, rri->mapToUrl)) != TS_SUCCESS) { |
| return TSREMAP_NO_REMAP; |
| } |
| |
| if (TSUrlHostSet(rri->requestBufp, rri->requestUrl, "foo.bar.com", 11) != TS_SUCCESS) { |
| return TSREMAP_NO_REMAP; |
| } |
| |
| memcpy(new_path, "47_copy", 7); |
| memcpy(&new_path[7], &temp2[2], len2 - 2); |
| |
| if (TSUrlPathSet(rri->requestBufp, rri->requestUrl, new_path, len2 + 5) == TS_SUCCESS) { |
| return TSREMAP_DID_REMAP; |
| } |
| } |
| |
| // Failure ... |
| return TSREMAP_NO_REMAP; |
| } |
| |
| /* ----------------------- TSRemapOSResponse ----------------------------- */ |
| void |
| TSRemapOSResponse(void *ih ATS_UNUSED, TSHttpTxn rh, int os_response_type) |
| { |
| void *data = TSHttpTxnArgGet((TSHttpTxn)rh, arg_index); // read counter (we store it in TSRemapDoRemap function call) |
| int request_id = data ? static_cast<int *>(data)[0] : -1; |
| |
| TSDebug(PLUGIN_NAME, "Read processing counter %d from request processing block\n", request_id); |
| TSDebug(PLUGIN_NAME, "OS response status: %d\n", os_response_type); |
| } |