在curl模块中增加了两个模块函数:curlSetPostByHashArray和curlSetHeaderByArray模块函数,在curlEasySetopt模块函数中增加了COOKIEFILE,COOKIEJAR,COOKIE,PROXY,POSTFIELDS,VERBOSE以及STDERR选项,调整了进程名称等。

    页面导航:

项目下载地址:

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

zenglServer v0.16.0:

    该版本新增的功能如下:

    在curl模块中增加了两个模块函数:curlSetPostByHashArray和curlSetHeaderByArray模块函数。

    在curlEasySetopt模块函数中增加了COOKIEFILE,COOKIEJAR,COOKIE,PROXY,POSTFIELDS,VERBOSE以及STDERR选项。

    在curlEasyPerform模块函数中增加了&ptr参数,可以获取到指向了抓取的数据的指针,利用该指针可以保存图像等二进制数据。

    在builtin模块中增加了bltFree及bltReadFile模块函数。

    此外,在主进程名称中增加了端口号,当前工作目录等,在启动zenglServer时还可以使用-l命令行参数来设置日志文件名。

    当前版本新增的很多测试脚本都需要启动两个zenglServer,一个绑定到8083端口,另一个绑定到8084端口。当通过8083端口访问当前版本的一些测试脚本时,它们会再通过curl去请求8084端口对应的zenglServer里的脚本,以测试POST请求等。

    要新启动一个绑定到8084端口的zenglServer,可以拷贝一份config.zl配置文件,并在该配置文件中修改端口号为8084,在启动时通过-c参数指定该配置文件即可:

[parallels@localhost zenglServerTest]$ ./zenglServer 
[parallels@localhost zenglServerTest]$ cat config_test.zl 
.....................................................................
port = 8084; // 绑定的端口

.....................................................................
[parallels@localhost zenglServerTest]$ ./zenglServer -c config_test.zl
[parallels@localhost zenglServerTest]$ ps aux | grep zenglServer
paralle+ 22252  0.0  0.1 139068  2348 ?        Ss   14:38   0:00 zenglServer: master[8083] cwd:/media/psf/Home/zenglServerTest -c config.zl -l logfile
paralle+ 22253  0.0  0.0 155460  1752 ?        Sl   14:38   0:00 zenglServer: child(0) ppid:22252
paralle+ 22256  0.0  0.0 139068  1752 ?        S    14:38   0:00 zenglServer: cleaner  ppid:22252
paralle+ 22346  0.0  0.1 139068  2348 ?        Ss   14:38   0:00 zenglServer: master[8084] cwd:/media/psf/Home/zenglServerTest -c config_test.zl -l logfile
paralle+ 22347  0.0  0.0 155460  1756 ?        Sl   14:38   0:00 zenglServer: child(0) ppid:22346
paralle+ 22350  0.0  0.0 139068  1752 ?        S    14:38   0:00 zenglServer: cleaner  ppid:22346
paralle+ 22368  0.0  0.0 112720   988 pts/1    S+   14:38   0:00 grep --color=auto zenglServer
[parallels@localhost zenglServerTest]$ 

    上面启动了两个zenglServer,一个绑定的端口是8083(通过默认的config.zl配置文件来启动),另一个绑定的端口是8084(通过拷贝和修改过的config_test.zl配置文件来启动),在ps显示的进程列表里也可以看到它们绑定的端口号。

curlSetPostByHashArray,curlSetHeaderByArray模块函数:

    curlSetPostByHashArray和curlSetHeaderByArray模块函数的代码,位于module_curl.c文件中:

/**
 * curlSetPostByHashArray模块函数,通过哈希数组来设置multipart/form-data类型的POST请求,
 * 该模块函数的第一个参数必须是有效的my_curl_handle_struct类型的指针,该指针由curlEasyInit模块函数返回,
 * 第二个参数hash_array是用于设置POST请求的哈希数组,数组中的每个带有字符串key的成员都对应一个POST请求的名值对信息,
 * 数组成员的字符串key将作为POST请求的name,数组成员的值将作为POST请求的值,
 * 当需要在POST请求中发送文件时,可以使用@file_path的格式来设置需要发送的文件的路径(该路径是相对于当前主执行脚本的路径),
 * 例如:@upload/upload_image.jpg 表示将 upload/upload_image.jpg 对应的文件内容通过POST请求发送出去,
 * 还可以设置发送文件的Content-Type类型,只需在路径后面跟随Content-Type类型名即可,
 * 文件路径和Content-Type类型名之间通过英文半角逗号隔开,
 * 例如:@upload/kernel_shell.png,image/png 表示需要发送的文件路径为upload/kernel_shell.png,Content-Type类型为image/png,
 *
 * 示例代码如下:

	use builtin, curl, request;
	def TRUE 1;
	def FALSE 0;

	rqtSetResponseHeader("Content-Type: text/html; charset=utf-8");

	print 'curl version: ' + curlVersion() + '<br/>';

	curl_handle = curlEasyInit();
	curlEasySetopt(curl_handle, 'URL', 'http://127.0.0.1:8084/v0_2_0/post.zl');
	curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0');
	curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE);
	curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE);
	curlEasySetopt(curl_handle, 'TIMEOUT', 30);
	data['name'] = 'zenglong';
	data['job'] = 'programmer';
	data['age'] = 30;
	data['money'] = 550.35;
	data['myjpg'] = '@upload/upload_image.jpg';
	data['mypng'] = '@upload/kernel_shell.png,image/png';
	curlSetPostByHashArray(curl_handle, data);
	ret = curlEasyPerform(curl_handle, &content);
	if(ret == 0)
		print content;
	else
		print 'error: ' + curlEasyStrError(ret);
	endif
	curlEasyCleanup(curl_handle);

	上面脚本中,先通过data数组设置需要发送的POST请求所对应的名值对信息,
	例如上面的 data['job'] = 'programmer'; 表示将要设置一个名为job,值为programmer的POST请求,
	在创建好包含名值对的哈希数组后,接着就可以通过curlSetPostByHashArray模块函数将数组中的数据和底层的curl操作指针进行绑定,
	当使用curlEasyPerform执行具体的请求操作时,就会根据这些数据来创建一个multipart/form-data类型的POST请求。

	上面脚本在执行时,会构建出类似如下所示的multipart/form-data类型的POST请求:

	POST /v0_2_0/post.zl HTTP/1.1
	................................
	Content-Type: multipart/form-data; boundary=------------------------------cb39d046a2e0
	................................

	------------------------------cb39d046a2e0
	Content-Disposition: form-data; name="name"

	zenglong
	------------------------------cb39d046a2e0
	Content-Disposition: form-data; name="job"

	programmer
	------------------------------cb39d046a2e0
	Content-Disposition: form-data; name="age"

	30
	------------------------------cb39d046a2e0
	Content-Disposition: form-data; name="money"

	550.35
	------------------------------cb39d046a2e0
	Content-Disposition: form-data; name="myjpg"; filename="upload_image.jpg"
	Content-Type: image/jpeg

	.......JFIF..............................
	.........................................
	------------------------------cb39d046a2e0
	Content-Disposition: form-data; name="mypng"; filename="kernel_shell.png"
	Content-Type: image/png

	..PNG....................................
	.........................................
	------------------------------cb39d046a2e0--

	该模块函数在底层会通过curl_formadd库函数,来进行实际的构建multipart/form-data类型的POST请求的操作,
	该库函数的官方地址:https://curl.haxx.se/libcurl/c/curl_formadd.html
 */
ZL_EXP_VOID module_curl_set_post_by_hash_array(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
	if(argcount < 2)
		zenglApi_Exit(VM_ARG,"usage: curlSetPostByHashArray(curl_handle, hash_array): integer");
	zenglApi_GetFunArg(VM_ARG,1,&arg);
	if(arg.type != ZL_EXP_FAT_INT) {
		zenglApi_Exit(VM_ARG,"the first argument [curl_handle] of curlSetPostByHashArray must be integer");
	}
	.......................................................................
}

/**
 * curlSetHeaderByArray模块函数,设置自定义的HTTP请求头,
 * 该模块函数的第一个参数必须是有效的my_curl_handle_struct类型的指针,该指针由curlEasyInit模块函数返回,
 * 第二个参数array必须是一个数组,数组中的每一项都对应一个自定义的请求头,
 * 如果自定义的请求头中只包含请求头名,而不包含对应的值时,表示如果存在该名称对应的请求头的话,就将其移除掉,
 * 例如:'Accept:' 就表示移除掉Accept请求头,
 * 当自定义的请求头名和已存在的请求头名称相同时,表示修改该请求头对应的值,
 * 例如:'Host: example.com',表示将已存在的Host请求头的值修改为 example.com,
 * 可以设置一个空的没有值的请求头,例如: 'X-silly-header;' 表示设置一个名为X-silly-header,值为空的请求头,
 * 不过自定义这种没有值的请求头,在低版本的curl库中并不支持,例如:7.15和7.19的版本。
 *
 * 示例代码如下:

	use builtin, curl, request;
	def TRUE 1;
	def FALSE 0;

	rqtSetResponseHeader("Content-Type: text/html; charset=utf-8");

	print 'curl version: ' + curlVersion() + '<br/>';

	curl_handle = curlEasyInit();
	curlEasySetopt(curl_handle, 'URL', 'http://127.0.0.1:8084/v0_5_0/show_header.zl');
	curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0');
	curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE);
	curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE);
	curlEasySetopt(curl_handle, 'TIMEOUT', 30);
	curlSetHeaderByArray(curl_handle, bltArray('Accept:', 'Another: yes', 'Host: example.com', 'X-silly-header;'));
	ret = curlEasyPerform(curl_handle, &content);
	if(ret == 0)
		print content;
	else
		print 'error: ' + curlEasyStrError(ret);
	endif
	curlEasyCleanup(curl_handle);

	上面脚本中,通过curlSetHeaderByArray添加了一个Another: yes的请求头,移除了Accept请求头,修改了Host请求头,
	还设置了一个名为X-silly-header的值为空的请求头,该脚本的执行结果类似如下所示:

	curl version: libcurl/7.29.0 NSS/3.34 zlib/1.2.11 libidn/1.28 libssh2/1.4.3
	请求头信息:

	User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0
	Another: yes
	Host: example.com
	X-silly-header:

	该模块函数在底层会通过curl_slist_append库函数,来执行具体的构建自定义请求头的操作,
	该库函数的官方地址:https://curl.haxx.se/libcurl/c/curl_slist_append.html
 */
ZL_EXP_VOID module_curl_set_header_by_array(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
	if(argcount < 2)
		zenglApi_Exit(VM_ARG,"usage: curlSetHeaderByArray(curl_handle, array): integer");
	zenglApi_GetFunArg(VM_ARG,1,&arg);
	if(arg.type != ZL_EXP_FAT_INT) {
		zenglApi_Exit(VM_ARG,"the first argument [curl_handle] of curlSetHeaderByArray must be integer");
	}
	.......................................................................
}

    在my_webroot/v0_16_0目录中增加了test_post.zl和test_set_header.zl测试脚本,用于测试上面这两个模块函数,其中,test_post.zl脚本的代码如下:

use builtin, curl, request;
def TRUE 1;
def FALSE 0;

rqtSetResponseHeader("Content-Type: text/html; charset=utf-8");

print 'curl version: ' + curlVersion() + '<br/>';

curl_handle = curlEasyInit();
curlEasySetopt(curl_handle, 'URL', 'http://127.0.0.1:8084/v0_2_0/post.zl');
curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0');
curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE);
curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE);
curlEasySetopt(curl_handle, 'TIMEOUT', 30);
data['name'] = 'zenglong';
data['job'] = 'programmer';
data['age'] = 30;
data['money'] = 550.35;
data['myjpg'] = '@upload/upload_image.jpg';
data['mypng'] = '@upload/kernel_shell.png,image/png';
curlSetPostByHashArray(curl_handle, data);
ret = curlEasyPerform(curl_handle, &content);
if(ret == 0)
	print content;
else
	print 'error: ' + curlEasyStrError(ret);
endif
curlEasyCleanup(curl_handle);

    上面脚本中,先通过data数组设置了需要发送的POST请求所对应的名值对信息,例如上面的 data['job'] = 'programmer'; 表示将要设置一个名为job,值为programmer的POST请求。

    在创建好包含名值对的哈希数组后,接着就可以通过curlSetPostByHashArray模块函数将数组中的数据和curl_handle进行绑定。当使用curlEasyPerform执行具体的请求操作时,就会根据这些数据来创建一个multipart/form-data类型的POST请求。

    上面脚本还使用@file_path的格式,设置了需要发送的文件的路径,例如:@upload/upload_image.jpg 表示将 upload/upload_image.jpg 对应的文件内容通过POST请求发送出去。

    @upload/kernel_shell.png,image/png 表示需要发送的文件路径为upload/kernel_shell.png,Content-Type类型为image/png,文件路径和Content-Type类型之间通过英文半角逗号隔开。

    上面这个脚本会通过curl向8084端口中的v0_2_0/post.zl脚本发送POST请求,并将POST请求返回的结果显示出来,test_post.zl脚本的执行结果如下:

test_post.zl在浏览器中的执行结果

    test_set_header.zl脚本(用于测试curlSetHeaderByArray模块函数)的代码如下:

use builtin, curl, request;
def TRUE 1;
def FALSE 0;

rqtSetResponseHeader("Content-Type: text/html; charset=utf-8");

print 'curl version: ' + curlVersion() + '<br/>';

curl_handle = curlEasyInit();
curlEasySetopt(curl_handle, 'URL', 'http://127.0.0.1:8084/v0_5_0/show_header.zl');
curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0');
curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE);
curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE);
curlEasySetopt(curl_handle, 'TIMEOUT', 30);
curlSetHeaderByArray(curl_handle, bltArray('Accept:', 'Another: yes', 'Host: example.com', 'X-silly-header;'));
ret = curlEasyPerform(curl_handle, &content);
if(ret == 0)
	print content;
else
	print 'error: ' + curlEasyStrError(ret);
endif
curlEasyCleanup(curl_handle);

    上面脚本中,通过curlSetHeaderByArray模块函数添加了一个Another: yes的请求头,移除了Accept请求头,修改了Host请求头,还设置了一个名为X-silly-header的值为空的请求头。

    该脚本的执行结果如下:

test_set_header.zl脚本在浏览器中的执行结果

curlEasySetopt模块函数新增的选项:

    当前版本还在curlEasySetopt模块函数中,增加了COOKIEFILE,COOKIEJAR,COOKIE,PROXY,POSTFIELDS,VERBOSE以及STDERR选项。相关的C代码如下:

/**
 * curlEasySetopt模块函数,设置curl抓取相关的选项,例如:抓取的目标地址,需要使用的用户代理等
 * 该模块函数的第一个参数必须是有效的my_curl_handle_struct指针,该指针由curlEasyInit模块函数返回,
 * 第二个参数是字符串类型的选项名称,暂时只支持以下几个选项:
 * 'URL':表示需要抓取的目标地址
 * 'USERAGENT':需要设置的用户代理
 * 'FOLLOWLOCATION':当抓取到重定向页面时,是否进行重定向操作
 * 'SSL_VERIFYPEER':是否校验SSL证书
 * 'TIMEOUT': 设置超时时间
 * 'COOKIEFILE': 设置需要读cookie的文件名,当需要发送cookie信息时,curl会读取该文件,并将其中的cookie作为请求发送出去
 * 'COOKIEJAR': 设置需要写入cookie的文件名,当curl获取到的响应头中包含了设置cookie的信息时,会将这些cookie写入到指定的文件
 * 'COOKIE': 设置自定义的cookie
 * 'PROXY': 设置http,socks5之类的代理,socks协议的代理需要7.21.7及以上的版本才支持,低版本的curl库只支持http协议的代理
 * 'POSTFIELDS': 设置application/x-www-form-urlencoded类型的POST请求
 * 'VERBOSE': 设置是否输出详细的连接信息,包含了请求头和响应头在内,方便调试,这些连接信息会输出到 STDERR 所指定的文件中
 * 'STDERR': 当开启了VERBOSE时,详细的连接信息会保存到STDERR所指定的文件中
 *
 * 第三个参数是需要设置的具体的选项值,
 *
 * 当第二个参数是'URL','USERAGENT','COOKIE','PROXY','POSTFIELDS'时,
 * 选项值必须是字符串类型,表示需要设置的url地址,用户代理等,
 * 当第二个参数是'COOKIEFILE','COOKIEJAR'时,选项值也是字符串,表示需要读取和写入cookie的文件的路径(该路径是相对于当前主执行脚本的)
 *
 * 当第二个参数是'STDERR'时,可以有两个选项值:
 * 	第一个选项值option_value表示相对于当前主执行脚本的文件路径,即需要输出VERBOSE信息到哪个文件,
 * 	第二个选项值option_value2是可选的,表示需要以什么模式来打开该文件,默认是wb表示以写入模式打开文件,该模式会清空文件中原有的内容,
 * 		如果option_value2设置为ab,则表示以追加的方式打开文件,VERBOSE信息会追加到文件的末尾。
 *
 * 当第二个参数是'VERBOSE'时,选项值必须是整数类型,表示是否输出详细的连接信息,默认是0,即不输出连接信息,如果要输出连接信息,可以将选项值设置为1
 * 当第二个参数是'FOLLOWLOCATION'时,选项值必须是整数类型,表示是否进行重定向操作,默认是0,即不进行重定向,需要进行重定向的,可以将选项值设置为1
 * 当第二个参数是'SSL_VERIFYPEER'时,选项值必须是整数类型,表示是否校验SSL证书,默认是1,即需要进行校验,如果不需要校验,可以将选项值设置为0
 * 当第二个参数是'TIMEOUT'时,选项值必须是整数类型,表示需要设置的超时时间
 *
 * 和'URL','USERAGENT','FOLLOWLOCATION','SSL_VERIFYPEER','TIMEOUT'选项相关的例子,请参考curlEasyPerform模块函数的注释部分
 * 和'COOKIEFILE'选项相关的例子可以参考 my_webroot/v0_16_0/test_cookiefile.zl 脚本对应的代码
 * 和'COOKIEJAR'选项相关的例子可以参考 my_webroot/v0_16_0/test_cookiejar.zl 脚本对应的代码
 * 和'COOKIE'选项相关的例子可以参考 my_webroot/v0_16_0/test_cookie.zl 脚本对应的代码
 * 和'PROXY'选项相关的例子可以参考 my_webroot/v0_16_0/test_proxy.zl 脚本对应的代码
 * 和'POSTFIELDS'选项相关的例子可以参考 my_webroot/v0_16_0/test_postfields.zl 脚本对应的代码
 * 和'VERBOSE','STDERR'选项相关的例子可以参考 my_webroot/v0_16_0/test_verbose.zl 脚本对应的代码
 *
 * 该模块函数最终会通过curl_easy_setopt库函数去执行具体的操作,
 * 该库函数的官方地址为:https://curl.haxx.se/libcurl/c/curl_easy_setopt.html
 */
ZL_EXP_VOID module_curl_easy_setopt(ZL_EXP_VOID * VM_ARG,ZL_EXP_INT argcount)
{
	......................................................................................
	char * options_str[] = {
			"URL", "USERAGENT", "FOLLOWLOCATION", "SSL_VERIFYPEER", "TIMEOUT",
			"COOKIEFILE", "COOKIEJAR", "COOKIE", "PROXY", "POSTFIELDS",
			"VERBOSE", "STDERR"
	};
	int options_str_len = sizeof(options_str)/sizeof(options_str[0]);
	CURLoption options_enum[] = {
			CURLOPT_URL, CURLOPT_USERAGENT, CURLOPT_FOLLOWLOCATION, CURLOPT_SSL_VERIFYPEER, CURLOPT_TIMEOUT,
			CURLOPT_COOKIEFILE, CURLOPT_COOKIEJAR, CURLOPT_COOKIE, CURLOPT_PROXY, CURLOPT_POSTFIELDS,
			CURLOPT_VERBOSE, CURLOPT_STDERR
	};
	......................................................................................
	zenglApi_GetFunArg(VM_ARG,3,&arg);
	CURLcode retval;
	switch(option) {
	case CURLOPT_URL:
	case CURLOPT_USERAGENT:
	case CURLOPT_COOKIEFILE:
	case CURLOPT_COOKIEJAR:
	case CURLOPT_COOKIE:
	case CURLOPT_PROXY:
	case CURLOPT_POSTFIELDS:
		if(arg.type == ZL_EXP_FAT_STR) {
			char * option_value = st_curl_process_str(VM_ARG, my_data, option, my_curl_handle, arg.val.str);
			retval = curl_easy_setopt(curl_handle, option, option_value);
		}
		else {
			zenglApi_Exit(VM_ARG,"the third argument [option_value] of curlEasySetopt must be string when [option_name] is %s", options_str[opt_idx]);
		}
		break;
	case CURLOPT_STDERR:
		if(arg.type == ZL_EXP_FAT_STR) {
			if(my_curl_handle->stderr_stream != NULL) {
				fclose(my_curl_handle->stderr_stream);
			}
			char * filename = arg.val.str;
			char full_path[FULL_PATH_SIZE];
			builtin_make_fullpath(full_path, filename, my_data);
			char * mode = "wb";
			if(argcount > 3) {
				zenglApi_GetFunArg(VM_ARG,4,&arg);
				if(arg.type != ZL_EXP_FAT_STR) {
					zenglApi_Exit(VM_ARG,"the fourth argument [option_value2] of curlEasySetopt must be string when [option_name] is %s",
							options_str[opt_idx]);
				}
				mode = arg.val.str;
			}
			my_curl_handle->stderr_stream = fopen(full_path, mode);
			retval = curl_easy_setopt(curl_handle, option, my_curl_handle->stderr_stream);
		}
		else {
			zenglApi_Exit(VM_ARG,"the third argument [option_value] of curlEasySetopt must be string when [option_name] is %s", options_str[opt_idx]);
		}
		break;
	case CURLOPT_FOLLOWLOCATION:
	case CURLOPT_SSL_VERIFYPEER:
	case CURLOPT_TIMEOUT:
	case CURLOPT_VERBOSE:
		if(arg.type == ZL_EXP_FAT_INT) {
			long option_value = arg.val.integer;
			retval = curl_easy_setopt(curl_handle, option, option_value);
		}
		else {
			zenglApi_Exit(VM_ARG,"the third argument [option_value] of curlEasySetopt must be integer when [option_name] is %s", options_str[opt_idx]);
		}
		break;
	default:
		zenglApi_Exit(VM_ARG, "the second argument [option_name] of curlEasySetopt is invalid: %s", options_str[opt_idx]);
		break;
	}
	if(retval == CURLE_OK)
		zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_INT, ZL_EXP_NULL, 0, 0);
	else if(retval > 0)
		zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_INT, ZL_EXP_NULL, (ZL_EXP_LONG)retval, 0);
	else
		zenglApi_SetRetVal(VM_ARG,ZL_EXP_FAT_INT, ZL_EXP_NULL, -1, 0);
}

    从上面的代码注释中可以看到,新增的几个选项的作用如下:

    'COOKIEFILE':设置需要读cookie的文件名,当需要发送cookie信息时,curl会读取该文件,并将其中的cookie作为请求发送出去。

    'COOKIEJAR':设置需要写入cookie的文件名,当curl获取到的响应头中包含了设置cookie的信息时,会将这些cookie写入到指定的文件。

    'COOKIE':设置自定义的cookie。

    'PROXY':设置http,socks5之类的代理,socks协议的代理需要7.21.7及以上的版本才支持,低版本的curl库只支持http协议的代理。

    'POSTFIELDS':设置application/x-www-form-urlencoded类型的POST请求。

    'VERBOSE':设置是否输出详细的连接信息,包含了请求头和响应头在内,方便调试,这些连接信息会输出到 STDERR 所指定的文件中。

    'STDERR':当开启了VERBOSE时,详细的连接信息会保存到STDERR所指定的文件中。

COOKIEFILE:

    和'COOKIEFILE'选项相关的示例脚本为:my_webroot/v0_16_0/test_cookiefile.zl脚本:

use builtin, curl, request;
def TRUE 1;
def FALSE 0;

rqtSetResponseHeader("Content-Type: text/html; charset=utf-8");

print 'curl version: ' + curlVersion() + '<br/>';

curl_handle = curlEasyInit();
curlEasySetopt(curl_handle, 'URL', 'http://127.0.0.1:8084/v0_5_0/show_header.zl');
curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0');
curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE);
curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE);
curlEasySetopt(curl_handle, 'TIMEOUT', 30);
curlEasySetopt(curl_handle, 'COOKIEFILE', 'cookies.txt');
ret = curlEasyPerform(curl_handle, &content);
if(ret == 0)
	print content;
else
	print 'error: ' + curlEasyStrError(ret);
endif
curlEasyCleanup(curl_handle);

    'COOKIEFILE'选项对应的选项值表示需要读取cookie的文件的路径(该路径是相对于当前主执行脚本的)。

    在本脚本中,'COOKIEFILE'的选项值就是cookies.txt,如果该文件存在,curl在发送网络请求时,就会去读取该文件,并将文件中的cookie作为请求头发送出去。如果cookies.txt文件不存在,则请求头中就不会包含cookie信息。

    我们可以先执行my_webroot/v0_16_0/test_cookiejar.zl脚本,以生成cookies.txt文件,然后再执行本脚本,就可以在发送的请求头中看到cookies.txt文件中的Cookie信息了:

执行test_cookiejar.zl脚本生成cookies.txt文件

test_cookiefile.zl脚本在浏览器中的执行结果

COOKIEJAR:

    和'COOKIEJAR'选项相关的示例脚本为:my_webroot/v0_16_0/test_cookiejar.zl,该脚本的代码如下:

use builtin, curl, request;
def TRUE 1;
def FALSE 0;

rqtSetResponseHeader("Content-Type: text/html; charset=utf-8");

print 'curl version: ' + curlVersion() + '<br/>';

curl_handle = curlEasyInit();
curlEasySetopt(curl_handle, 'URL', 'http://127.0.0.1:8084/v0_5_0/set_header.zl');
curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0');
curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE);
curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE);
curlEasySetopt(curl_handle, 'TIMEOUT', 30);
curlEasySetopt(curl_handle, 'COOKIEJAR', 'cookies.txt');
ret = curlEasyPerform(curl_handle, &content);
curlEasyCleanup(curl_handle);
if(ret == 0)
	ret = bltReadFile('cookies.txt', &file_content, &file_size);
	if(ret == 0)
		print 'cookies.txt file size: ' + file_size;
		print 'file content: \n' + file_content;
	else
		print 'read cookies.txt failed, maybe the file does not exists, or open failed.';
	endif
else
	print 'error: ' + curlEasyStrError(ret);
endif

    'COOKIEJAR'选项对应的选项值表示需要写入cookie的文件的路径(该路径是相对于当前主执行脚本的),当curl获取到的响应头中包含了设置cookie的信息时,就会将这些cookie写入到指定的文件中,在本脚本中,需要写入cookie信息的文件就是'cookies.txt'。

    test_cookiejar.zl脚本的执行结果如下:

[parallels@localhost zenglServerTest]$ curl http://127.0.0.1:8083/v0_16_0/test_cookiejar.zl
curl version: libcurl/7.29.0 NSS/3.34 zlib/1.2.11 libidn/1.28 libssh2/1.4.3<br/>
cookies.txt file size: 228
file content: 
# Netscape HTTP Cookie File
# http://curl.haxx.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.

127.0.0.1	FALSE	/v0_5_0/	FALSE	0	name	zengl
127.0.0.1	FALSE	/v0_5_0/	FALSE	0	hobby	play game

[parallels@localhost zenglServerTest]$ cat my_webroot/v0_16_0/cookies.txt 
# Netscape HTTP Cookie File
# http://curl.haxx.se/docs/http-cookies.html
# This file was generated by libcurl! Edit at your own risk.

127.0.0.1	FALSE	/v0_5_0/	FALSE	0	name	zengl
127.0.0.1	FALSE	/v0_5_0/	FALSE	0	hobby	play game
[parallels@localhost zenglServerTest]$ 

    test_cookiejar.zl脚本会通过curl去请求 http://127.0.0.1:8084/v0_5_0/set_header.zl 脚本,set_header.zl脚本在执行时会返回 Set-Cookie: name=zengl 之类的设置Cookie的响应头,curl在获取到这些响应头信息后,就会将需要设置的Cookie写入到'COOKIEJAR'所指定的cookies.txt文件中。

COOKIE:

    和'COOKIE'选项相关的示例脚本为:my_webroot/v0_16_0/test_cookie.zl脚本:

use builtin, curl, request;
def TRUE 1;
def FALSE 0;

rqtSetResponseHeader("Content-Type: text/html; charset=utf-8");

print 'curl version: ' + curlVersion() + '<br/>';

curl_handle = curlEasyInit();
curlEasySetopt(curl_handle, 'URL', 'http://127.0.0.1:8084/v0_5_0/show_header.zl');
curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0');
curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE);
curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE);
curlEasySetopt(curl_handle, 'TIMEOUT', 30);
curlEasySetopt(curl_handle, 'COOKIE', 'name=daniel; present=yes; job=programmer; =hello world');
ret = curlEasyPerform(curl_handle, &content);
if(ret == 0)
	print content;
else
	print 'error: ' + curlEasyStrError(ret);
endif
curlEasyCleanup(curl_handle);

    'COOKIE'选项值必须是字符串,表示自定义的Cookie信息,curl在发送请求时,会将这些自定义的Cookie作为请求头发送出去。test_cookie.zl脚本的执行结果如下:

test_cookie.zl脚本在浏览器中的执行结果

    test_cookie.zl脚本会通过curl去请求8084端口的v0_5_0目录中的show_header.zl脚本,该脚本会将请求过来的请求头显示出来,同时还会将请求头中的Cookie转成数组。

PROXY:

    和'PROXY'选项相关的示例脚本为:my_webroot/v0_16_0/test_proxy.zl脚本:

use builtin, curl, request;
def TRUE 1;
def FALSE 0;

rqtSetResponseHeader("Content-Type: text/html; charset=utf-8");

print 'curl version: ' + curlVersion();

curl_handle = curlEasyInit();
curlEasySetopt(curl_handle, 'URL', 'https://www.google.com');
curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0');
curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE);
curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE);
curlEasySetopt(curl_handle, 'TIMEOUT', 30);
curlEasySetopt(curl_handle, 'PROXY', 'http://127.0.0.1:8232');
// curlEasySetopt(curl_handle, 'PROXY', 'socks5h://127.0.0.1:1080');
curlEasySetopt(curl_handle, 'VERBOSE', TRUE);
curlEasySetopt(curl_handle, 'STDERR', 'dump_proxy.txt');
ret = curlEasyPerform(curl_handle, &content, &size);
if(ret == 0)
	print 'size: ' + size;
	print 'content: ' + content;
else
	print 'error: ' + curlEasyStrError(ret);
endif
curlEasyCleanup(curl_handle);

    'PROXY'的选项值必须是字符串,表示需要设置的http,socks5之类的代理。其中,socks协议的代理需要7.21.7及以上的版本才支持,低版本的curl库只支持http协议的代理。如果系统中只有低版本的curl库,可以通过polipo将socks代理转为http代理。

    上面脚本会通过http代理去访问www.google.com,执行结果如下:

test_proxy.zl脚本在浏览器中的执行结果

    由于只通过代理抓取了主体数据,而主体数据中没有包含logo之类的,所以上图中的LOGO就无法显示出来。

    在test_proxy.zl脚本中还通过VERBOSE选项,将连接代理的详细信息,输出到了STDERR选项所指定的dump_proxy.txt文件中。如果代理连接失败,可以通过该文件了解到更多信息,方便排查问题:

[parallels@localhost zenglServerTest]$ cat my_webroot/v0_16_0/dump_proxy.txt 
* About to connect() to proxy 127.0.0.1 port 8232 (#8)
*   Trying 127.0.0.1...
* Connected to 127.0.0.1 (127.0.0.1) port 8232 (#8)
* Establish HTTP proxy tunnel to www.google.com:443
> CONNECT www.google.com:443 HTTP/1.1
Host: www.google.com:443
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0
Proxy-Connection: Keep-Alive

< HTTP/1.1 200 Tunnel established
< 
* Proxy replied OK to CONNECT request
* Initializing NSS with certpath: sql:/etc/pki/nssdb
* warning: ignoring value of ssl.verifyhost
* skipping SSL peer certificate verification
* SSL connection using TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
* Server certificate:
* 	subject: CN=www.google.com,O=Google LLC,L=Mountain View,ST=California,C=US
* 	start date: Dec 04 09:33:00 2018 GMT
* 	expire date: Feb 26 09:33:00 2019 GMT
* 	common name: www.google.com
* 	issuer: CN=Google Internet Authority G3,O=Google Trust Services,C=US
> GET / HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0
Host: www.google.com
Accept: */*

< HTTP/1.1 200 OK
< Date: Thu, 20 Dec 2018 11:20:44 GMT
< Expires: -1
< Cache-Control: private, max-age=0
< Content-Type: text/html; charset=UTF-8
< Strict-Transport-Security: max-age=31536000
< P3P: CP="This is not a P3P policy! See g.co/p3phelp for more info."
< Server: gws
< X-XSS-Protection: 1; mode=block
< X-Frame-Options: SAMEORIGIN
< Set-Cookie: 1P_JAR=2018-12-20-11; expires=Sat, 19-Jan-2019 11:20:45 GMT; path=/; domain=.google.com
< Set-Cookie: NID=152=vza0OKlZhU6Ewhoom6mYcOJDCTppJNg_AY42XCTHWmjp8k-cxKbevj-GVXD1LHZ-ODKeUlI7rzYweOAaSbu3zFbvy6IdivGlp4TPyO-gg8eExkyPswwW1DFw-28uYvwNSgqCEfkAM2ucza2bAMQ_r82KuvoakIOLXkPQERlj3C8; expires=Fri, 21-Jun-2019 11:20:45 GMT; path=/; domain=.google.com; HttpOnly
< Alt-Svc: quic=":443"; ma=2592000; v="44,43,39,35"
< Accept-Ranges: none
< Vary: Accept-Encoding
< Transfer-Encoding: chunked
< 
* Connection #8 to host 127.0.0.1 left intact
[parallels@localhost zenglServerTest]$ 

POSTFIELDS:

    和'POSTFIELDS'选项相关的示例脚本为:my_webroot/v0_16_0/test_postfields.zl脚本:

use builtin, curl, request;
def TRUE 1;
def FALSE 0;

rqtSetResponseHeader("Content-Type: text/html; charset=utf-8");

print 'curl version: ' + curlVersion() + '<br/>';

curl_handle = curlEasyInit();
curlEasySetopt(curl_handle, 'URL', 'http://127.0.0.1:8084/v0_2_0/post.zl');
curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0');
curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE);
curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE);
curlEasySetopt(curl_handle, 'TIMEOUT', 30);
curlEasySetopt(curl_handle, 'POSTFIELDS', 'name=zengl&hobby=play game!&其他=测试数据,我是谁!');
ret = curlEasyPerform(curl_handle, &content);
if(ret == 0)
	print content;
else
	print 'error: ' + curlEasyStrError(ret);
endif
curlEasyCleanup(curl_handle);

    'POSTFIELDS'的选项值必须是字符串,表示需要设置的application/x-www-form-urlencoded类型的POST请求,上面脚本的执行结果如下:

test_postfields.zl脚本在浏览器中的执行结果

VERBOSE,STDERR:

    和'VERBOSE'以及'STDERR'选项相关的示例脚本为:my_webroot/v0_16_0/test_verbose.zl脚本:

use builtin, curl, request;
def TRUE 1;
def FALSE 0;

rqtSetResponseHeader("Content-Type: text/html; charset=utf-8");

print 'curl version: ' + curlVersion();

curl_handle = curlEasyInit();
curlEasySetopt(curl_handle, 'URL', 'https://www.example.com');
curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0');
curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE);
curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE);
curlEasySetopt(curl_handle, 'TIMEOUT', 30);
curlEasySetopt(curl_handle, 'VERBOSE', TRUE);
curlEasySetopt(curl_handle, 'STDERR', 'dump.txt');
// curlEasySetopt(curl_handle, 'STDERR', 'dump.txt', 'ab');
ret = curlEasyPerform(curl_handle, &content, &size);
if(ret == 0)
	print 'size: ' + size;
	print 'content: ' + content;
else
	print 'error: ' + curlEasyStrError(ret);
endif
curlEasyCleanup(curl_handle);

    'VERBOSE'的选项值必须是整数类型,表示是否输出详细的连接信息,默认是0,即不输出连接信息。如果要输出连接信息,可以将选项值设置为1。上面脚本中就通过将'VERBOSE'选项设置为TRUE即整数1,从而让curl能够输出详细的连接信息。

    'STDERR'可以有两个选项值:

    第一个选项值表示相对于当前主执行脚本的文件路径,即需要输出VERBOSE信息到哪个文件中。上面脚本中指定的文件为dump.txt

    第二个选项值是可选的,表示需要以什么模式来打开该文件,默认是wb,表示以写入模式来打开文件,该模式会清空文件中原有的内容。如果将其设置为ab,则表示以追加的方式打开文件,这样VERBOSE信息就会追加到文件的末尾。

    test_verbose.zl脚本的执行结果如下:

[parallels@localhost zenglServerTest]$ curl http://127.0.0.1:8083/v0_16_0/test_verbose.zl
curl version: libcurl/7.29.0 NSS/3.34 zlib/1.2.11 libidn/1.28 libssh2/1.4.3
size: 1270
content: <!doctype html>
<html>
<head>
    <title>Example Domain</title>

.....................................................................
</body>
</html>

[parallels@localhost zenglServerTest]$ cat my_webroot/v0_16_0/dump.txt 
* About to connect() to www.example.com port 443 (#10)
*   Trying 93.184.216.34...
* Connected to www.example.com (93.184.216.34) port 443 (#10)
* Initializing NSS with certpath: sql:/etc/pki/nssdb
* warning: ignoring value of ssl.verifyhost
* skipping SSL peer certificate verification
* SSL connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
* Server certificate:
* 	subject: CN=www.example.org,OU=Technology,O=Internet Corporation for Assigned Names and Numbers,L=Los Angeles,ST=California,C=US
* 	start date: Nov 28 00:00:00 2018 GMT
* 	expire date: Dec 02 12:00:00 2020 GMT
* 	common name: www.example.org
* 	issuer: CN=DigiCert SHA2 Secure Server CA,O=DigiCert Inc,C=US
> GET / HTTP/1.1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0
Host: www.example.com
Accept: */*

< HTTP/1.1 200 OK
< Cache-Control: max-age=604800
< Content-Type: text/html; charset=UTF-8
< Date: Thu, 20 Dec 2018 12:07:18 GMT
< Etag: "1541025663+ident"
< Expires: Thu, 27 Dec 2018 12:07:18 GMT
< Last-Modified: Fri, 09 Aug 2013 23:54:35 GMT
< Server: ECS (sjc/4E8B)
< Vary: Accept-Encoding
< X-Cache: HIT
< Content-Length: 1270
< 
* Connection #10 to host www.example.com left intact
[parallels@localhost zenglServerTest]$ 

    上面的test_verbose.zl脚本将请求www.example.com相关的详细连接信息,都保存到了dump.txt文件中,在dump.txt中还可以看到请求头和响应头信息。

curlEasyPerform新增参数:

    当前版本在curlEasyPerform模块函数中增加了&ptr参数(该参数是可选的,位于&size参数之后),可以获取到指向了抓取的数据的指针,利用该指针可以保存图像等二进制数据。相关测试脚本为:my_webroot/v0_16_0/test_download.zl,该脚本的代码如下:

use builtin, curl, request;
def TRUE 1;
def FALSE 0;

rqtSetResponseHeader("Content-Type: text/html; charset=utf-8");

print 'curl version: ' + curlVersion();

curl_handle = curlEasyInit();
curlEasySetopt(curl_handle, 'URL', 'https://raw.githubusercontent.com/zenglong/zenglOX/master/screenshot/v302_1.jpg');
// curlEasySetopt(curl_handle, 'URL', 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1544860168&di=62c1ebae354ee09f89093043177cfd2a&imgtype=jpg&er=1&src=http%3A%2F%2Fwinters.com%2Fwp-content%2Fuploads%2FPGTK315CM.jpg');
curlEasySetopt(curl_handle, 'USERAGENT', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:63.0) Gecko/20100101 Firefox/63.0');
curlEasySetopt(curl_handle, 'FOLLOWLOCATION', TRUE);
curlEasySetopt(curl_handle, 'SSL_VERIFYPEER', FALSE);
curlEasySetopt(curl_handle, 'TIMEOUT', 30);
ret = curlEasyPerform(curl_handle, &content, &size, &ptr);
if(ret == 0)
    print 'size: ' + size;
    bltWriteFile('download.jpg', ptr, size);
    print 'write to <a href="download.jpg" target="_blank">download.jpg</a>';
else
    print 'error: ' + curlEasyStrError(ret);
endif
curlEasyCleanup(curl_handle);
bltFree(ptr);

    上面脚本中,在curlEasyPerform模块函数中使用了&ptr,将获取的图像数据的指针存储到ptr变量。接着就可以使用bltWriteFile模块函数,根据ptr指针和size图像的字节大小,将curl抓取到的图像的二进制数据写入到download.jpg文件中了。

    该脚本的执行结果如下:

test_download.zl脚本通过curl下载图片,并保存为download.jpg文件

下载的download.jpg图片文件

bltFree及bltReadFile模块函数:

    当前版本还在builtin模块中,新增了bltFree和bltReadFile模块函数。这些模块函数相关的C代码位于module_builtin.c文件中:

/**
 * bltFree模块函数,用于释放掉zengl脚本中分配的指针资源
 * 某些模块函数会分配一些指针以供调用者使用,
 * 例如,curl模块的curlEasyPerform模块函数,当提供了&ptr参数时,
 * 就会在内部分配一个指针,该指针指向的内存空间中存储了curl抓取到的数据,当抓取到的数据是jpg,png之类的图像等的二进制数据时,
 * 脚本中就可以利用指针将这些二进制数据写入文件,从而可以将curl下载的图像数据保存为文件,
 * 当这些指针不再需要使用时,可以用bltFree模块函数将其释放掉,如果没有使用该模块函数进行释放,
 * 在脚本退出时,zengl虚拟机也会自动清理这些指针
 *
 * 该模块函数的第一个参数ptr表示需要释放的指针,在zengl脚本中,指针其实就是一个整数,该整数代表了指针所指向的内存地址
 */
ZL_EXP_VOID module_builtin_free(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: bltFree(ptr): integer");
    zenglApi_GetFunArg(VM_ARG,1,&arg);
    if(arg.type != ZL_EXP_FAT_INT) {
        zenglApi_Exit(VM_ARG,"the first argument [ptr] of bltFree must be integer");
    }
    ZL_EXP_VOID * ptr = (ZL_EXP_VOID *)arg.val.integer;
    if(ptr != NULL)
        zenglApi_FreeMem(VM_ARG, ptr);
    zenglApi_SetRetVal(VM_ARG, ZL_EXP_FAT_INT, ZL_EXP_NULL, 0, 0);
}

/**
 * bltReadFile模块函数,根据文件名读取文件内容
 * 该模块函数的第一个参数filename表示需要读取的文件的文件名,该文件名是一个相对于当前主执行脚本的路径,
 * 第二个&content参数必须是引用类型,用于存储读取的文件内容,
 * 第三个&size参数是可选的,当提供了该参数时,也必须是引用类型,用于存储文件的字节大小,
 * 该模块函数如果执行成功会返回整数0,执行失败则返回小于0的值,当返回值为-1时表示文件不存在,返回值为-2时表示无法打开文件,
 * 例如:
    use builtin;
    ret = bltReadFile('cookies.txt', &file_content, &file_size);
    if(ret == 0)
        print 'cookies.txt file size: ' + file_size;
        print 'file content: \n' + file_content;
    else
        print 'read cookies.txt failed, maybe the file does not exists, or open failed.';
    endif
 * 上面这段代码会将cookies.txt文件的内容读取到file_content变量,同时将该文件的字节大小存储到file_size变量
 */
ZL_EXP_VOID module_builtin_read_file(ZL_EXP_VOID * VM_ARG, ZL_EXP_INT argcount)
{
    ZENGL_EXPORT_MOD_FUN_ARG arg = {ZL_EXP_FAT_NONE,{0}};
    if(argcount < 2)
        zenglApi_Exit(VM_ARG,"usage: bltReadFile(filename, &content[, &size]): integer");
    zenglApi_GetFunArg(VM_ARG,1,&arg);
    if(arg.type != ZL_EXP_FAT_STR) {
        zenglApi_Exit(VM_ARG,"the first argument [filename] of bltReadFile must be string");
    }
    char * filename = arg.val.str;
    .........................................................................
}

    bltFree模块函数在之前提到过的test_download.zl脚本中就用到了,bltReadFile模块函数也在之前介绍过的test_cookiejar.zl文件中用到了。

调整进程名称:

    该版本对进程名进行了调整,在主进程中包含了端口号,当前工作目录,所使用的配置文件等。子进程名称中也包含了父进程的进程ID。类似如下所示:

[parallels@localhost zenglServerTest]$ ps aux | grep zenglServer
paralle+ 22252  0.0  0.1 139068  2348 ?        Ss   15:31   0:00 zenglServer: master[8083] cwd:/media/psf/Home/zenglServerTest -c config.zl -l logfile
paralle+ 22253  0.0  0.3 301524  6420 ?        Sl   15:31   0:00 zenglServer: child(0) ppid:22252
paralle+ 22256  0.0  0.0 139068  1752 ?        S    15:31   0:00 zenglServer: cleaner  ppid:22252
..................................................
[parallels@localhost zenglServerTest]$ 

    上面master[8083]表示该zenglServer所绑定的是8083端口,cwd:/media/psf/Home.... 表示zenglServer的当前工作目录。-c config.zl表示使用的配置文件是config.zl,该配置文件是相对于当前工作目录的,-l logfile表示所使用的日志文件是logfile,该日志文件名也是相对于当前工作目录的。

    子进程中的ppid:22252,表示这些子进程的父进程的进程ID是22252。

    为了修改进程名称,在根目录中专门增加了两个文件:zlsrv_setproctitle.c 和 zlsrv_setproctitle.h,这两个文件是专门用来设置进程名称的,zlsrv_setproctitle.c文件的代码如下:

/*
 * zlsrv_setproctitle.c
 *
 *  Created on: Dec 16, 2018
 *      Author: zengl
 */

#include "zlsrv_setproctitle.h"
#include <stdlib.h>
#include <string.h>

#define ZLSRV_ERR_ALLOC_FAILED "alloc failed for init setproctitle"

extern char **environ;

extern char ** zlsrv_main_argv;

static char * st_zlsrv_argv_last;

/**
 * 设置进程名称的初始化操作,在设置进程名称之前,需要先将环境变量拷贝到新建的堆空间中,
 * 并将environ数组中包含的旧的环境变量指针指向新的堆空间,在将环境变量拷贝出去后,
 * 就可以设置很长的进程名称了,设置进程名称的原理是将进程main函数的argv[0]指向的字符串进行修改,
 * 但是argv[0]指向的原字符串可能很短,这样当设置较长的进程名称时,就会溢出,
 * 而进程main函数的argv参数列表后面紧跟的是环境变量,因此就会将环境变量给破坏掉,
 * 所以在设置新的进程名称之前,需要先将环境变量拷贝一份,并将环境变量指针指向拷贝数据
 *
 * 函数代码参考自nginx的代码,参考地址:https://github.com/firebase/nginx/blob/master/src/os/unix/ngx_setproctitle.c
 */
int zlsrv_init_setproctitle(char ** errorstr)
{
    int i;
    size_t size;
    char * p;

    size = 0;

    for (i = 0; environ[i]; i++) {
        size += strlen(environ[i]) + 1;
    }

    p = malloc(size);
    if(p == NULL) {
        (*errorstr) = ZLSRV_ERR_ALLOC_FAILED;
        return -1;
    }

    st_zlsrv_argv_last = zlsrv_main_argv[0];

    for (i = 0; zlsrv_main_argv[i]; i++) {
        if (st_zlsrv_argv_last == zlsrv_main_argv[i]) {
            st_zlsrv_argv_last = zlsrv_main_argv[i] + strlen(zlsrv_main_argv[i]) + 1;
        }
    }

    for (i = 0; environ[i]; i++) {
        if (st_zlsrv_argv_last == environ[i]) {
            size = strlen(environ[i]) + 1;
            st_zlsrv_argv_last = environ[i] + size;
            strncpy(p, environ[i], size);
            environ[i] = (char *) p;
            p += size;
        }
    }

    st_zlsrv_argv_last--;
    return 0;
}

/**
 * 在执行了上面的初始化操作后,就可以使用下面这个函数来设置当前进程的名称,
 * 设置进程名称时,只需将新的进程名称,拷贝到进程main函数的argv[0]所指向的内存空间中即可,
 * 在设置了新的进程名称后,还将进程名称之后的原有的残留数据给清空了(包括参数列表后的环境变量,环境变量已经在初始化时拷贝出去了,所以没有影响),
 * 防止ps在显示进程名称时,将进程名之后的原来残留的数据当作参数列表给显示出来。
 */
void zlsrv_setproctitle(char * title)
{
    char * p;
    size_t size;

    zlsrv_main_argv[1] = NULL;
    p = zlsrv_main_argv[0];
    size = strlen(title);
    if(size > (st_zlsrv_argv_last - p))
        size = st_zlsrv_argv_last - p;
    strncpy(p, title, size);
    p += size;
    if(st_zlsrv_argv_last - p) {
        memset(p, '\0', (st_zlsrv_argv_last - p));
    }
}

新增-l命令行参数,用于指定日志文件:

    当前版本的zenglServer在启动时,还可以使用-l的命令行参数来指定日志文件名:

[parallels@localhost zenglServerTest]$ ./zenglServer -l logfile_test
[parallels@localhost zenglServerTest]$ ps aux | grep zenglServer
paralle+ 25592  0.0  0.1 139068  2344 ?        Ss   21:22   0:00 zenglServer: master[8083] cwd:/media/psf/Home/zenglServerTest -c config.zl -l logfile_test
paralle+ 25593  0.0  0.0 155460  1756 ?        Sl   21:22   0:00 zenglServer: child(0) ppid:25592
paralle+ 25596  0.0  0.0 139068  1756 ?        S    21:22   0:00 zenglServer: cleaner  ppid:25592
paralle+ 25604  0.0  0.0 112720   988 pts/1    S+   21:22   0:00 grep --color=auto zenglServer
[parallels@localhost zenglServerTest]$ kill 25592
[parallels@localhost zenglServerTest]$ tail -n 20 logfile_test 
run config.zl complete, config: 
port: 8083 process_num: 1
webroot: my_webroot
session_dir: my_sessions session_expire: 1440 cleaner_interval: 3600
remote_debug_enable: False remote_debugger_ip: 127.0.0.1 remote_debugger_port: 9999 zengl_cache_enable: False shm_enable: False shm_min_size: 307200
bind done
accept sem initialized.
process_max_open_fd_num: 1024 
Master: Spawning child(0) [pid 25593] 
Master: Spawning cleaner [pid 25596] 
epoll max fd count : 896 
------------ cleaner sleep begin: 1545312169
Termination signal received! Killing children..
All children reaped, shutting down.
------------ remove shm number: 0
closed accept_sem
shutdowned server socket
closed server socket
===================================

[parallels@localhost zenglServerTest]$ 

    上面通过-l logfile_test指定了zenglServer所使用的日志文件为logfile_test。

    以上就是当前版本的相关内容,就到这里,休息,休息一下。

结束语:

    梦想只要能持久,就能成为现实。我们不就是生活在梦想中的吗?

—— 阿尔弗雷德·丁尼生

 

上下篇

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

上一篇: zenglServer v0.15.0 - v0.15.1 增加curl模块,用于执行数据抓取操作

相关文章

zenglServer v0.13.0 目录入口文件以及模板路径调整

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

zenglServer v0.18.0 直接在命令行中执行脚本

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

zenglServer v0.7.0-v0.7.1 mustache模板解析

zenglServer v0.20.0 增加openssl加密解密相关的模块