transform插件

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 plugin对应一个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事件。

重点函数:

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 = "[email protected]";

	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");
}
时间: 2024-11-05 19:27:42

transform插件的相关文章

使用 Browserify 时引入文本文件(style和template)

/* http://prismjs.com/download.html?themes=prism&languages=markup+css+clike+javascript+bash+css-extras+handlebars+jade+java+less+php+php-extras+python+scss+typescript */ /** * prism.js default theme for JavaScript, CSS and HTML * Based on dabblet (ht

Android组件化最佳实践 ARetrofit原理

简介 ARetrofit是一款针对Android组件之间通信的框架,实现组件之间解耦的同时还可以通信. 源码链接:https://github.com/yifei8/ARetrofit欢迎star.issues.fork 组件化 Android组件化已经不是一个新鲜的概念了,出来了已经有很长一段时间了,大家可以自行Google,可以看到一堆相关的文章. 简单的来说,所谓的组件就是Android Studio中的Module,每一个Module都遵循高内聚的原则,通过ARetrofit来实现无耦合

OpenStack监控架构解析

1  采集模块整体架构 采集模块主要分为三大块. Ceilometer:用于采集数据并处理数据后发送到gnocchi服务去存储 Gnocchi:用于将采集数据进行计算合并和存储并提供rest api方式接收和查询监控数据 Aodh:主要负责告警功能 1.1  Ceilometer架构 Ceilometer-polling服务:通过调用多个采集插件(采集插件在setup.cfg中有定义,ceilometer.poll.compute对应的就是采集插件)收集信息,这个服务收集的是有关虚拟机资源使用情

jquery插件--h5移动设备自适应 transform scale

// 创建一个闭包 (function($) { // 插件的定义 $.fn.scale = function(options) { var obj = this; var opts = $.extend({}, $.fn.scale.defaults, options); init(obj, opts); $(window).resize(function(event) { init(obj, opts); }); }; function init(obj, opts){ var w = $(

可替代CSS3 transition和transform的jQuery插件

jQuery Transit是一款可制作超级平滑的CSS3 transformations 和 transitions动画的jQuery插件.它能够通过jquery来完成CSS转换和过渡动画效果,这对于一些不支持css3转换和过渡属性的浏览器来说是一个非常有用的jquery插件. 在线演示:http://www.htmleaf.com/Demo/201501281290.html 下载地址:http://www.htmleaf.com/jQuery/Layout-Interface/201501

unity(Exploder插件)研究

哎 好久没写博客了 不是因为最近忙 而是比较懒 学的东西不深入 前段时间发现一个很好用的插件叫Exploder(是一个可以制作任何物体的爆炸效果) 好!我们开始我们的炸学校旅程!(O(∩_∩)O哈哈~ 开玩笑了) 不过这次我们使用的模型就是一个学校模型 首先:我们在学校中安装一个炸弹 --将插件中的Exploder预制体拖进去 选中要爆炸的物体 tag层设置为Exploder 开始撸代码~~~ using System.Collections; using System.Collections.

移动前端滑动插件——JRoll面世

又过了一年,终于,第三篇博文要出炉了. 去年9月底,结束创业生涯后,我进入了一家外包公司从事移动前端工作,洽洽这年html5火到要爆,而具备html5技能的工程师却千里难觅,虽然我一直从事PC端的工作,但凭借扎实的js基础,也谋了个中级工程师的职位.多学点东西准没错的. 从事正规的前端工作后,我接触到了underscore.ratchet.backbone.requirejs.seajs.cordova.angular等等一大堆前端框架工具,不禁感慨,原来我以前的圈子是那么的渺小.iscroll

jQuery.fly插件实现添加购物车抛物线效果

样例 使用电商 APP 购买商品时,很多都有上图的红色小球抛物线效果,下面通过 jQuery.fly 插件来实现一个简单 Demo. 实现 简单思路: 确定抛物线的起始和终止位置: 通过 js 在起始位置创建一个 document 对象,作为红色小球: 通过 jQuery.fly 插件提供的fly函数来移动小球,移动至终止位置: 当小球到达终止位置后,通过fly插件提供的 onEnd 回调函数,将小球销毁: Demo 源码: <!DOCTYPE html> <html lang=&quo

来一个朴素的验证码小插件

随机倾斜,随机颜色的小验证码插件.<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>验证码</title> <style> #check { width: 100px; height: 20px; } #showResult{ height: 20px; } </style></h