该版本新增了rqtSetResponseHeader模块函数可以设置响应头,新增rqtGetResponseHeader可以获取设置过的响应头,以及新增rqtGetCookie模块函数可以将客户端请求头中的Cookie名值对信息以哈希数组的形式返回。

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

    zenglServer源代码的相关地址:https://github.com/zenglong/zenglServer  当前版本对应的tag标签为:v0.5.0 ,包含两个分支:master主分支和develop开发分支。作者会在开发分支上进行日常的开发工作,在某个版本开发结束时,会合并到主分支上。

zenglServer v0.5.0:

    该版本新增了rqtSetResponseHeader模块函数可以设置响应头,新增rqtGetResponseHeader可以获取设置过的响应头,以及新增rqtGetCookie模块函数可以将客户端请求头中的Cookie名值对信息以哈希数组的形式返回。

    为了测试这几个模块函数,在my_webroot/v0_5_0目录中增加了两个测试脚本:set_header.zl 和 show_header.zl,其中,set_header.zl脚本主要用于向客户端设置响应头,该脚本的代码如下所示:

use request;

print '<!Doctype html>
<html>
<head><meta http-equiv="content-type" content="text/html;charset=utf-8" />
<title>设置响应头</title>
</head>
<body>';

rqtSetResponseHeader("Set-Cookie: name=zengl");
rqtSetResponseHeader("Set-Cookie: hobby=play game");
//rqtSetResponseHeader("Set-Cookie: hobby=play game; expires=Thu, 01 Jan 1970 00:00:01 GMT;");
rqtSetResponseHeader("Set-Cookie: =hello worlds");
print '设置的响应头信息:' + rqtGetResponseHeader();
print '&nbsp;&nbsp;<a href="show_header.zl" target="_blank">查看请求头</a>';

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


    上面脚本中,通过rqtSetResponseHeader模块函数向客户端中输出了Set-Cookie响应头,从而向客户端中设置了相关的Cookie信息,Cookie可以起到维持会话等作用。在该脚本的最后还通过rqtGetResponseHeader模块函数,将脚本中设置过的响应头显示出来,该脚本在浏览器中的执行效果类似下图所示:


图1:设置响应头

    点击查看请求头的链接,会访问show_header.zl脚本,该脚本的代码如下:

use request, builtin;

print '<!Doctype html>
<html>
<head><meta http-equiv="content-type" content="text/html;charset=utf-8" />
<title>显示请求头信息</title>
</head>
<body>';

print '请求头信息:<br/><br/>';
headers = rqtGetHeaders();
for(i=0; bltIterArray(headers,&i,&k,&v); )
	if(k == 'Cookie')
		print '<span style="color:green">' + k +": " + v + '</span><br/>';
	else
		print k +": " + v + '<br/>';
	endif
endfor

if(headers['Cookie'] != '')
	print '<br/><br/>获取到的Cookie数组:<br/><br/>';
	cookies = rqtGetCookie();
	for(i=0; bltIterArray(cookies,&i,&k,&v); )
		print k +": " + v + '<br/>';
	endfor
endif

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


    该脚本会通过rqtGetCookie模块函数将客户端请求头中的Cookie名值对信息以哈希数组的形式返回,show_header.zl脚本在浏览器中的执行结果类似下图所示:


图2:查看请求头,并从请求头中获取Cookie信息

    从上图中可以看到,请求头中的Cookie信息被标记为了绿色,同时,该Cookie信息在通过rqtGetCookie模块函数转为了哈希数组后,通过数组迭代将Cookie中的各名值对信息给显示了出来。可以看到,这些Cookie就是之前set_header.zl脚本在响应头中,设置过的Cookie。

    上面提到过的三个脚本模块函数:rqtSetResponseHeader,rqtGetResponseHeader以及rqtGetCookie的底层相关C源码,都是在module_request.c文件中实现的,其中,rqtSetResponseHeader的相关C源码如下:

/**
 * rqtSetResponseHeader模块函数,用于设置需要输出到客户端的响应头
 * 例如:rqtSetResponseHeader("Set-Cookie: name=zengl"); 在执行后
 * 响应头中就会输出Set-Cookie: name=zengl\r\n信息,从而可以设置客户端的cookie
 */
ZL_EXP_VOID module_request_SetResponseHeader(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: rqtSetHeader(response_header)");
	zenglApi_GetFunArg(VM_ARG,1,&arg);
	if(arg.type != ZL_EXP_FAT_STR) {
		zenglApi_Exit(VM_ARG,"the first argument of rqtSetHeader must be string");
	}
	char * response_header = arg.val.str;
	int response_header_length = strlen(response_header);
	MAIN_DATA * my_data = zenglApi_GetExtraData(VM_ARG, "my_data");
	dynamic_string_append(&my_data->response_header, response_header, response_header_length, RESPONSE_HEADER_STR_SIZE);
	dynamic_string_append(&my_data->response_header, "\r\n", 2, RESPONSE_HEADER_STR_SIZE);
	zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_INT, ZL_EXP_NULL, (response_header_length + 2), 0);
}


    该模块函数,会将响应头信息追加到response_header动态字符串中,该动态字符串在脚本执行结束时,会被自动输出给客户端,可以在main.c文件的routine_process_client_socket函数中查看到相关C代码:

....................................
static int routine_process_client_socket(CLIENT_SOCKET_LIST * socket_list, int lst_idx)
{
	.............................................
	// 关闭zengl虚拟机及zl_debug_log日志文件
	zenglApi_Close(VM);
	if(my_data.zl_debug_log != NULL) {
		fclose(my_data.zl_debug_log);
	}
	// 如果在zengl脚本中设置了响应头,则先将响应头输出给客户端
	if(my_data.response_header.count > 0) {
		client_socket_list_append_send_data(socket_list, lst_idx, my_data.response_header.str, my_data.response_header.count);
		// 输出完响应头后,将response_header动态字符串释放掉
		dynamic_string_free(&my_data.response_header);
	}
	// zengl脚本中的输出数据会写入到my_data里的response_body动态字符串中,
	// 因此,将response_body动态字符串的长度作为Content-Length,并将其作为响应内容,反馈给客户端
	.............................................
}


    rqtGetResponseHeader模块函数相关的C源码如下(也定义在module_request.c文件中):

/**
 * rqtGetResponseHeader模块函数,用于返回脚本中设置过的响应头信息
 */
ZL_EXP_VOID module_request_GetResponseHeader(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	MAIN_DATA * my_data = zenglApi_GetExtraData(VM_ARG, "my_data");
	// 响应头信息存储在response_header动态字符串中
	if(my_data->response_header.count > 0) {
		char * response_header = (char *)zenglApi_AllocMem(VM_ARG, my_data->response_header.count + 1);
		strncpy(response_header, my_data->response_header.str, my_data->response_header.count);
		// 动态字符串是通过response_header.count来确定字符串的长度的,并没有对数据进行过清0处理,因此,需要手动追加一个'\0'的字符串终止符,这样返回的字符串中才不会包含count后的无效字符。
		response_header[my_data->response_header.count] = STR_NULL;
		zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_STR, response_header, 0, 0);
		zenglApi_FreeMem(VM_ARG, response_header);
	}
	else {
		zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_STR, "", 0, 0);
	}
}


    该模块函数直接将response_header动态字符串作为结果返回。为了防止返回无效字符,上面C函数中新建了一个response_header动态字符串的拷贝,并根据动态字符串的count有效字符数,在拷贝字符串的末尾设置了'\0'的字符串终止符。

    rqtGetCookie模块函数相关的C源码如下(同样定义在module_request.c文件中):

/**
 * rqtGetCookie模块函数,用于将请求头中的Cookie名值对信息以数组的形式返回
 * 例如:
 * cookies = rqtGetCookie();
 * for(i=0; bltIterArray(cookies,&i,&k,&v); )
 *		print k +": " + v + '<br/>';
 * endfor
 * 该脚本在执行时,如果客户端的请求头中包含 Cookie: name=zengl; hobby=play game; hello worlds 信息时
 * 执行的结果就会是:
 *	name: zengl
 *	hobby: play game
 *	: hello worlds
 *	请求头Cookie中的hello worlds等效于=hello worlds,也就是key为空
 */
ZL_EXP_VOID module_request_GetCookie(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	MAIN_DATA * my_data = zenglApi_GetExtraData(VM_ARG, "my_data");
	if(my_data->cookie_memblock.ptr == ZL_EXP_NULL) {
		if(zenglApi_CreateMemBlock(VM_ARG,&my_data->cookie_memblock,0) == -1) {
			zenglApi_Exit(VM_ARG,zenglApi_GetErrorString(VM_ARG));
		}
		zenglApi_AddMemBlockRefCount(VM_ARG,&my_data->cookie_memblock,1); // 手动增加该内存块的引用计数值,使其不会在脚本函数返回时,被释放掉。
		get_headers(VM_ARG, my_data);
		ZENGL_EXPORT_MOD_FUN_ARG cookie_header_value = zenglApi_GetMemBlockByHashKey(VM_ARG,&my_data->headers_memblock, "Cookie");
		if(cookie_header_value.type == ZL_EXP_FAT_STR) {
			ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
			char *s,*k,*v, prev_last_k_char, prev_last_v_char;
			int s_len, k_len, v_len, count;
			s = cookie_header_value.val.str; s_len = strlen(cookie_header_value.val.str);
			while(s_len > 0) {
				count = parse_cookie_header_value(s, s_len, &k, &k_len, &v, &v_len);
				if(k_len > 0) {
					prev_last_k_char = k[k_len];
					k[k_len] = STR_NULL;
					arg.type = ZL_EXP_FAT_STR;
					if(v_len > 0) {
						prev_last_v_char = v[v_len];
						v[v_len] = STR_NULL;
						arg.val.str = v;
						zenglApi_SetMemBlockByHashKey(VM_ARG, &my_data->cookie_memblock, k, &arg);
						v[v_len] = prev_last_v_char;
					}
					else if(v_len == 0) {
						arg.val.str = "";
						zenglApi_SetMemBlockByHashKey(VM_ARG, &my_data->cookie_memblock, k, &arg);
					}
					k[k_len] = prev_last_k_char;
				}
				else if(k_len == 0 && v_len > 0) {
					prev_last_v_char = v[v_len];
					v[v_len] = STR_NULL;
					arg.val.str = v;
					zenglApi_SetMemBlockByHashKey(VM_ARG, &my_data->cookie_memblock, "", &arg);
					v[v_len] = prev_last_v_char;
				}
				s += count;
				s_len -= count;
			}
		}
		zenglApi_SetRetValAsMemBlock(VM_ARG,&my_data->cookie_memblock);
	}
	else {
		zenglApi_SetRetValAsMemBlock(VM_ARG,&my_data->cookie_memblock);
	}
}


    该模块函数,会先从header请求头中获取Cookie的原始字符串信息,接着通过parse_cookie_header_value的C函数对字符串中的名值对信息进行解析,解析出key -> value名值对后,再将其加入到哈希数组中,parse_cookie_header_value函数的相关C代码如下:

/*
 * module_request.c
 *
 *  Created on: 2017-6-15
 *      Author: zengl
 */

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

// 对请求头中的Cookie名值对进行解析时,需要用到的状态机
enum _my_cookie_parser_status {
	m_cookie_p_status_start = 1,
	m_cookie_p_status_key_start,
	m_cookie_p_status_key_end,
	m_cookie_p_status_value_start
};

...........................................
typedef enum _my_cookie_parser_status my_cookie_parser_status;
...........................................

/**
 * 使用状态机,将请求头中的Cookie名值对信息解析出来
 * 例如:Cookie: name=zengl; hobby=play game;
 * 下面的函数可以将name -> zengl和hobby -> play game这样的名值对信息给解析出来
 * 外部调用者,就可以通过解析出来的key(名),value(值),来设置哈希数组成员
 */
static int parse_cookie_header_value(char * s, int s_len,
						char ** key, int * key_len,
						char ** value, int * value_len)
{
	char c;
	char * k = NULL, * v = NULL;
	int k_len = 0, v_len = 0;
	int i;
	my_cookie_parser_status status = m_cookie_p_status_start;
	for(i = 0; i < s_len; i++) {
		c = s[i];
		if(c == ';') {
			i++;
			break;
		}
		switch(status){
		case m_cookie_p_status_start:
			if(c != ' ') {
				if(c == '=') {
					k_len = 0;
					status = m_cookie_p_status_key_end;
				}
				else {
					k = &s[i];
					k_len++;
					status = m_cookie_p_status_key_start;
				}
			}
			break;
		case m_cookie_p_status_key_start:
			if(c == '=')
				status = m_cookie_p_status_key_end;
			else
				k_len++;
			break;
		case m_cookie_p_status_key_end:
			v = &s[i];
			v_len++;
			status = m_cookie_p_status_value_start;
			break;
		case m_cookie_p_status_value_start:
			v_len++;
			break;
		}
	}
	// 对于请求头 Cookie: name=zengl; hobby=play game; hello worlds
	// 其中 hello worlds 等效于 =hello worlds ,也就是key是空的,因此,这种情况,需要将解析出来的key作为value进行返回,而key则设置为NULL
	if(status == m_cookie_p_status_key_start) {
		// 将k对应的指针赋值给v,长度赋值给v_len,再将k设置为NULL,k_len设置为0,也就是将解析出来的key设置为空,并将原始的key作为value返回
		v = k;
		v_len = k_len;
		k = NULL;
		k_len = 0;
	}
	(*key) = k;
	(*key_len) = k_len;
	(*value) = v;
	(*value_len) = v_len;
	return i;
}


    当前版本除了新增三个模块函数外,还将zenglServer的响应头信息也写入到了日志文件中,对于上面的set_header.zl脚本以及show_header.zl脚本,在脚本执行后,相关的日志信息如下:

zengl@zengl-ubuntu:~/zenglServer$ tail -f logfile
.........................................
-----------------------------------
Sun Nov 26 11:18:26 2017
recv [client_socket_fd:9] [lst_idx:0] [pid:3420] [tid:3422]:

request header: Host: 192.168.1.104:8083 | User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 | Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 | Accept-Encoding: gzip, deflate | Connection: keep-alive | Upgrade-Insecure-Requests: 1 |

url: /v0_5_0/set_header.zl
url_path: /v0_5_0/set_header.zl
status: 200, content length: 340
response header: HTTP/1.1 200 OK
Set-Cookie: name=zengl
Set-Cookie: hobby=play game
Set-Cookie: =hello worlds
Content-Length: 340
Connection: Closed
Server: zenglServer
free socket_list[0]/list_cnt:0 epoll_fd_add_count:0 pid:3420 tid:3422
-----------------------------------
Sun Nov 26 11:30:13 2017
recv [client_socket_fd:10] [lst_idx:0] [pid:3420] [tid:3422]:

request header: Host: 192.168.1.104:8083 | User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 | Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 | Accept-Encoding: gzip, deflate | Referer: http://192.168.1.104:8083/v0_5_0/set_header.zl | Cookie: name=zengl; hobby=play game; hello worlds | Connection: keep-alive | Upgrade-Insecure-Requests: 1 |

url: /v0_5_0/show_header.zl
url_path: /v0_5_0/show_header.zl
status: 200, content length: 832
response header: HTTP/1.1 200 OK
Content-Length: 832
Connection: Closed
Server: zenglServer
free socket_list[0]/list_cnt:0 epoll_fd_add_count:0 pid:3420 tid:3422


    从上面的输出中可以看到,除了可以查看到request header(客户端的请求头信息)外,还可以查看到response header(zenglServer的响应头信息)。

    在main.c文件的routine_process_client_socket函数中,可以看到和输出响应头相关的C代码:

static int routine_process_client_socket(CLIENT_SOCKET_LIST * socket_list, int lst_idx)
{
	.............................................
	// 在日志中输出响应状态码和响应主体数据的长度
	write_to_server_log_pipe(WRITE_TO_PIPE, "status: %d, content length: %d\n", status_code, content_length);
	// 通过client_socket_list_log_response_header函数,在日志中记录完整的响应头信息
	client_socket_list_log_response_header(socket_list, lst_idx);
	return lst_idx;
}


    上面是通过client_socket_list_log_response_header这个C函数来记录完整的响应头信息的,该函数定义在client_socket_list.c文件中:

/**
 * 在日志中记录完整的响应头信息
 */
void client_socket_list_log_response_header(CLIENT_SOCKET_LIST * list, int idx)
{
	if(list->member[idx].send_data.count > 0) {
		char * data = list->member[idx].send_data.str;
		// 响应头是以\r\n\r\n结尾的,因此,先确定响应头的结束位置,接着就可以将完整的响应头写入到日志中了
		char * header_last_ptr = strstr(data, "\r\n\r\n");
		if(header_last_ptr != NULL) {
			char prev_char = header_last_ptr[2];
			header_last_ptr[2] = STR_NULL;
			write_to_server_log_pipe(WRITE_TO_PIPE, "response header: %s", data);
			header_last_ptr[2] = prev_char;
		}
	}
}


    以上就是当前版本的相关内容,就到这里,休息,休息一下 o(∩_∩)o~~

结束语:

    无论遇到什么情况,你都必须明白,唯有自救,才能生存

——  《囚徒》
 
上下篇

下一篇: zenglServer v0.6.0 session会话

上一篇: zenglServer v0.4.0 daemon守护进程, epoll事件驱动

相关文章

zenglServer v0.4.0 daemon守护进程, epoll事件驱动

zenglServer v0.8.0-v0.8.1

zenglServer v0.21.0 增加base64编解码相关的内建模块函数

zenglServer v0.10.0 使用zengl脚本的编译缓存,跳过编译过程

zenglServer v0.17.0 设置精简日志模式,设置允许上传文件大小,日志分割等

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