CGI 编程基础

                                                         CGI 编程基础

一、简述

      记--CGI编程基础,CGI编程简单例子。

       例子1:常用参数

       例子2:GET、POST请求参数获取、文件上传

       例子3:原生AJAX GET、POST请求

       例子4:JQuery AJAX GET、POST请求

       例子打包:https://wwa.lanzous.com/iGkmLh7ycqb

二、例子1--常用参数

客户端IP:REMOTE_ADDR

客户端浏览器信息:HTTP_USER_AGENT

客户端请求方式:REQUEST_METHOD

客户端语言版本:HTTP_ACCEPT_LANGUAGE

获取GET参数:QUERY_STRING

获取POST参数:CONTENT_LENGTH(内容长度)、CONTENT_TYPE(MIME类型)、CONTENT_FILE(文件名称)

测试代码:

/****************************************************************
 * Project:cgi_test
 * File name: cgi.c
 * Create on: 2020.09.14
 * Anthor: Genven_Liang
 * Description: cgi excample
 ****************************************************************/

#include <stdio.h>
#include <stdlib.h> //getenv

int main(void)
{	
	/* 说明返回内容类型为html文本 */
	printf("Content-Type:text/html\n\n");

	/* 客户端IP */
	char *client_ip = getenv("REMOTE_ADDR");

	/* 客户端浏览器信息 */
	char *user_agent = getenv("HTTP_USER_AGENT");

	/* 请求方式 */
	char *req_method = getenv("REQUEST_METHOD");

	/* 客户端语言 */
	char *client_language = getenv("HTTP_ACCEPT_LANGUAGE");

	/* 返回数据给浏览器 浏览器一般只处理第一句printf (除printf("Content-Typexxx")) */
	printf("client ip:%s<br/>request method:%s<br/>client language:%s<br/>user agent:%s", client_ip, req_method, client_language, user_agent);//<br/>是html的换行

	return 0;	
}

测试结果:

注:更多客户端信息请参考http请求首部:

 

三、例子2: GET、POST请求参数获取、文件上传

说明:在直接请求url是Get方式,参数直接在url后面添加,参数与url以'?'隔开,如:http://192.168.159.128/cgi-bin/test.cgi?参数

链接也是get方式。form表单请求可以指定get方式或者是post方式。method="get"或method="post"

使用form表单上传文件:设置enctype="multipart/form-data"

测试代码:

test.html文件

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>CGI 例子2</title>
	</head>

	<body>
		<a href="/cgi-bin/test.cgi?1234">Get请求</a>
		<hr/>
		<form action="/cgi-bin/test.cgi" method="get">
			<label>用户名:</label><input type="text" name="userName"/></br>
			<label>用户信息:</label><input type="text" name="userInfo"/></br>
			<input type="submit" name="get_request" value="Get请求">
		</form>
		<hr/>
		<form action="/cgi-bin/test.cgi" method="post" enctype="text/plain">
			<label>用户名:</label><input type="text" name="userName"/></br>
			<label>用户信息:</label><input type="text" name="userInfo"/></br>
			<input type="submit" name="post_request" value="Post请求">
		</form>
		<hr/>
		<form action="/cgi-bin/test.cgi" method="post" enctype="multipart/form-data">			
			<input type="submit" name="post_file" value="上传文件">
			<input type="file" name="file">
		</form>
	</body>
</html>

cgi.c文件

/****************************************************************
 * Project:cgi_test
 * File name: cgi.c
 * Create on: 2020.09.14
 * Anthor: Genven_Liang
 * Description: cgi excample
 ****************************************************************/

#include <stdio.h>
#include <stdlib.h> //getenv
#include <string.h>

#define BUF_MAX_SIZE	(2*1024)
#define FILE_BUF_MAX_SIZE	(4*1024)

int main(void)
{	
	/* 说明返回内容类型为html文本 */
	printf("Content-Type:text/html\n\n");

	/* 请求方式 */
	char *req_method = getenv("REQUEST_METHOD");

	if (0 == strcmp("GET", req_method)) { /* 处理GET请求 */
		char *get_arg = getenv("QUERY_STRING");//获取Get数据
		if (NULL == get_arg) {
			get_arg = "";
		}
		printf("get arguments:%s.", get_arg);
	} else if (0 == strcmp("POST", req_method)) { /* 处理POST请求 */
		char *content_len = getenv("CONTENT_LENGTH");//获取数据长度
		char *content_type = getenv("CONTENT_TYPE");//获取数据类型 application/x-www-form-urlencoded、multipart/form-data、text/plain 其中:multipart/form-data是文件传输

		int len = 0;
		if (NULL != content_len) {
			len = atoi(content_len);
		}		

		if (len > 0) { //获取post数据	
			if (NULL != content_type && NULL == strstr(content_type, "multipart/form-data")) {//普通文本参数
				char dat_buf[BUF_MAX_SIZE] = {0};
				if (len > BUF_MAX_SIZE) {
					len = BUF_MAX_SIZE;
				}
				len = fread(dat_buf, 1, len, stdin);
				printf("post type:%s. len:%d, data:%s.", content_type, len, dat_buf);
				//使用字符串分割函数获取各个参数:strtok_r
			} else {//文件数据, boundary包含文件偏移量,大文件采用的是续传方式,会用到该参数,例子上传的是小文件,因此没有判断处理
				//这里仅处理小文件不分包情况,大文件需要额外处理

				//获取边界标识符
				char *boundary = strstr(content_type, "boundary=");
				if (NULL == boundary) {
					printf("post-file find boundary error.");
					return -1;
				}
				boundary += strlen("boundary=");

				char file_buf[FILE_BUF_MAX_SIZE] = {0};//实际中最好使用堆空间,栈空间有限
				if (len > FILE_BUF_MAX_SIZE) {
					len = FILE_BUF_MAX_SIZE;
				}
				size_t read_size = fread(file_buf, 1, len, stdin);//获取post数据,里面不仅仅是文件数据,还有其它信息:文件名
				if (read_size < 1) {
					printf("post-file read file data error.");
					return -1;
				}
				char *fileNamePtr = strstr(file_buf, "filename=");//filename="post_file_test.txt"
				if (NULL == fileNamePtr) {
					printf("post-file get file name error.");
					return -1;
				}

				//提取文件名
				char file_name[128] = {0};
				sscanf(&fileNamePtr[sizeof("filename=")], "%s\"", file_name);				
				len = strlen(file_name);
				if (0 >= len) {
					printf("post-file file name is empty.");
					return -1;
				}

				//去掉文件名的右引号
				if (file_name[len - 1] == '"') {
					file_name[len - 1] = '\0';
				}

				len = strlen(file_name);
				if (0 >= len) {
					printf("post-file file name is empty.");
					return -1;
				}

				//找到文件数据起始 两个回车换行符
				char *file_start_ptr = strstr(fileNamePtr, "\r\n\r\n");
				if (NULL == file_start_ptr) {
					printf("post-file find file start error.file_buf:%s.", file_buf);
					return -1;
				}
				file_start_ptr += 4;

				//找到文件数据结尾处 边界符--boundary--
				char *file_end_ptr = strstr(file_start_ptr, boundary);
				if (NULL != file_end_ptr) {
					file_end_ptr -= 4;//边界符前后还有"--" 文件数据后面还有一个换车换行符2字节
					file_end_ptr[0] = '\0';//截断
				}

				len = strlen(file_start_ptr);
				if (0 >= len) {
					printf("post-file file empty.");
					return -1;
				}

				//保存数据 file_start_ptr
				char path[256] = {0};//这个必须是CGI有权限操作的路径, 不然会写不进去
				sprintf(path, "/var/www/upload/%s", file_name);
				FILE *save_file = fopen(path, "w+");
				if (NULL == save_file) {
					printf("post-file save error. data:%s.", file_start_ptr);
					return -1;
				}
				
				if (len != fwrite(file_start_ptr, 1, len, save_file)) {
					printf("post-file write error. data:%s.", file_start_ptr);
					fclose(save_file);
					return -1;
				}

				fclose(save_file);
				printf("save file success. file name:%s. size:%d.", file_name, len);
				//printf("post-file type:%s. file_name:%s, boundary:%s, len:%lu, data:%s.", content_type, file_name, boundary, read_size, file_buf);
			}
		} else {
			printf("post content_len error:%s.", content_type);
		}

	} else {
		printf("request method error:%s.", req_method);
	}

	return 0;	
}

测试结果:

 

四、例子3:原生AJAX GET、POST请求

判断用户名和密码是否正确:

测试代码:

test.html文件

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>CGI 例子3</title>
	</head>

	<body>
		<label>用户名:</label><input type="text" name="userName1"/></br>
		<label>密码:</label><input type="password" name="password1"/></br>
		<input type="button" name="get_request" onclick="AjaxGetRequest()" value="Ajax_Get请求">
		<hr/>
		<label>用户名:</label><input type="text" name="userName2"/></br>
		<label>密码:</label><input type="password" name="password2"/></br>
		<input type="button" name="post_request" onclick="AjaxPostRequest()" value="Ajax_Post请求">

		<script type="text/javascript">

			/* ajax get请求 */
			function AjaxGetRequest() {
				var xhr = new XMLHttpRequest();
				var data = "name=" + document.getElementsByName("userName1")[0].value + "&pwd="+document.getElementsByName("password1")[0].value;
		        xhr.open('GET', "http://192.168.159.128/cgi-bin/test.cgi?"+data, false);
		        xhr.onreadystatechange=function(){
		            // readyState == 4说明请求已完成
		            if (xhr.readyState==4) {
		                if (xhr.status==200 || xhr.status==304) {
		                    console.log(xhr.responseText);//
		                    if (xhr.responseText.indexOf("success") >=0 ) {
		                    	alert("Ajax GET 密码用户名正确");
		                    } else {
		                    	alert("Ajax GET 密码或用户名错误");
		                    }
		                }
		            }
		        }
		        
		        xhr.send();
			}

			/* ajax post请求 */
			function AjaxPostRequest() {
				var xhr=new XMLHttpRequest();
		        xhr.open('POST', "http://192.168.159.128/cgi-bin/test.cgi", false);
		        // 添加http头,发送信息至服务器时内容编码类型
		        xhr.setRequestHeader('Content-Type','application/x-www-form-urlencoded');
		        xhr.onreadystatechange=function(){
		            if (xhr.readyState==4){
		                if (xhr.status==200 || xhr.status==304){
		                    console.log(xhr.responseText);//
		                    if (xhr.responseText.indexOf("success") >=0 ) {
		                    	alert("Ajax POST 密码用户名正确");
		                    } else {
		                    	alert("Ajax POST 密码或用户名错误");
		                    }
		                }
		            }
		        }

		        var data = "name=" + document.getElementsByName("userName2")[0].value + "&pwd="+document.getElementsByName("password2")[0].value;
		        xhr.send(data);
			}
			
		</script>
	</body>
</html>

cgi.c文件

/****************************************************************
 * Project:cgi_test
 * File name: cgi.c
 * Create on: 2020.09.14
 * Anthor: Genven_Liang
 * Description: cgi excample
 ****************************************************************/

#include <stdio.h>
#include <stdlib.h> //getenv
#include <string.h>

#define BUF_MAX_SIZE	(2*1024)
#define FILE_BUF_MAX_SIZE	(4*1024)

int main(void)
{	
	/* 说明返回内容类型为html文本 */
	printf("Content-Type:text/html\n\n");

	/* 请求方式 */
	char *req_method = getenv("REQUEST_METHOD");

	if (0 == strcmp("GET", req_method)) { /* 处理GET请求 */
		char *get_arg = getenv("QUERY_STRING");//获取Get数据
		if (NULL == get_arg) {
			get_arg = "";
		}

		//获取用户名和密码
		char *token_name = NULL;
		char *token_pwd = NULL;
		token_name = strtok_r(get_arg, "&", &token_pwd);
		char *name = NULL;
		strtok_r(token_name, "=", &name);//用户名
		char *pwd = NULL;
		strtok_r(token_pwd, "=", &pwd);//密码

		if (0 == strcmp("liang", name) && 0 == strcmp("123", pwd)) {
			printf("get login success.");
		} else {
			printf("get name or password error.name:%s, pwd:%s.", name, pwd);
		}
	} else if (0 == strcmp("POST", req_method)) { /* 处理POST请求 */
		char *content_len = getenv("CONTENT_LENGTH");//获取数据长度
		char *content_type = getenv("CONTENT_TYPE");//获取数据类型 application/x-www-form-urlencoded、multipart/form-data、text/plain 其中:multipart/form-data是文件传输

		int len = 0;
		if (NULL != content_len) {
			len = atoi(content_len);
		}		

		if (len > 0) { //获取post数据	
			if (NULL != content_type && NULL == strstr(content_type, "multipart/form-data")) {//普通文本参数
				char dat_buf[BUF_MAX_SIZE] = {0};
				if (len > BUF_MAX_SIZE) {
					len = BUF_MAX_SIZE;
				}
				len = fread(dat_buf, 1, len, stdin);
				//printf("post type:%s. len:%d, data:%s.", content_type, len, dat_buf);
				//使用字符串分割函数获取各个参数:strtok_r
				char *token_name = NULL;
				char *token_pwd = NULL;
				token_name = strtok_r(dat_buf, "&", &token_pwd);
				char *name = NULL;
				strtok_r(token_name, "=", &name);//用户名
				char *pwd = NULL;
				strtok_r(token_pwd, "=", &pwd);//密码

				if (0 == strcmp("zhangsan", name) && 0 == strcmp("456", pwd)) {
					printf("post login success.");
				} else {
					printf("post name or password error.name:%s, pwd:%s.", name, pwd);
				}
			} 
		} else {
			printf("post content_len error:%s.", content_type);
		}

	} else {
		printf("request method error:%s.", req_method);
	}

	return 0;	
}

测试结果:

五、例子4:JQuery AJAX GET、POST请求

需要下载jqueryxxx.js 官网地址:https://jquery.com/download/

下载之后将js文件放在test.html的统计目录即可, 

测试代码

test.html文件

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<title>CGI 例子4</title>
	</head>

	<body>
		<label>用户名:</label><input type="text" name="userName1"/></br>
		<label>密码:</label><input type="password" name="password1"/></br>
		<input type="button" name="get_request" onclick="AjaxGetRequest()" value="Ajax_Get请求">
		<hr/>
		<label>用户名:</label><input type="text" name="userName2"/></br>
		<label>密码:</label><input type="password" name="password2"/></br>
		<input type="button" name="post_request" onclick="AjaxPostRequest()" value="Ajax_Post请求">

		<script type="text/javascript" src="./jquery-3.5.1.min.js"></script> <!-- 引用JQ库 -->
		<script type="text/javascript">

			/*jquery ajax get请求 */
			function AjaxGetRequest() {
				var arg = "name=" + document.getElementsByName("userName1")[0].value + "&pwd="+document.getElementsByName("password1")[0].value;
				var req_url = "http://192.168.159.128/cgi-bin/test.cgi?" + arg;
		        $.ajax({
				    url: req_url,
				    type:"GET",
				    success: function(result){
				         console.log(result);//
		                    if (result.indexOf("success") >=0 ) {
		                    	alert("JQ Ajax GET 密码用户名正确");
		                    } else {
		                    	alert("JQ Ajax GET 密码或用户名错误");
		                    }
				    },
				    error: function(){
				          alert("JQ Ajax GET 请求失败");
				    }
				 });
			}

			/* jquery ajax post请求 */
			function AjaxPostRequest() {
				
		        var dat = "name=" + document.getElementsByName("userName2")[0].value + "&pwd="+document.getElementsByName("password2")[0].value;

		        $.ajax({
				    url: "http://192.168.159.128/cgi-bin/test.cgi",
				    type:"POST",
				    data: dat,
				    success: function(result){
				         console.log(result);//
		                    if (result.indexOf("success") >=0 ) {
		                    	alert("JQ Ajax POST 密码用户名正确");
		                    } else {
		                    	alert("JQ Ajax POST 密码或用户名错误");
		                    }
				    },
				    error: function(){
				          alert("JQ Ajax POST 请求失败");
				    }
				 });
			}
			
		</script>
	</body>
</html>

cgi.c文件同例子3

测试结果:

JQuery Ajax说明:

url:是请求url地址:

type:“GET” 或 "POST"

async: 指示是否异步请求,false或true

dataType:可以是json,期望服务器返回的数据类型,不一致会进入error处理流程

success:是服务器响应后执行的处理

error:一般是服务器没有响应,超时处理。

result:是服务器返回的数据内容

$.ajax({
    url: ,
    type: '',
    async: '',
    dataType: '',
    data: {
          
    },
    success: function(result){
         
    },
    error: function(result){
          
    }
 })

六、总结

6.1 请求一般由浏览器发起,服务器被动响应并回传数据;也就是CGI是被调用的,不要试图使用全局静态变量记住某种状态,因为这是无效的。要想保持状态,客户端使用kookie、缓存、本地存储等、服务器端使用session、配置文件、环境变量等。

6.2 前端请求(html)--》服务器(Apache)--》后台处理(CGI)--》再经过服务器回传给前端

6.3 CGI返回的数据类型可以是一个完整的html数据、也可以仅仅是数据,最好是仅仅返回数据,让前端自行展示。

6.4 错误页可以直接返回一个简单的页面。

6.5 前端要等待CGI返回并处理结果的需要使用AJAX。

6.6 也可以在CGI程序设置浏览器端的cookie,但是需要在返回Content-Type之前,并且仅能设置一个cookie (测试中发现该情况,不保证正确性,有待验证)

6.7 CGI通过标准输出将数据发给前端,通过getenv("QUERY_STRING")获取get请求数据,通过标准输入获取post数据。

6.8 例子中CGI没有url解码操作,实际上需要对数据进行解码操作,url参数,post参数可能是经过url编码的,如空格,post会变为'+', get变为%20,问号get变为%3F。get方式其实就是%然后加上字符的ASCII码的十六进制数。还有中文编码。

url解码示例:


void UrlDecode(char *src, int len, char *dest)
{
	int i;
	int tmp;   
	for (i = 0; i < len; i++, dest++) {
		if (src[i] == '+') {
			*dest = ' ';
		} else if(src[i] == '%') {			 
			if (sscanf(&src[i+1], "%2x", &tmp)!=1) {
				tmp = '?';
			}
			*dest = tmp;
			i += 2;
		} else {
			*dest = src[i];
		}
	}
}

6.9 post请求的enctype

enctype规定在发送表单数据之前如何对其进行编码

enctype可以取以下三种值:

application/x-www-form-urlencoded(默认)

multipart/form-data (文件上传使用)

text/plain
6.9 设置默认编码为UTF-8,这样子html的中文不会乱码

执行:sudo vi  /etc/apache2/conf-available/charset.conf, 将#AddDefaultCharset UTF-8 前面的'#'号去掉

重启apache使配置生效:sudo /etc/init.d/apache2 restart

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页