transform插件用于修改POST请求的body和http响应的body,可以理解为一个过滤器,或者nginx的body_filter。
有两个地方可以添加transform回调函数,TS_HTTP_REQUEST_TRANSFORM_HOOK和TS_HTTP_RESPONSE_TRANSFORM_HOOK,分别是过滤POST请求的body和http响应的body。在适当的hook点判断是否需要使transform逻辑生效,需要的话在相应的transform hook点添加回调函数。比如这样一个场景,在缓存查询结束的时候判断查询结果是hit还是miss,miss的话需要在http响应body末尾添加一些内容,这样响应的内容连同被添加的内容会当成整个的响应内容被缓存住,后续的请求如果在缓存查询结束判断查询结果是hit就会将transform后的结果响应出去。
有两个vio,input_vio(write vio)和output_vio(read vio)。每个vio都有配套的TSIOBuffer和TSIOBufferReader一起工作。
数据的流向是input_vio->分片插件->output_vio。transform插件对应一个continuation和一个回调函数,假设这个回调函数叫plutin_handler,plutin_handler根据不同的event有不同的逻辑。分片插件每transform一些东西,都会触发上游的TS_EVENT_VCONN_WRITE_READY事件通知上游可以继续发送东西过来,并且触发下游的TS_EVENT_IMMEDIATE事件通知下游有东西可以读了。如果分片插件判断已经没有数据需要继续处理了,会触发上游的TS_EVENT_VCONN_WRITE_COMPLETE事件,并且触发下游的TS_EVENT_IMMEDIATE事件。或者可以这样理解。整个流程分为三部分,上游,分片插件,下游。下游处理完数据会通过TSContCall触发上游的TS_EVENT_VCONN_WRITE_READY或TS_EVENT_VCONN_WRITE_COMPLETE事件,上游准备好了数据会通过TSVIOReenable触发下游的TS_EVENT_IMMEDIATE事件。
ats插件的编译:tsxs -o hello-world.so -c hello-world.c,注意tsxs要使用与其它可执行文件配套一起生成的,不然的话会出错。重点函数:
TSVConnWrite:
tsapi TSVIO TSVConnWrite(TSVConn connp, TSCont contp, TSIOBufferReader readerp, int64_t nbytes);
生成一个vio,有什么操作的话会触发contp,要写的字节数就是nbytes
TSVConnWriteVIOGet:
tsapi TSVIO TSVConnWriteVIOGet(TSVConn connp);
获取一个VConnection的input_vio(write_vio),VConnection是input_vio的实施者
TSVIOContGet:
tsapi TSCont TSVIOContGet(TSVIO viop);
获取一个vio的continuation,continuation是vio的使用者,是transform的上游
TSTransformOutputVConnGet:
tsapi TSVConn TSTransformOutputVConnGet(TSVConn connp);
获取一个下游的VConnection
TSVIONDoneGet:
tsapi TSIOBuffer TSVIOBufferGet(TSVIO viop);
获取一个vio已经操作完成的字节数
TSVIONBytesSet:
tsapi void TSVIONBytesSet(TSVIO viop, int64_t nbytes);
设置这个vio将要处理的字节数,第二个参数要比TSVIONDoneGet得到的结果大
TSVIONBytesGet:
tsapi int64_t TSVIONBytesGet(TSVIO viop);
获取这个vio将要处理的字节数
TSIOBufferCopy:
tsapi int64_t TSIOBufferCopy(TSIOBuffer bufp, TSIOBufferReader readerp, int64_t length, int64_t offset);
将内容从一个TSIOBufferReader拷贝到一个TSIOBuffer,拷贝的字节数是length,偏移量是offset
TSIOBufferReaderConsume:
tsapi void TSIOBufferReaderConsume(TSIOBufferReader readerp, int64_t nbytes);
拷贝操作结束后要执行这个函数,要消费input_vio指定字节数
TSVIONDoneSet:
tsapi void TSVIONDoneSet(TSVIO viop, int64_t ndone);
拷贝操作结束后要执行这个函数,要告知input_vio已经消费了多少字节
TSVConnClosedGet:
tsapi int TSVConnClosedGet(TSVConn connp);
判断connp是否已经被关闭,connp是transform插件本身的contiuation
如下是一个完整的transform插件,实现的功能是在读源站的响应头时添加transform插件。如果缓存查询结果是hit则不需要回源,也就不会添加transform插件了。插件的逻辑是在响应末尾添加"hello world\n"字符串。
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <ts/ts.h>
#define ASSERT_SUCCESS(_x) TSAssert ((_x) == TS_SUCCESS)
typedef struct {
TSVIO output_vio;
TSIOBuffer output_buffer;
TSIOBufferReader output_reader;
int append_needed;
} MyData;
static TSIOBuffer append_buffer;
static TSIOBufferReader append_buffer_reader;
static int append_buffer_length;
static MyData *
my_data_alloc() {
MyData *data;
data = (MyData *) TSmalloc(sizeof(MyData));
TSReleaseAssert(data);
data->output_vio = NULL;
data->output_buffer = NULL;
data->output_reader = NULL;
data->append_needed = 1;
return data;
}
static void my_data_destroy(MyData * data) {
if (data) {
if (data->output_buffer)
TSIOBufferDestroy(data->output_buffer);
TSfree(data);
}
}
static void handle_transform(TSCont contp) {
TSVConn output_conn;
TSVIO write_vio;
MyData *data;
int64_t towrite;
int64_t avail;
/*获取transform插件的下游VConnection,这个VConnection会接收transform插件的处理完的数据
这个VConnection会触发contp的TS_EVENT_VCONN_WRITE_READY,TS_EVENT_VCONN_WRITE_COMPLETE
或TS_EVENT_ERROR事件*/
output_conn = TSTransformOutputVConnGet(contp);
/*获取input_vio,通过input_vio可以获取到input_buffer*/
write_vio = TSVConnWriteVIOGet(contp);
/*获取这个contp的数据,没有的话初始化并且TSContDataSet*/
data = TSContDataGet(contp);
if (!data) {
towrite = TSVIONBytesGet(write_vio);
if (towrite != INT64_MAX) {
towrite += append_buffer_length;
}
data = my_data_alloc();
data->output_buffer = TSIOBufferCreate();
data->output_reader = TSIOBufferReaderAlloc(data->output_buffer);
/*获取output_vio,并且指定要向其传送的字节数,字节数就是http响应body的长度加上"hello world\n"的长度*/
data->output_vio = TSVConnWrite(output_conn, contp, data->output_reader, towrite);
TSContDataSet(contp, data);
}
/*如果input_buffer已经为空,说明上游不会再有数据传到transform插件*/
if (!TSVIOBufferGet(write_vio)) {
if (data->append_needed) {
data->append_needed = 0;
TSIOBufferCopy(TSVIOBufferGet(data->output_vio), append_buffer_reader, append_buffer_length, 0);
}
TSVIONBytesSet(data->output_vio, TSVIONDoneGet(write_vio) + append_buffer_length);
TSVIOReenable(data->output_vio);
return;
}
/*获取还有多少数据需要处理,也即还有多少东西会从上游发送来到transform插件*/
towrite = TSVIONTodoGet(write_vio);
if (towrite > 0) {
avail = TSIOBufferReaderAvail(TSVIOReaderGet(write_vio));
if (towrite > avail) {
towrite = avail;
}
if (towrite > 0) {
TSIOBufferCopy(TSVIOBufferGet(data->output_vio), TSVIOReaderGet(write_vio), towrite, 0);
TSIOBufferReaderConsume(TSVIOReaderGet(write_vio), towrite);
TSVIONDoneSet(write_vio, TSVIONDoneGet(write_vio) + towrite);
}
}
/*获取还有多少数据需要处理,也即还有多少东西会从上游发送来到transform插件*/
if (TSVIONTodoGet(write_vio) > 0) {
/*如果上游还有数据需要传送*/
if (towrite > 0) {
/*通知下游可以读数据了*/
TSVIOReenable(data->output_vio);
/*如果上游尚有数据会发送过来,需要通知上游可以继续发送数据了*/
TSContCall(TSVIOContGet(write_vio), TS_EVENT_VCONN_WRITE_READY, write_vio);
}
}
else {
/*如果上游已经没有更多的数据,需要执行append的逻辑了,也即将"hello world\n"添加到响应数据末尾*/
if (data->append_needed) {
data->append_needed = 0;
TSIOBufferCopy(TSVIOBufferGet(data->output_vio), append_buffer_reader, append_buffer_length, 0);
}
/*设置output_vio需要处理的字节数,这样output_vio处理完这些内容就可以发送TS_EVENT_VCONN_WRITE_COMPLETE
给transform插件了*/
TSVIONBytesSet(data->output_vio, TSVIONDoneGet(write_vio) + append_buffer_length);
/*通知下游可以读数据了*/
TSVIOReenable(data->output_vio);
/*如果上游尚有数据会发送过来,需要通知上游可以继续发送数据了*/
TSContCall(TSVIOContGet(write_vio), TS_EVENT_VCONN_WRITE_COMPLETE, write_vio);
}
}
static int append_transform(TSCont contp, TSEvent event, void *edata) {
/*如果transform插件已经被关闭,需要释放资源*/
if (TSVConnClosedGet(contp)) {
my_data_destroy(TSContDataGet(contp));
TSContDestroy(contp);
return 0;
} else {
switch (event) {
case TS_EVENT_ERROR: {
TSVIO write_vio;
write_vio = TSVConnWriteVIOGet(contp);
/*通知上游出错了*/
TSContCall(TSVIOContGet(write_vio), TS_EVENT_ERROR, write_vio);
}
break;
case TS_EVENT_VCONN_WRITE_COMPLETE:
/*如果下游触发了TS_EVENT_VCONN_WRITE_COMPLETE事件,需要关闭下游的VConnection*/
TSVConnShutdown(TSTransformOutputVConnGet(contp), 0, 1);
break;
case TS_EVENT_VCONN_WRITE_READY:
default:
handle_transform(contp);
break;
}
}
return 0;
}
static int transformable(TSHttpTxn txnp) {
TSMBuffer bufp;
TSMLoc hdr_loc;
TSHttpStatus resp_status;
TSHttpTxnServerRespGet(txnp, &bufp, &hdr_loc);
//判断源站给的状态码,只有源站返回200时才会执行transform逻辑
if (TS_HTTP_STATUS_OK == (resp_status = TSHttpHdrStatusGet(bufp, hdr_loc))) {
ASSERT_SUCCESS(TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc));
return 1;
} else {
ASSERT_SUCCESS(TSHandleMLocRelease(bufp, TS_NULL_MLOC, hdr_loc));
return 0;
}
}
static int transform_plugin(TSCont contp, TSEvent event, void *edata) {
TSHttpTxn txnp = (TSHttpTxn) edata;
switch (event) {
case TS_EVENT_HTTP_READ_RESPONSE_HDR:
//只有缓存查询结果为miss的时候才会回源,才会执行这个case的逻辑
if(transformable(txnp))
{
TSVConn connp;
connp = TSTransformCreate(append_transform, txnp);
TSHttpTxnHookAdd(txnp, TS_HTTP_RESPONSE_TRANSFORM_HOOK, connp);
}
TSHttpTxnReenable(txnp, TS_EVENT_HTTP_CONTINUE);
return 0;
default:
break;
}
return 0;
}
static int data_prepared() {
append_buffer = TSIOBufferCreate();
append_buffer_reader = TSIOBufferReaderAlloc(append_buffer);
TSIOBufferWrite(append_buffer, "hello world\n", sizeof("hello world\n") - 1);
append_buffer_length = TSIOBufferReaderAvail(append_buffer_reader);
return 1;
}
int check_ts_version() {
const char *ts_version = TSTrafficServerVersionGet();
int result = 0;
if (ts_version) {
int major_ts_version = 0;
int minor_ts_version = 0;
int patch_ts_version = 0;
if (sscanf(ts_version, "%d.%d.%d", &major_ts_version, &minor_ts_version, &patch_ts_version) != 3) {
return 0;
}
if (major_ts_version >= 2) {
result = 1;
}
}
return result;
}
void TSPluginInit(int argc, const char *argv[]) {
TSPluginRegistrationInfo info;
info.plugin_name = "append-transform";
info.vendor_name = "MyCompany";
info.support_email = "ts-api-support@MyCompany.com";
if (TSPluginRegister(TS_SDK_VERSION_3_0, &info) != TS_SUCCESS) {
TSError("Plugin registration failed.\n");
goto Lerror;
}
if (!check_ts_version()) {
TSError("Plugin requires Traffic Server 3.0 or later\n");
goto Lerror;
}
if (!data_prepared()) {
TSError("[append-transform] Failed to prepared data\n");
goto Lerror;
}
TSHttpHookAdd(TS_HTTP_READ_RESPONSE_HDR_HOOK,TSContCreate(transform_plugin, NULL));
return;
Lerror:
TSError("[append-transform] Unable to initialize plugin\n");
}
转载于:https://blog.51cto.com/11490450/1876931