页面导航:
项目下载地址:
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 ' <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~~
结束语:
无论遇到什么情况,你都必须明白,唯有自救,才能生存
—— 《囚徒》