blob: d8638f316ddbe9a7d98cf9899a9fa9936b53e6bf [file] [log] [blame]
/*
* 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 <iostream>
#include <sstream>
#include <cstdio>
#include <cstring>
#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<struct txndata *>(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<char *>(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<TSHttpTxn>(edata);
struct txndata *txn_data = static_cast<struct txndata *>(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());
}