/* * 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 #include #include #include #include "ts/ts.h" #include "ts/remap.h" #include "money_trace.h" /** * Allocate transaction data structure. */ static struct txndata * allocTransactionData() { LOG_DEBUG("allocating transaction state data."); struct txndata *txn_data = static_cast(TSmalloc(sizeof(struct txndata))); txn_data->client_request_mt_header = nullptr; txn_data->new_span_mt_header = nullptr; return txn_data; } /** * free any previously allocated transaction data. */ static void freeTransactionData(struct txndata *txn_data) { LOG_DEBUG("de-allocating transaction state data."); if (txn_data != nullptr) { LOG_DEBUG("freeing transaction data."); TSfree(txn_data->client_request_mt_header); TSfree(txn_data->new_span_mt_header); TSfree(txn_data); } } /** * The TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE event callback. * * If there is a cache hit only schedule a TS_HTTP_SEND_RESPONSE_HDR_HOOK * continuation to send back the money trace header in the response to the * client. * * If there is a cache miss, a new money trace header is created and a * TS_HTTP_SEND_REQUES_HDR_HOOK continuation is scheduled to add the * new money trace header to the parent request. */ static void mt_cache_lookup_check(TSCont contp, TSHttpTxn txnp, struct txndata *txn_data) { MT generator; int cache_result = 0; char *new_mt_header; if (TS_SUCCESS != TSHttpTxnCacheLookupStatusGet(txnp, &cache_result)) { LOG_ERROR("Unable to get cache status."); } else { switch (cache_result) { case TS_CACHE_LOOKUP_MISS: case TS_CACHE_LOOKUP_SKIPPED: new_mt_header = const_cast(generator.moneyTraceHdr(txn_data->client_request_mt_header)); if (new_mt_header != nullptr) { LOG_DEBUG("cache miss, built a new money trace header: %s.", new_mt_header); txn_data->new_span_mt_header = new_mt_header; } else { LOG_DEBUG("failed to build a new money trace header."); } TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_REQUEST_HDR_HOOK, contp); // fall through to the default as we always need to send the original // money trace header received from the client back to the client in the // response // fallthrough default: TSHttpTxnHookAdd(txnp, TS_HTTP_SEND_RESPONSE_HDR_HOOK, contp); break; } } } /** * remap entry point, called to check for the existence of a money trace * header. If there is one, schedule the continuation to call back and * process on TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK and TS_HTTP_TXN_CLOSE_HOOK. */ static void mt_check_request_header(TSHttpTxn txnp) { int length = 0; struct txndata *txn_data = nullptr; TSMBuffer bufp; TSMLoc hdr_loc = nullptr, field_loc = nullptr; TSCont contp; // check for a money trace header. If there is one, schedule appropriate continuations. if (TS_SUCCESS == TSHttpTxnClientReqGet(txnp, &bufp, &hdr_loc)) { field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, MIME_FIELD_MONEY_TRACE, MIME_LEN_MONEY_TRACE); if (TS_NULL_MLOC != field_loc) { const char *hdr_value = TSMimeHdrFieldValueStringGet(bufp, hdr_loc, field_loc, 0, &length); if (!hdr_value || length <= 0) { LOG_DEBUG("ignoring, corrupt money trace header."); } else { if (nullptr == (contp = TSContCreate(transaction_handler, nullptr))) { LOG_ERROR("failed to create the transaction handler continuation"); } else { txn_data = allocTransactionData(); txn_data->client_request_mt_header = TSstrndup(hdr_value, length); txn_data->client_request_mt_header[length] = '\0'; // workaround for bug in core. LOG_DEBUG("found money trace header: %s, length: %d", txn_data->client_request_mt_header, length); TSContDataSet(contp, txn_data); TSHttpTxnHookAdd(txnp, TS_HTTP_CACHE_LOOKUP_COMPLETE_HOOK, contp); TSHttpTxnHookAdd(txnp, TS_HTTP_TXN_CLOSE_HOOK, contp); } } } else { LOG_DEBUG("no money trace header was found in the request."); } } else { LOG_DEBUG("failed to retrieve the client request."); } TSHandleMLocRelease(bufp, hdr_loc, field_loc); } /** * The TS_EVENT_HTTP_SEND_RESPONSE_HDR callback. * * Adds the money trace header received in the client request to the * client response headers. */ static void mt_send_client_response(TSHttpTxn txnp, struct txndata *txn_data) { TSMBuffer bufp; TSMLoc hdr_loc = nullptr, field_loc = nullptr; if (txn_data->client_request_mt_header == nullptr) { LOG_DEBUG("no client request header to return."); return; } // send back the money trace header received in the request // back in the response to the client. if (TS_SUCCESS != TSHttpTxnClientRespGet(txnp, &bufp, &hdr_loc)) { LOG_DEBUG("could not get the server response headers."); return; } else { if (TS_SUCCESS == TSMimeHdrFieldCreateNamed(bufp, hdr_loc, MIME_FIELD_MONEY_TRACE, strlen(MIME_FIELD_MONEY_TRACE), &field_loc)) { if (TS_SUCCESS == TSMimeHdrFieldValueStringSet(bufp, hdr_loc, field_loc, -1, txn_data->client_request_mt_header, strlen(txn_data->client_request_mt_header))) { LOG_DEBUG("response header added: %s: %s", MIME_FIELD_MONEY_TRACE, txn_data->client_request_mt_header); TSMimeHdrFieldAppend(bufp, hdr_loc, field_loc); } } else { LOG_DEBUG("failed to create money trace response header."); } } TSHandleMLocRelease(bufp, hdr_loc, field_loc); return; } /** * The TS_EVENT_HTTP_SEND_REQUEST_HDR callback. * * When a parent request is made, this function adds the new * money trace header to the parent request headers. */ static void mt_send_server_request(TSHttpTxn txnp, struct txndata *txn_data) { TSMBuffer bufp; TSMLoc hdr_loc = nullptr, field_loc = nullptr; if (txn_data->new_span_mt_header == nullptr) { LOG_DEBUG("there is no new mt request header to send."); return; } if (TS_SUCCESS == TSHttpTxnServerReqGet(txnp, &bufp, &hdr_loc)) { field_loc = TSMimeHdrFieldFind(bufp, hdr_loc, MIME_FIELD_MONEY_TRACE, MIME_LEN_MONEY_TRACE); if (TS_NULL_MLOC != field_loc) { if (TS_SUCCESS == TSMimeHdrFieldValueStringSet(bufp, hdr_loc, field_loc, -1, txn_data->new_span_mt_header, strlen(txn_data->new_span_mt_header))) { LOG_DEBUG("server request header updated: %s: %s", MIME_FIELD_MONEY_TRACE, txn_data->new_span_mt_header); } } else { LOG_DEBUG("unable to retrieve the money trace header location from the server request headers."); return; } } TSHandleMLocRelease(bufp, hdr_loc, field_loc); return; } /** * 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; } LOG_DEBUG("cache_range_requests remap is successfully initialized."); return TS_SUCCESS; } /** * not used, one instance per remap and no parameters are used. */ TSReturnCode TSRemapNewInstance(int argc, char *argv[], void **ih, char * /*errbuf */, int /* errbuf_size */) { return TS_SUCCESS; } /** * not used, one instance per remap */ void TSRemapDeleteInstance(void *ih) { LOG_DEBUG("no op"); } /** * Remap entry point. */ TSRemapStatus TSRemapDoRemap(void *ih, TSHttpTxn txnp, TSRemapRequestInfo * /* rri */) { mt_check_request_header(txnp); return TSREMAP_NO_REMAP; } /** * Transaction event handler. */ static int transaction_handler(TSCont contp, TSEvent event, void *edata) { TSHttpTxn txnp = static_cast(edata); struct txndata *txn_data = static_cast(TSContDataGet(contp)); switch (event) { case TS_EVENT_HTTP_CACHE_LOOKUP_COMPLETE: LOG_DEBUG("transaction cache lookup complete."); mt_cache_lookup_check(contp, txnp, txn_data); break; case TS_EVENT_HTTP_SEND_REQUEST_HDR: LOG_DEBUG("updating send request headers."); mt_send_server_request(txnp, txn_data); break; case TS_EVENT_HTTP_SEND_RESPONSE_HDR: LOG_DEBUG("updating send response headers."); mt_send_client_response(txnp, txn_data); break; case TS_EVENT_HTTP_TXN_CLOSE: LOG_DEBUG("handling transaction close."); freeTransactionData(txn_data); TSContDestroy(contp); break; default: TSAssert(!"Unexpected event"); break; } TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE); return TS_SUCCESS; } const char * MT::moneyTraceHdr(const char *mt_request_hdr) { char copy[8192] = {'\0'}; char *toks[3], *p = nullptr, *saveptr = nullptr; std::ostringstream temp_str; std::string mt_header_str; int numtoks = 0; if (mt_request_hdr == nullptr) { LOG_DEBUG("an empty header was passed in."); return nullptr; } else { strncpy(copy, mt_request_hdr, 8191); } // parse the money header. p = strtok_r(copy, ";", &saveptr); if (p != nullptr) { toks[numtoks++] = p; // copy the traceid } else { LOG_DEBUG("failed to parse the money_trace_header: %s", mt_request_hdr); return nullptr; } do { p = strtok_r(nullptr, ";", &saveptr); if (p != nullptr) { toks[numtoks++] = p; } } while (p != nullptr && numtoks < 3); if (numtoks != 3 || toks[0] == nullptr || toks[1] == nullptr || toks[2] == nullptr) { LOG_DEBUG("failed to parse the money_trace_header: %s", mt_request_hdr); return nullptr; } if (strncmp(toks[0], "trace-id", strlen("trace-id")) == 0 && strncmp(toks[2], "span-id", strlen("span-id")) == 0 && (p = strchr(toks[2], '=')) != nullptr) { p++; if (strncmp("0x", p, 2) == 0) { temp_str << toks[0] << ";parent-id=" << p << ";span-id=0x" << std::hex << spanId() << std::ends; } else { temp_str << toks[0] << ";parent-id=" << p << ";span-id=" << spanId() << std::ends; } } else { LOG_DEBUG("invalid money_trace_header: %s", mt_request_hdr); return nullptr; } mt_header_str = temp_str.str(); return TSstrndup(mt_header_str.c_str(), mt_header_str.length()); }