blob: 743393247e0b09f9d3722afd0c191a8e2286adbb [file]
/*
* 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 <cstdio>
#include <cstring>
#include <cstdlib>
#include <cinttypes>
#include <fstream>
#include <string_view>
#include <string>
#include <bitset>
#include <tsutil/PostScript.h>
#include <tscpp/api/Cleanup.h>
#include <ts/ts.h>
#include <ts/remap.h>
/*
Regression testing code for TS API. Not comprehensive, hopefully will be built up over time.
*/
#define PINAME "test_tsapi"
namespace
{
char PIName[] = PINAME;
DbgCtl dbg_ctl{PIName};
DbgCtl off_dbg_ctl{"yada-yada-yada"};
// NOTE: It's important to flush this after writing so that a gold test using this plugin can examine the log before TS
// terminates.
//
std::fstream logFile;
TSCont tCont, gCont;
std::uintptr_t remap_count;
std::bitset<64> remap_mask;
void
testsForReqHdr(char const *desc, TSMBuffer hbuf, TSMLoc hloc)
{
logFile << desc << ':' << std::endl;
logFile << "TSHttpHdrEffectiveUrlBufGet(): ";
int64_t url_length;
if (TSHttpHdrEffectiveUrlBufGet(hbuf, hloc, nullptr, 0, &url_length) != TS_SUCCESS) {
logFile << "sizing call failed " << std::endl;
} else if (0 == url_length) {
logFile << "zero URL length returned" << std::endl;
} else {
std::string s(url_length, '?');
s += "yada";
int64_t url_length2;
if (TSHttpHdrEffectiveUrlBufGet(hbuf, hloc, s.data(), url_length + 4, &url_length2) != TS_SUCCESS) {
logFile << "data-obtaining call failed" << std::endl;
} else if (url_length2 != url_length) {
logFile << "second size does not match first" << std::endl;
} else if (s.substr(url_length, 4) != "yada") {
logFile << "overwrite" << std::endl;
} else {
logFile << s.substr(0, url_length) << std::endl;
}
}
logFile << "TSUrlSchemeGet(): ";
TSMLoc url_loc;
if (TSHttpHdrUrlGet(hbuf, hloc, &url_loc) != TS_SUCCESS) {
logFile << "failed to get URL loc" << std::endl;
} else {
ts::PostScript ps([=]() -> void { TSHandleMLocRelease(hbuf, TS_NULL_MLOC, url_loc); });
int scheme_len;
char const *scheme_data = TSUrlSchemeGet(hbuf, url_loc, &scheme_len);
if (!scheme_data || !scheme_len) {
logFile << "failed to get URL scheme" << std::endl;
} else {
logFile << std::string_view(scheme_data, scheme_len) << std::endl;
}
logFile << "TSUrlRawSchemeGet(): ";
scheme_data = TSUrlRawSchemeGet(hbuf, url_loc, &scheme_len);
if (!scheme_data || !scheme_len) {
logFile << "failed to get raw URL scheme" << std::endl;
} else {
logFile << std::string_view(scheme_data, scheme_len) << std::endl;
}
logFile << "TSUrlPortGet(): " << TSUrlPortGet(hbuf, url_loc) << std::endl;
logFile << "TSUrlRawPortGet(): " << TSUrlRawPortGet(hbuf, url_loc) << std::endl;
}
}
void
testsForEffectiveUrlStringGet(TSHttpTxn txn)
{
logFile << "TSHttpTxnEffectiveUrlStringGet(): ";
int urlLength;
char *urlStr = TSHttpTxnEffectiveUrlStringGet(txn, &urlLength);
if (!urlStr) {
logFile << "URL null" << std::endl;
} else if (0 == urlLength) {
logFile << "URL length zero" << std::endl;
} else if (0 > urlLength) {
logFile << "URL length negative" << std::endl;
} else {
logFile << std::string_view(urlStr, urlLength) << std::endl;
TSfree(urlStr);
}
}
void
testsForReadReqHdrHook(TSHttpTxn txn)
{
testsForEffectiveUrlStringGet(txn);
{
TSMBuffer hbuf;
TSMLoc hloc;
if (TSHttpTxnClientReqGet(txn, &hbuf, &hloc) != TS_SUCCESS) {
logFile << "failed to get client request" << std::endl;
} else {
testsForReqHdr("Client Request", hbuf, hloc);
TSHandleMLocRelease(hbuf, TS_NULL_MLOC, hloc);
}
}
}
void
testsForSendReqHdrHook(TSHttpTxn txn)
{
testsForEffectiveUrlStringGet(txn);
{
TSMBuffer hbuf;
TSMLoc hloc;
if (TSHttpTxnServerReqGet(txn, &hbuf, &hloc) != TS_SUCCESS) {
logFile << "failed to get server request" << std::endl;
} else {
testsForReqHdr("Request To Server", hbuf, hloc);
TSHandleMLocRelease(hbuf, TS_NULL_MLOC, hloc);
}
}
}
int
transactionContFunc(TSCont, TSEvent event, void *eventData)
{
logFile << "Transaction: event=" << TSHttpEventNameLookup(event) << std::endl;
Dbg(dbg_ctl, "Transaction: event=%s(%d) eventData=%p", TSHttpEventNameLookup(event), event, eventData);
Dbg(off_dbg_ctl, "Should not see this, tag does not match regular expression");
switch (event) {
case TS_EVENT_HTTP_READ_REQUEST_HDR: {
auto txn = static_cast<TSHttpTxn>(eventData);
testsForReadReqHdrHook(txn);
TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
} break;
case TS_EVENT_HTTP_SEND_REQUEST_HDR: {
auto txn = static_cast<TSHttpTxn>(eventData);
testsForSendReqHdrHook(txn);
TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
} break;
default: {
TSReleaseAssert(false);
} break;
} // end switch
return 0;
}
int
globalContFunc(TSCont, TSEvent event, void *eventData)
{
logFile << "Global: event=" << TSHttpEventNameLookup(event) << std::endl;
Dbg(dbg_ctl, "Global: event=%s(%d) eventData=%p", TSHttpEventNameLookup(event), event, eventData);
switch (event) {
case TS_EVENT_HTTP_TXN_START: {
auto txn = static_cast<TSHttpTxn>(eventData);
TSHttpTxnHookAdd(txn, TS_HTTP_READ_REQUEST_HDR_HOOK, tCont);
TSHttpTxnHookAdd(txn, TS_HTTP_SEND_REQUEST_HDR_HOOK, tCont);
TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
} break;
case TS_EVENT_HTTP_READ_REQUEST_HDR: {
auto txn = static_cast<TSHttpTxn>(eventData);
testsForReadReqHdrHook(txn);
TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
} break;
case TS_EVENT_HTTP_SEND_REQUEST_HDR: {
auto txn = static_cast<TSHttpTxn>(eventData);
testsForSendReqHdrHook(txn);
TSHttpTxnReenable(txn, TS_EVENT_HTTP_CONTINUE);
} break;
default: {
TSReleaseAssert(false);
} break;
} // end switch
return 0;
}
} // end anonymous namespace
TSReturnCode
TSRemapInit(TSRemapInterface *api_info, char * /* errbuf ATS_UNUSED */, int /* errbuf_size ATS_UNUSED */)
{
Dbg(dbg_ctl, "TSRemapInit()");
SpecificDbg(true, off_dbg_ctl, "Should see this");
SpecificDbg(false, dbg_ctl, "Should see this");
SpecificDbg(false, off_dbg_ctl, "Should NOT see this");
TSReleaseAssert(api_info != nullptr);
TSReleaseAssert(api_info->size == sizeof(TSRemapInterface));
TSReleaseAssert(api_info->tsremap_version == TSREMAP_VERSION);
const char *fileSpec = std::getenv("OUTPUT_FILE");
if (nullptr == fileSpec) {
TSError(PINAME ": Environment variable OUTPUT_FILE not found.");
return TS_ERROR;
}
// Disable output buffering for logFile, so that explicit flushing is not necessary.
logFile.rdbuf()->pubsetbuf(nullptr, 0);
logFile.open(fileSpec, std::ios::out);
if (!logFile.is_open()) {
TSError(PINAME ": could not open log file \"%s\"", fileSpec);
return TS_ERROR;
}
// Mutex to protect the logFile object.
//
TSMutex mtx = TSMutexCreate();
gCont = TSContCreate(globalContFunc, mtx);
TSHttpHookAdd(TS_HTTP_TXN_START_HOOK, gCont);
TSHttpHookAdd(TS_HTTP_READ_REQUEST_HDR_HOOK, gCont);
TSHttpHookAdd(TS_HTTP_SEND_REQUEST_HDR_HOOK, gCont);
tCont = TSContCreate(transactionContFunc, mtx);
return TS_SUCCESS;
}
TSReturnCode
TSRemapNewInstance(int argc, char *argv[], void **instance, char *errbuf, int errbuf_size)
{
TSReleaseAssert(errbuf && errbuf_size);
TSReleaseAssert(remap_count < remap_mask.size());
remap_mask[remap_count++] = true;
*instance = reinterpret_cast<void *>(remap_count);
logFile << "TSRemapNewInstance():" << std::endl;
for (int i = 0; i < argc; ++i) {
logFile << "argv[" << i << "]=" << argv[i] << std::endl;
}
return TS_SUCCESS;
}
void
TSRemapDeleteInstance(void *instance)
{
// NOTE: Currently this is never called.
auto inum = reinterpret_cast<std::uintptr_t>(instance) - 1;
logFile << "TSRemapNewInstance(): instance=" << inum << std::endl;
TSReleaseAssert(inum < remap_mask.size());
TSReleaseAssert(remap_mask[inum]);
remap_mask[inum] = false;
}
TSRemapStatus
TSRemapDoRemap(void *instance, TSHttpTxn txnp, TSRemapRequestInfo *rri)
{
TSReleaseAssert(txnp && rri);
auto inum = reinterpret_cast<std::uintptr_t>(instance) - 1;
TSReleaseAssert(inum < remap_mask.size());
logFile << "TSRemapDoRemap(): instance=" << inum << " redirect=" << rri->redirect << std::endl;
testsForReqHdr("Remap Request", rri->requestBufp, rri->requestHdrp);
return TSREMAP_NO_REMAP;
}
namespace
{
class Cleanup
{
public:
~Cleanup()
{
// In practice it is not strictly necessary to destroy remaining continuations on program exit.
if (tCont) {
TSContDestroy(tCont);
}
if (gCont) {
TSContDestroy(gCont);
}
}
};
// Do any needed cleanup for this source file at program termination time.
//
Cleanup cleanup;
} // end anonymous namespace