添加了bltJsonDecode,bltJsonEncode,bltMd5,bltStr,bltCount,bltUnset等模块函数,此外,根据访问的文件名后缀设置响应头中的Content-Type,例如:.html设置为text/html,.css设置为text/css等,对于静态文件,会在响应头中输出Last-Modified,当客户端的请求头中包含了If-Modified-Since字段时,会将该字段的时间值,与所访问的静态文件的修改时间进行比较,如果两个时间相同,则返回304状态码...

    页面导航: 项目下载地址:

    zenglServer源代码的相关地址:https://github.com/zenglong/zenglServer  当前版本对应的tag标签为:v0.8.1

zenglServer v0.8.0:

    该版本在builtin内建模块中添加了如下模块函数:

    bltJsonDecode:用于对json字符串进行解码

    bltJsonEncode:用于json编码操作,同时修复json.c的Bug

    bltMd5:用于生成字符串的md5值,添加md5.c和md5.h文件,md5.c中包含了生成md5的算法

    bltStr:返回参数的字符串形式

    bltCount:获取数组的有效成员数,或者获取字符串的有效长度

    bltUnset:将参数所引用的变量或数组元素或类成员等重置为未初始化状态

    bltGetZenglServerVersion: 获取zenglServer的版本号

    bltGetZenglVersion: 获取zengl语言的版本号

    这些模块函数的C源码都定义在module_builtin.c文件中:

/**
 * bltJsonDecode模块函数,将字符串进行json解码
 * 例如:
 * json = '{"hello": "world!!", "name": "zengl", "val": "programmer", "arr":[1,2,3]}';
 * json = bltJsonDecode(json);
 * for(i=0; bltIterArray(json,&i,&k,&v); )
 *	if(k == 'arr')
 *		print 'arr:<br/>';
 *		for(j=0; bltIterArray(v,&j,&inner_k,&inner_v); )
 *			print ' -- ' + inner_k +": " + inner_v + '<br/>';
 *		endfor
 *	else
 *		print k +": " + v + '<br/>';
 *	endif
 * endfor
 * 执行结果如下:
 * hello: world!!
 * name: zengl
 * val: programmer
 * arr:
 * -- 0: 1
 * -- 1: 2
 * -- 2: 3
 * 上面将json字符串解码为了zengl数组
 * 第二个参数max_depth用于设置json最多解析的对象或数组的层次
 * 例如,将上面代码json = bltJsonDecode(json);修改为json = bltJsonDecode(json,1);后,执行时就会报500错误
 * 并在日志中输出 user defined error: json depth 2 is big than 1 的错误信息,也就是只能解析一层json对象或数组
 * 第三个参数max_memory用于设置json解析最多可以使用的内存,例如:
 * 将代码修改为:json = bltJsonDecode(json,2,400);表示最多解析两层json对象或数组,同时,最多只能分配400字节的内存,
 * 如果json解析时,使用的内存超过400字节时,就会报500错误,同时日志中输出
 * user defined error: bltJsonDecode error: Unable to parse data, json error: Memory allocation failure 的错误信息
 * 表示内存分配失败,这里是由于内存超出允许使用的最大值而引起的
 */
ZL_EXP_VOID module_builtin_json_decode(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
	if(argcount < 1)
		zenglApi_Exit(VM_ARG,"usage: bltJsonDecode(str[, max_depth[, max_memory]])");
	zenglApi_GetFunArg(VM_ARG,1,&arg); //得到第一个参数
	if(arg.type != ZL_EXP_FAT_STR)
		zenglApi_Exit(VM_ARG,"first argument str of bltJsonDecode must be string");
	json_char * json = (json_char *)arg.val.str;
	json_settings settings = { 0 };
	settings.mem_alloc = my_json_mem_alloc;
	settings.mem_free = my_json_mem_free;
	settings.user_data = VM_ARG;
	settings.settings = json_enable_comments;
	unsigned int max_depth = 1000;
	if(argcount >= 2) {
		zenglApi_GetFunArg(VM_ARG,2,&arg); //得到第二个参数
		if(arg.type != ZL_EXP_FAT_INT)
			zenglApi_Exit(VM_ARG,"the second argument max_depth of bltJsonDecode must be integer");
		max_depth = (unsigned int)arg.val.integer;
		if(argcount >= 3) {
			zenglApi_GetFunArg(VM_ARG,3,&arg); //得到第三个参数
			if(arg.type != ZL_EXP_FAT_INT)
				zenglApi_Exit(VM_ARG,"the third argument max_memory of bltJsonDecode must be integer");
			settings.max_memory = (unsigned long)arg.val.integer;
		}
	}
	json_char json_error_str[json_error_max];
	json_value * value;
	size_t json_length = strlen(json);
	// 通过json-parser第三方解析程式来解析会话文件中的json数据,解析的结果是一个json_value结构
	value = json_parse_ex (&settings, json, json_length, json_error_str);
	if (value == NULL) {
		zenglApi_Exit(VM_ARG,"bltJsonDecode error: Unable to parse data, json error: %s", json_error_str);
	}
	ZENGL_EXPORT_MEMBLOCK memblock;
	switch (value->type) {
	case json_none: // 将json中的null转为整数0
		zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_INT, ZL_EXP_NULL, 0, 0);
		break;
	case json_object:
	case json_array:
		// 如果是json对象或json数组,则创建一个memblock内存块
		if(zenglApi_CreateMemBlock(VM_ARG,&memblock,0) == -1) {
			zenglApi_Exit(VM_ARG,zenglApi_GetErrorString(VM_ARG));
		}
		// 通过process_json_object_array函数,循环将value中的json成员填充到memblock中
		process_json_object_array(VM_ARG, &memblock, value, 1, max_depth);
		zenglApi_SetRetValAsMemBlock(VM_ARG,&memblock);
		break;
	case json_integer:
		zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_INT, ZL_EXP_NULL, (ZL_EXP_LONG)value->u.integer, 0);
		break;
	case json_double:
		zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_FLOAT, ZL_EXP_NULL, 0, value->u.dbl);
		break;
	case json_string:
		zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_STR, value->u.string.ptr, 0, 0);
		break;
	case json_boolean: // 将json中的bool类型转为整数,例如:true转为1,false转为0
		zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_INT, ZL_EXP_NULL, (ZL_EXP_LONG)value->u.boolean, 0);
		break;
	default:
		zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_INT, ZL_EXP_NULL, 0, 0);
		break;
	}
	json_value_free_ex (&settings, value);
}

/**
 * bltJsonEncode模块函数,将data参数进行json编码,返回json格式的字符串
 * 例如:
 * array['username'] = 'zenglong';
 * array['password'] = '123456';
 * tmp = bltArray(100,200,300,400,500,600);
 * array['tmp'] = tmp;
 * json = bltJsonEncode(array);
 * print 'array转json字符串:<br/>';
 * print json + '<br/><br/>';
 * 执行结果如下:
 * array转json字符串:
 * {"username":"zenglong","password":"123456","tmp":[100,200,300,400,500,600]}
 * 上面是数组转json的例子,对于整数,浮点数,直接返回整数和浮点数的字符串形式
 * 对于字符串类型的参数,直接将字符串的原值返回
 * 其他类型的参数都返回null字符串
 */
ZL_EXP_VOID module_builtin_json_encode(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
	if(argcount < 1)
		zenglApi_Exit(VM_ARG,"usage: bltJsonEncode(data)");
	zenglApi_GetFunArg(VM_ARG,1,&arg);
	BUILTIN_INFO_STRING infoString = { 0 };
	switch(arg.type) {
	case ZL_EXP_FAT_MEMBLOCK:
		// 通过builtin_write_array_to_string函数将zengl数组转为json格式的字符串
		builtin_write_array_to_string(VM_ARG, &infoString, arg.val.memblock);
		break;
	case ZL_EXP_FAT_INT:
		builtin_make_info_string(VM_ARG, &infoString, "%ld",arg.val.integer);
		break;
	case ZL_EXP_FAT_FLOAT:
		builtin_make_info_string(VM_ARG, &infoString, "%.16g",arg.val.floatnum);
		break;
	case ZL_EXP_FAT_STR:
		builtin_make_info_string(VM_ARG, &infoString, "%s",arg.val.str);
		break;
	default:
		zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_STR, "null", 0, 0);
		return;
	}
	if(infoString.str != NULL) {
		zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_STR, infoString.str, 0, 0);
		zenglApi_FreeMem(VM_ARG, infoString.str);
	}
	else
		zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_STR, "null", 0, 0);
}

/**
 * bltMd5模块函数,获取字符串的md5值,第一个参数str是要转成md5的字符串
 * 第二个参数isLowerCase表示是否生成小写的md5值(默认值是1,也就是小写,将该参数设置为0,可以生成大写的md5值)
 * 第三个参数is32表示是否生成32位的md5值(默认值是1,也就是32位,将该参数设置为0,可以生成16位的md5值)
 * 例如:
 * def MD5_LOWER_CASE 1;
 * def MD5_UPPER_CASE 0;
 * def MD5_32BIT 1;
 * def MD5_16BIT 0;
 * print '"admin@123456"的md5值:<br/>';
 * print bltMd5('admin@123456') + ' [32位小写]<br/>';
 * print bltMd5('admin@123456', MD5_UPPER_CASE) + ' [32位大写]<br/>';
 * print bltMd5('admin@123456', MD5_LOWER_CASE, MD5_16BIT) + ' [16位小写]<br/>';
 * print bltMd5('admin@123456', MD5_UPPER_CASE, MD5_16BIT) + ' [16位大写]<br/><br/>';
 * 执行结果如下:
 * "admin@123456"的md5值:
 * f19b8dc2029cf707939e886e4b164681 [32位小写]
 * F19B8DC2029CF707939E886E4B164681 [32位大写]
 * 029cf707939e886e [16位小写]
 * 029CF707939E886E [16位大写]
 */
ZL_EXP_VOID module_builtin_md5(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
	if(argcount < 1)
		zenglApi_Exit(VM_ARG,"usage: bltMd5(str[, isLowerCase[, is32]])");
	zenglApi_GetFunArg(VM_ARG,1,&arg);
	if(arg.type != ZL_EXP_FAT_STR)
		zenglApi_Exit(VM_ARG,"first argument str of bltMd5 must be string");
	MD5_CTX md5;
	MD5Init(&md5);
	unsigned char * encrypt = (unsigned char *)arg.val.str;
	unsigned char decrypt[16];
	MD5Update(&md5,encrypt,strlen((char *)encrypt));
	MD5Final(&md5,decrypt);
	int isLowerCase = ZL_EXP_TRUE;
	int is32 = ZL_EXP_TRUE;
	if(argcount >= 2) {
		zenglApi_GetFunArg(VM_ARG,2,&arg);
		if(arg.type != ZL_EXP_FAT_INT)
			zenglApi_Exit(VM_ARG,"the second argument isLowerCase of bltMd5 must be integer");
		isLowerCase = arg.val.integer;
		if(argcount >= 3) {
			zenglApi_GetFunArg(VM_ARG,3,&arg);
			if(arg.type != ZL_EXP_FAT_INT)
				zenglApi_Exit(VM_ARG,"the third argument is32 of bltMd5 must be integer");
			is32 = arg.val.integer;
		}
	}
	char buf[33];
	char * p = buf;
	int start_idx = is32 ? 0 : 4;
	int end_idx = is32 ? 16 : 12;
	const char * format = isLowerCase ? "%02x" : "%02X";
	for(int i = start_idx; i < end_idx; i++) {
		sprintf(p, format, decrypt[i]);
		p += 2;
	}
	(*p) = '\0';
	zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_STR, buf, 0, 0);
}

/**
 * bltStr模块函数,返回第一个参数的字符串形式
 * 如果第一个参数的值为NONE类型,要获取他对应的字符串形式即空字符串,需要将第一个参数的引用传递过来
 * 例如:
 * print 'bltStr(test): "' + bltStr(test) + '"<br/>';
 * print 'bltStr(&test): "' + bltStr(&test) + '"<br/>';
 * 执行的结果如下:
 * bltStr(test): "0"
 * bltStr(&test): ""
 * 上面test在没有被赋值的情况下,是NONE类型,NONE类型变量在参与运算或者以参数形式传递给函数时,是以整数0的形式进行运算和传递的
 * 因此,要将NONE转为空字符串返回,需要将test的引用传递进去
 * 如果将第二个参数设置为非0值,bltStr会同时将转化的结果赋值给第一个参数(需要将第一个参数的引用传递进来)
 * 例如:
 * def TRUE 1;
 * def FALSE 0;
 * print 'test: "' + test + '"<br/>';
 * bltStr(&test, TRUE);
 * print 'test: "' + test + '"<br/><br/>';
 * 执行结果如下:
 * test: "0"
 * test: ""
 * 在经过bltStr(&test, TRUE);转化后,test就被赋值为了空字符串
 */
ZL_EXP_VOID module_builtin_str(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
	if(argcount < 1)
		zenglApi_Exit(VM_ARG,"usage: bltStr(data|&data[, isSetData=0])");
	zenglApi_GetFunArg(VM_ARG,1,&arg);
	int isSetData = ZL_EXP_FALSE;
	if(argcount >= 2) {
		ZENGL_EXPORT_MOD_FUN_ARG arg2 = {ZL_EXP_FAT_NONE,{0}};
		zenglApi_GetFunArg(VM_ARG,2,&arg2);
		if(arg2.type != ZL_EXP_FAT_INT)
			zenglApi_Exit(VM_ARG,"the second argument isSetData of bltStr must be integer");
		isSetData = arg2.val.integer;
	}
	char * retstr;
	char tmpstr[40];
	switch(arg.type) {
	case ZL_EXP_FAT_STR:
		retstr = arg.val.str;
		break;
	case ZL_EXP_FAT_INT:
		snprintf(tmpstr, 40, "%ld", arg.val.integer);
		retstr = tmpstr;
		break;
	case ZL_EXP_FAT_FLOAT:
		snprintf(tmpstr, 40, "%.16g", arg.val.floatnum);
		retstr = tmpstr;
		break;
	case ZL_EXP_FAT_MEMBLOCK:
		retstr = "[array or class obj type]";
		break;
	default:
		retstr = "";
		break;
	}
	if(isSetData) {
		if(arg.type != ZL_EXP_FAT_STR) {
			arg.type = ZL_EXP_FAT_STR;
			arg.val.str = retstr;
			zenglApi_SetFunArg(VM_ARG,1,&arg);
		}
	}
	zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_STR, retstr, 0, 0);
}

/**
 * bltCount模块函数,获取数组的有效成员数,或者获取字符串的有效长度
 */
ZL_EXP_VOID module_builtin_count(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
	if(argcount < 1)
		zenglApi_Exit(VM_ARG,"usage: bltCount(data)");
	zenglApi_GetFunArg(VM_ARG,1,&arg);
	int retcount;
	switch(arg.type) {
	case ZL_EXP_FAT_STR:
		retcount = strlen(arg.val.str);
		break;
	case ZL_EXP_FAT_MEMBLOCK:
		retcount = zenglApi_GetMemBlockNNCount(VM_ARG, &arg.val.memblock);
		break;
	default:
		retcount = 0;
		break;
	}
	zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_INT, ZL_EXP_NULL, retcount, 0);
}

/**
 * bltGetZenglServerVersion模块函数,获取zenglServer的版本号
 */
ZL_EXP_VOID module_builtin_get_zengl_server_version(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
	ZENGL_EXPORT_MEMBLOCK memblock;
	if(zenglApi_CreateMemBlock(VM_ARG,&memblock,0) == -1) {
		zenglApi_Exit(VM_ARG,zenglApi_GetErrorString(VM_ARG));
	}
	arg.type = ZL_EXP_FAT_INT;
	arg.val.integer = ZLSERVER_MAJOR_VERSION;
	zenglApi_SetMemBlock(VM_ARG,&memblock,1,&arg);
	arg.val.integer = ZLSERVER_MINOR_VERSION;
	zenglApi_SetMemBlock(VM_ARG,&memblock,2,&arg);
	arg.val.integer = ZLSERVER_REVISION;
	zenglApi_SetMemBlock(VM_ARG,&memblock,3,&arg);
	zenglApi_SetRetValAsMemBlock(VM_ARG,&memblock);
}

/**
 * bltGetZenglVersion模块函数,获取zengl语言的版本号
 */
ZL_EXP_VOID module_builtin_get_zengl_version(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
	ZENGL_EXPORT_MEMBLOCK memblock;
	if(zenglApi_CreateMemBlock(VM_ARG,&memblock,0) == -1) {
		zenglApi_Exit(VM_ARG,zenglApi_GetErrorString(VM_ARG));
	}
	arg.type = ZL_EXP_FAT_INT;
	arg.val.integer = ZL_EXP_MAJOR_VERSION;
	zenglApi_SetMemBlock(VM_ARG,&memblock,1,&arg);
	arg.val.integer = ZL_EXP_MINOR_VERSION;
	zenglApi_SetMemBlock(VM_ARG,&memblock,2,&arg);
	arg.val.integer = ZL_EXP_REVISION;
	zenglApi_SetMemBlock(VM_ARG,&memblock,3,&arg);
	zenglApi_SetRetValAsMemBlock(VM_ARG,&memblock);
}

/**
 * builtin模块的初始化函数,里面设置了与该模块相关的各个模块函数及其相关的处理句柄
 */
ZL_EXP_VOID module_builtin_init(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT moduleID)
{
	.....................................
	zenglApi_SetModFunHandle(VM_ARG,moduleID,"bltUnset",zenglApiBMF_unset);
	.....................................
	zenglApi_SetModFunHandle(VM_ARG,moduleID,"bltJsonDecode",module_builtin_json_decode);
	zenglApi_SetModFunHandle(VM_ARG,moduleID,"bltJsonEncode",module_builtin_json_encode);
	zenglApi_SetModFunHandle(VM_ARG,moduleID,"bltMd5",module_builtin_md5);
	zenglApi_SetModFunHandle(VM_ARG,moduleID,"bltStr",module_builtin_str);
	zenglApi_SetModFunHandle(VM_ARG,moduleID,"bltCount",module_builtin_count);
	zenglApi_SetModFunHandle(VM_ARG,moduleID,"bltGetZenglServerVersion",module_builtin_get_zengl_server_version);
	zenglApi_SetModFunHandle(VM_ARG,moduleID,"bltGetZenglVersion",module_builtin_get_zengl_version);
}


    这些模块函数的测试脚本 my_webroot/v0_8_0/test.zl 的代码如下:

use builtin;

def TRUE 1;
def FALSE 0;
def MD5_LOWER_CASE 1;
def MD5_UPPER_CASE 0;
def MD5_32BIT 1;
def MD5_16BIT 0;

print '<!Doctype html>
<html>
<head><meta http-equiv="content-type" content="text/html;charset=utf-8" />
<title>json编解码测试</title>
</head>
<body>';

json = '{"hello": "world!!", "name": "zengl", "val": "programmer", "arr":[1,2,3]}';

json = bltJsonDecode(json);
//json = bltJsonDecode(json,1);
//json = bltJsonDecode(json,2,400);

for(i=0; bltIterArray(json,&i,&k,&v); )
	if(k == 'arr')
		print 'arr:<br/>';
		for(j=0; bltIterArray(v,&j,&inner_k,&inner_v); )
			print ' -- ' + inner_k +": " + inner_v + '<br/>';
		endfor
	else
		print k +": " + v + '<br/>';
	endif
endfor

array['username'] = 'zenglong';
array['password'] = '123456';
tmp = bltArray(100,200,300,400,500,600);
array['tmp'] = tmp;
json = bltJsonEncode(array);

print '<br/>';
print 'array转json字符串:<br/>';
print json + '<br/><br/>';

print '"admin@123456"的md5值:<br/>';
print bltMd5('admin@123456') + ' [32位小写]<br/>';
print bltMd5('admin@123456', MD5_UPPER_CASE) + ' [32位大写]<br/>';
print bltMd5('admin@123456', MD5_LOWER_CASE, MD5_16BIT) + ' [16位小写]<br/>';
print bltMd5('admin@123456', MD5_UPPER_CASE, MD5_16BIT) + ' [16位大写]<br/><br/>';

print 'bltStr(test): "' + bltStr(test) + '"<br/>';
print 'bltStr(&test): "' + bltStr(&test) + '"<br/>';
print 'test: "' + test + '"<br/>';
bltStr(&test, TRUE);
print 'test: "' + test + '"<br/><br/>';

print 'bltCount(array): ' + bltCount(array) + '<br/>';
print 'bltCount("hello world"): ' + bltCount("hello world") + '<br/><br/>';

bltUnset(&array['tmp']);
print 'array after unset:<br/>';
print bltJsonEncode(array) + '<br/>';
print 'bltCount(array): ' + bltCount(array) + '<br/><br/>';

zls_version = bltGetZenglServerVersion();
print 'zenglServer版本号:' + zls_version[0] + '.' + zls_version[1] + '.' + zls_version[2] + '<br/>';
zl_version = bltGetZenglVersion();
print 'zengl语言版本号:' + zl_version[0] + '.' + zl_version[1] + '.' + zl_version[2];

print '</body></html>';


    该脚本的执行结果如下:


图1:test.zl的执行结果

    该版本支持自定义HTTP响应状态码,测试脚本 my_webroot/v0_8_0/test2.zl:

use builtin, request;

rqtSetResponseHeader("HTTP/1.1 302 Moved Temporarily");
rqtSetResponseHeader("Location: test.zl");
bltExit();


    该脚本会通过302跳转到test.zl:


图2:浏览器中test2.zl跳转到test.zl


图3:logfile日志中记录的302跳转信息

    该版本还在session模块中增加了sessDelete模块函数,该模块函数可以根据会话文件名来删除会话文件,该模块函数的C源码定义在module_session.c文件中:

/**
 * sessDelete模块函数,根据sess_file_name会话文件名删除会话文件
 */
ZL_EXP_VOID module_session_delete(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
	if(argcount != 1)
		zenglApi_Exit(VM_ARG,"usage:sessDelete(sess_file_name)");
	// 获取第一个参数,也就是会话文件名
	zenglApi_GetFunArg(VM_ARG,1,&arg);
	if(arg.type != ZL_EXP_FAT_STR) {
		zenglApi_Exit(VM_ARG,"the first argument [sess_file_name] of sessDelete must be string");
	}
	// 如果是空的会话文件名,直接返回0
	if(strlen(arg.val.str) == 0) {
		zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_INT, ZL_EXP_NULL, 0, 0);
		return;
	}
	char filename[SESSION_FILEPATH_MAX_LEN];
	char * session_dir;
	long session_expire;
	// 先通过main_get_session_config函数获取会话目录,然后根据会话目录和会话文件名生成会话文件的相对路径
	main_get_session_config(&session_dir, &session_expire, NULL);
	session_make_filename(filename, session_dir, arg.val.str);

	struct stat filestatus;
	if ( stat(filename, &filestatus) == 0) {
		remove(filename); // 删除会话文件
	}
	zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_INT, ZL_EXP_NULL, 1, 0);
}


    sessDelete模块函数的测试脚本为 my_webroot/v0_8_0/test_sess_delete.zl:

use builtin, request, session;
def TRUE 1;

print '<!Doctype html>
<html>
<head><meta http-equiv="content-type" content="text/html;charset=utf-8" />
<title>删除会话测试</title>
</head>
<body>';

cookies = rqtGetCookie();
bltStr(&cookies['SESSION'], TRUE);
if(bltCount(cookies['SESSION']) == 0)
	print 'cookie中的SESSION为空,没有要删除的会话!';
else
	sess_data = sessGetData(cookies['SESSION']);
	if(bltCount(sess_data) > 0)
		sessDelete(cookies['SESSION']);
		rqtSetResponseHeader("Set-Cookie: SESSION=; path=/; expires=Thu, 01 Jan 1970 00:00:01 GMT;");
		print '删除会话: ' + cookies['SESSION'] + ' 成功!';
	else
		print '会话: ' + cookies['SESSION'] + ' 不存在,或者已经被删除!';
	endif
endif

print '</body></html>';


    该脚本会检测cookie中是否包含SESSION,如果包含且存在相应的会话数据,就调用sessDelete将会话文件删除掉,同时通过Set-Cookie响应头将客户端Cookie中的SESSION记录给删除掉。

    测试时,可以先通过 my_webroot/v0_6_0/test_write_sess.zl 脚本生成会话,再通过该脚本来测试删除会话:


图4:创建会话


图5:查看会话数据


图6:删除会话

    该版本还调整了module_mysql.c的代码,在调用mysql相关的模块函数时,会先校验mysql连接或者结果集是否有效:

/**
 * 判断指针con对应的连接,是否是有效的mysql连接
 */
static ZL_EXP_BOOL is_valid_mysql_connection(RESOURCE_LIST * resource_list, void * con)
{
	int ret = resource_list_get_ptr_idx(resource_list, con, module_mysql_free_connection_resource_callback);
	if(ret >= 0)
		return ZL_EXP_TRUE;
	else
		return ZL_EXP_FALSE;
}

/**
 * 判断指针res对应的结果集,是否是有效的mysql结果集
 */
static ZL_EXP_BOOL is_valid_mysql_result(RESOURCE_LIST * resource_list, void * res)
{
	int ret = resource_list_get_ptr_idx(resource_list, res, module_mysql_free_result_resource_callback);
	if(ret >= 0)
		return ZL_EXP_TRUE;
	else
		return ZL_EXP_FALSE;
}

.................................................

/**
 * mysqlGetServerVersion模块函数对应的C函数
 * 通过mysql_get_server_version官方库函数,获取服务端的版本号信息,
 * 主版本,子版本,修正版本号会依次存储在返回数组的前三个成员中
 */
ZL_EXP_VOID module_mysql_get_server_version(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	.....................................................
	MYSQL *con = (MYSQL *)arg.val.integer;
	MAIN_DATA * my_data = zenglApi_GetExtraData(VM_ARG, "my_data");
	if(!is_valid_mysql_connection(&(my_data->resource_list), con)) {
		zenglApi_Exit(VM_ARG,"mysqlGetServerVersion runtime error: invalid connection");
	}
	.....................................................
}

.................................................

/**
 * mysqlFreeResult模块函数对应的C函数
 * 通过mysql_free_result官方库函数,将结果指针中的mysql_res给释放掉,再通过zenglApi_FreeMem接口将结果指针释放掉
 * 最后通过resource_list_remove_member函数,将结果指针从资源列表中移除
 */
ZL_EXP_VOID module_mysql_free_result(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	.....................................................
	MODULE_MYSQL_RES * result = (MODULE_MYSQL_RES *)arg.val.integer;
	MAIN_DATA * my_data = zenglApi_GetExtraData(VM_ARG, "my_data");
	if(!is_valid_mysql_result(&(my_data->resource_list), result)) {
		zenglApi_Exit(VM_ARG,"mysqlFreeResult runtime error: invalid result");
	}
	.....................................................
}

.................................................


zenglServer v0.8.1:

    v0.8.1的版本会根据访问的文件名后缀设置响应头中的Content-Type,例如:.html设置为text/html,.css设置为text/css等。IE高版本浏览器,css样式文件如果没有Content-Type,会报Mime类型不匹配而被忽略的警告信息,从而导致样式不生效。

    对于zengl脚本,如果在脚本中没有自定义HTTP状态码,并且没有设置过Content-Type响应头的话,默认会设置text/html的内容类型。

    对于静态文件,会在响应头中输出Last-Modified,并且,当客户端的请求头中包含了If-Modified-Since字段时,会将该字段的时间值,与所访问的静态文件的修改时间进行比较,如果两个时间相同,则返回304状态码。

    如果访问的是非常规文件,例如访问的是目录的话,会返回403状态码,表示禁止访问。

    该版本主要改动的C源文件是main.c:

...............................................

/**
 * 根据文件名后缀检测内容类型的结构体
 */
typedef struct _SERVER_CONTENT_TYPE{
	const char * suffix; // 文件名后缀
	int suffix_length;   // 后缀长度
	const char * content_type; // 内容类型
} SERVER_CONTENT_TYPE;

...............................................

// server_content_types数组中存储了文件名后缀与内容类型之间的对应关系
static SERVER_CONTENT_TYPE server_content_types[] = {
	{".html", 5, "text/html"},
	{".css", 4, "text/css"},
	{".js", 3, "application/javascript"},
	{".png", 4, "image/png"},
	{".jpg", 4, "image/jpeg"},
	{".jpeg", 5, "image/jpeg"},
	{".gif", 4, "image/gif"},
	{".ico", 4, "image/x-icon"}
};

// server_content_types数组的成员个数
static int server_content_types_number = 8;

/**
 * 通过检测文件名后缀,在响应头中输出相应的Content-Type内容类型(IE高版本浏览器,css样式文件如果没有Content-Type,会报Mime类型不匹配而被忽略的警告信息,从而导致样式不生效)
 */
static int main_output_content_type(char * full_path, CLIENT_SOCKET_LIST * socket_list, int lst_idx)
{
	int full_length = strlen(full_path);
	for(int i=0; i < server_content_types_number; i++) {
		SERVER_CONTENT_TYPE * sct = &server_content_types[i];
		if(full_length > sct->suffix_length &&
			(full_path[full_length -1] == sct->suffix[sct->suffix_length - 1])) {
			if(strncmp(full_path + (full_length - sct->suffix_length), sct->suffix, sct->suffix_length) == 0) {
				client_socket_list_append_send_data(socket_list, lst_idx, "Content-Type: ", 14);
				client_socket_list_append_send_data(socket_list, lst_idx, (char *)sct->content_type, strlen(sct->content_type));
				client_socket_list_append_send_data(socket_list, lst_idx, "\r\n", 2);
				return 1;
			}
		}
	}
	return 0;
}

/**
 * 将buffer字符串解析成相应的时间结构
 */
static void main_parse_date(const char *buffer, struct tm *date) {
	int len;
	char firstElement[20];
	sscanf(buffer, "%s", firstElement);
	len = strlen(firstElement);

	switch (len) {
	/* RFC 822, updated by RFC 1123; firstElement "[wkday]," */
	case 4:
		strptime(buffer, "%a, %d %b %Y %T GMT", date);
		break;
		/*  ANSI C's asctime() format; firstElement "[wkday]" */
	case 3:
		strptime(buffer, "%a %b %d %T %Y", date);
		break;
		/* RFC 850, obsoleted by RFC 1036; firstElement "[weekdey],
		 * " */
	default:
		strptime(buffer, "%A, %d-%b-%y %T GMT", date);
	}
}

/**
 * 比较两个时间结构,判断他们是否相等
 */
static int main_compare_dates(const struct tm *date1, const struct tm *date2) {
	time_t sec1;
	time_t sec2;

	sec1 = mktime((struct tm*) date1);
	sec2 = mktime((struct tm*) date2);

	return (sec2 - sec1);
}

/**
 * 根据静态文件的修改时间,生成Last-Modified响应头
 */
static void main_output_last_modified(struct stat * filestatus, CLIENT_SOCKET_LIST * socket_list, int lst_idx)
{
	char dateLine[60];
	struct tm * tempDate;
	tempDate = gmtime(&filestatus->st_mtim.tv_sec);
	strftime(dateLine, 60, "Last-Modified: %a, %d %b %Y %T GMT\r\n", tempDate);
	client_socket_list_append_send_data(socket_list, lst_idx, dateLine, strlen(dateLine));
}

/**
 * 如果客户的的请求头中包含了If-Modified-Since字段的话,就将该字段的时间值,与所访问的静态文件的修改时间进行比较
 * 如果两个时间相同,则返回304状态码
 */
static void main_process_if_modified_since(char * request_header, int request_header_count,
		struct stat * filestatus, CLIENT_SOCKET_LIST * socket_list, int lst_idx, int * status_code)
{
	const char * if_modified_since = "If-Modified-Since";
	int if_modified_since_length = strlen(if_modified_since);
	char * tmp = request_header;
	char * end = request_header + request_header_count;
	do{
		ZL_EXP_CHAR * field = tmp;
		ZL_EXP_CHAR * value = field + strlen(field) + 1;
		if(field >= end || value >= end) {
			break;
		}
		int field_len = strlen(field);
		if(field_len == if_modified_since_length) {
			if(!strcasecmp(field, if_modified_since)) {
				struct tm reqestedDate;
				main_parse_date(value, &reqestedDate);
				struct tm * fileModDate = gmtime(&(filestatus->st_mtime));
				if(!main_compare_dates(&reqestedDate, fileModDate)) {
					if((*status_code) == 200) {
						(*status_code) = 304;
					}
					return;
				}
			}
		}
		tmp = value + strlen(value) + 1;
	} while(1);
}

..................................................
static int routine_process_client_socket(CLIENT_SOCKET_LIST * socket_list, int lst_idx)
{
	..................................................
	// 如果doc_fd大于0,则直接输出相关的静态文件的内容
	if(doc_fd > 0) {
		client_socket_list_append_send_data(socket_list, lst_idx, "HTTP/1.1 ", 9);
		ZL_EXP_BOOL is_reg_file = ZL_EXP_TRUE;
		// 非常规文件,直接返回403禁止访问
		if(!S_ISREG(filestatus.st_mode)) {
			status_code = 403;
			is_reg_file = ZL_EXP_FALSE;
		}
		else
			main_process_if_modified_since(parser_data->request_header.str, parser_data->request_header.count, &filestatus, socket_list, lst_idx, &status_code);
		switch(status_code){
		case 403:
			client_socket_list_append_send_data(socket_list, lst_idx, "403 Forbidden\r\n", 15);
			break;
		case 404:
			client_socket_list_append_send_data(socket_list, lst_idx, "404 Not Found\r\n", 15);
			break;
		case 200:
			client_socket_list_append_send_data(socket_list, lst_idx, "200 OK\r\n", 8);
			client_socket_list_append_send_data(socket_list, lst_idx, "Cache-Control: public, max-age=600\r\n", 36);
			break;
		case 304:
			client_socket_list_append_send_data(socket_list, lst_idx, "304 Not Modified\r\n", 18);
			client_socket_list_append_send_data(socket_list, lst_idx, "Cache-Control: public, max-age=600\r\n", 36);
			break;
		}
		char doc_fd_content_length[20] = {0};
		if(is_reg_file && status_code != 304) { // 获取常规文件的内容长度,并根据文件名后缀,在响应头中输出文件类型
			content_length = (int)lseek(doc_fd, 0, SEEK_END);
			lseek(doc_fd, 0, SEEK_SET);
			if(main_output_content_type(full_path, socket_list, lst_idx) && (status_code == 200)) {
				main_output_last_modified(&filestatus, socket_list, lst_idx);
			}
		}
		else
			content_length = 0;
		sprintf(doc_fd_content_length, "%d", content_length);
		client_socket_list_append_send_data(socket_list, lst_idx, "Content-Length: ", 16);
		client_socket_list_append_send_data(socket_list, lst_idx, doc_fd_content_length, strlen(doc_fd_content_length));
		client_socket_list_append_send_data(socket_list, lst_idx, "\r\nConnection: Closed\r\nServer: zenglServer\r\n\r\n", 45);
		if(is_reg_file && status_code != 304) { // 输出常规文件的内容
			char buffer[1025];
			int data_length;
			while((data_length = read(doc_fd, buffer, sizeof(buffer))) > 0){
				client_socket_list_append_send_data(socket_list, lst_idx, buffer, data_length);
			}
		}
		close(doc_fd);
	}
	..................................................
}


结束语:

    If you think you can, you can!

——  《巨人》
 
上下篇

下一篇: zenglServer v0.9.0 pydebugger 远程调试

上一篇: zenglServer v0.7.0-v0.7.1 mustache模板解析

相关文章

zenglServer v0.16.0 增加curlSetPostByHashArray,curlSetHeaderByArray模块函数,调整进程名称等

zenglServer v0.22.0 增加支付宝支付测试脚本,增加bltUrlEncode等模块函数

zenglServer v0.25.0 使用v1.9.0版本的zengl语言库,增加backlog及timezone配置,增加bltSetTimeZone模块函数

zenglServer v0.10.1 添加bltInt,bltFloat,bltHtmlEscape模块函数,使用v1.8.1版本的zengl语言库

zenglServer v0.19.0 增加redis缓存相关的模块

zenglServer v0.3.0 mysql模块