CGI

CGI

一个网关协议,按照CGI协议规定的数据格式,进行数据的处理(接收数据,输出数据)。存在目的是为了使web能够有动态页面的处理能力。是网站上实现动态页面的最简单和常用的方法

HTTP协议

CGI程序其实就是HTTP请求中的一种请求资源的类型,所以需要简单的了解一下HTTP协议

客户端(浏览器)与web服务器的交互流程

WEB服务器是使用TCP/IP协议搭建的一个服务器,获取静态资源的流程如下:

静态资源获取

下面是大学期间写的一个web服务器的demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>

/*
* 初始化监听socket描述符
* */
int init_listen_socket(short port) {
int listen_fd;
int ret;
struct sockaddr_in server_addr;

listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0) {
fprintf(stderr, "fail to socket : %s\n", strerror(errno));
return -1;
}

int on = 1;
ret = setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (ret == -1) {
perror("set sock reuse addr:");
return -1;
}

memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

ret = bind(listen_fd, (struct sockaddr *) &server_addr, sizeof(server_addr));
if (ret < 0) {
perror("fail to bind");
return -1;
}

listen(listen_fd, 5);

return listen_fd;
}

void get_filetype(char *filename, char *filetype)
{
if (strstr(filename, ".html"))
strcpy(filetype, "text/html");
else if (strstr(filename, ".gif"))
strcpy(filetype, "image/gif");
else if (strstr(filename, ".png"))
strcpy(filetype, "image/png");
else if (strstr(filename, ".jpg"))
strcpy(filetype, "image/jpeg");
else
strcpy(filetype, "text/plain");
}

int main(int argc, char *argv[])
{
int listen_fd;
int new_fd;
char buf[1024];
char head[1024];
struct stat *file = NULL;
char *date = NULL;
char filetype[20] = {0};
int ret;
FILE *fp;
unsigned short port = 8888;

if (argc == 2) {
port = strtoul(argv[1], NULL, 10);
}

listen_fd = init_listen_socket(port);
if (listen_fd == -1) {
exit(-1);
}
printf("listen %d port...\n", port);

while (1) {
new_fd = accept(listen_fd, NULL, NULL);
if (new_fd < 0) {
perror("fail to accept");
break;
}
printf("Have a new connection!\n");


memset(buf, 0, sizeof(buf));
ret = recv(new_fd, buf, sizeof(buf), 0);
if(ret<0){
perror("recv");
}

printf("recv:%s" ,buf);
for(int i = 4;;i++)
if(buf[i] == 'H'&& buf[i+1] == 'T' && buf[i+2] == 'T' && buf[i+3] == 'P'){
buf[i-1] = 0;
break;
}
char *filename = buf + 5;
if(strlen(filename)==0)
strcpy(filename, "index.html");

memset(head,0,sizeof(head));
if(strcmp(filename,"health")==0)
{
filename="health.html";
}
fp = fopen(filename ,"rb");
if(NULL == fp){
strcpy(filename, "404.html");
sprintf(head, "HTTP/1.0 404 NOT FOUND\r\n");//应该为404
fp = fopen(filename ,"rb");
}else
sprintf(head, "HTTP/1.0 200 OK\r\n");
fseek(fp, 0, SEEK_END);
int len = ftell(fp);
if (len <= 0)
{
fclose(fp);
return -1;
}
fseek(fp, 0, SEEK_SET);

date =(char *)malloc( 0x01 << 24);

len = fread(date, 1, len, fp);

get_filetype(filename, filetype);

sprintf(head, "%sServer: Tiny Web Server\r\n", head);
sprintf(head, "%sConnection: close\r\n", head);
sprintf(head, "%sContent-length: %d\r\n", head,len);//file->st_size
sprintf(head, "%sContent-type: %s\r\n\r\n", head, filetype);
//write(new_fd, head , strlen(head));
printf("Response headers:\n");
printf("%s", head);

ret = send(new_fd, head ,strlen(head) , 0) ;
if(ret <0){
perror("send head");
exit(1);
}
ret = send(new_fd, date ,len , 0) ;
if(ret <0){
perror("send date");
exit(1);
}
free(date);
close(new_fd);
printf("close connection!\n");
}
return 0;
}

获取动态资源(CGI)如下:

静态资源获取

CGI程序

输出头部

1
2
Content-type:text/html\r\n\r\n
Content-Type: application/json\r\n\r\n

头部实际不是文本的一部分,是服务器与浏览器之间的信息协议,常见头部为:

头部

获取数据

  1. 通过环境变量(GET方式)
  2. stdin(POST方式)

GET方式

  1. 通过环境变量:REQUEST_METHOD来判断是否是GET方式
  2. 从环境变量:QUERY_STRING获取数据
1
2
3
4
5
6
7
8
char *pQuery = NULL;
char *pMethod = NULL;
pMethod = getenv("REQUEST_METHOD");

if (!strcmp(pMethod, "GET"))
{
pQuery = getenv("QUERY_STRING");
}

POST方式

  1. 通过环境变量:REQUEST_METHOD 来判断是否是POST方式
  2. 从环境变量:CONTENT_LENGTH 获取数据长度n
  3. 从stdin中读取n个字节的数据
1
2
3
4
5
6
7
8
char *pQuery = NULL;
char *pMethod = NULL;
pMethod = getenv("REQUEST_METHOD");

if (!strcmp(pMethod, "POST")){
len = atoi(getenv("CONTENT_LENGTH"));
fgets(pQuery,len+1,stdin);
}

环境变量

环境变量 意义
SERVER_NAME CGI脚本运行时的主机名和IP地址.
SERVER_SOFTWARE 你的服务器的类型如: CERN/3.0 或 NCSA/1.3.
GATEWAY_INTERFACE 运行的CGI版本. 对于UNIX服务器, 这是CGI/1.1.
SERVER_PROTOCOL 服务器运行的HTTP协议. 这里当是HTTP/1.0.
SERVER_PORT 服务器运行的TCP口,通常Web服务器是80.
REQUEST_METHOD POST 或 GET, 取决于你的表单是怎样递交的.
HTTP_ACCEPT 浏览器能直接接收的Content-types, 可以有HTTP Accept header定义.
HTTP_USER_AGENT 递交表单的浏览器的名称、版本 和其他平台性的附加信息。
HTTP_REFERER 递交表单的文本的 URL,不是所有的浏览器都发出这个信息,不要依赖它
PATH_INFO 附加的路径信息, 由浏览器通过GET方法发出.
PATH_TRANSLATED 在PATH_INFO中系统规定的路径信息.
SCRIPT_NAME 指向这个CGI脚本的路径, 是在URL中显示的(如, /cgi-bin/thescript).
QUERY_STRING 脚本参数或者表单输入项(如果是用GET递交). QUERY_STRING 包含URL中问号后面的参数.
REMOTE_HOST 递交脚本的主机名,这个值不能被设置.
REMOTE_ADDR 递交脚本的主机IP地址.
REMOTE_USER 递交脚本的用户名. 如果服务器的authentication被激活,这个值可以设置。
REMOTE_IDENT 如果Web服务器是在ident (一种确认用户连接你的协议)运行, 递交表单的系统也在运行ident, 这个变量就含有ident返回值.
CONTENT_TYPE 如果表单是用POST递交, 这个值将是 application/x-www-form-urlencoded. 在上载文件的表单中, content-type 是个 multipart/form-data.
CONTENT_LENGTH 对于用POST递交的表单, 标准输入口的字节数.

输出数据

1、输出数据要和输出头部相匹配

2、通过标准输出stdout进行数据的输出

1
2
3
4
5
6
7
sprintf(output_buff, "Content-Type: application/json\r\n\r\n" \
"{" \
"\r\"status\":0," \
"\r\"msg\":username or password incorrect" \
"\r}");

websWrite(stdout, T("%s"), output_buff);

CGI实现原理

在lighttpd中是cgi_create_env函数中进行实现,实现步骤如下:

  1. 创建管道
  2. Fork()子进程
  3. 在子进程中设置管道一端为标准输入和标准输出
  4. 在子进程中添加环境变量
  5. 启动CGI进程
  6. 父进程负责向管道中写数据

CGI
https://carl-5535.github.io/2021/07/21/工作总结/cgi/
作者
Carl Chen
发布于
2021年7月21日
许可协议