这一小节我们将实现服务器对get和post的请求进行对cgi程序的调用。对于web服务器以前的章节已经实现了对get和post请求的调用接口,接下来给出对应接口的实现。
int WebServer::ServerGetFunction(int cli_fd,char *path,char *args)
{
ServerExecuteCGI(cli_fd,path,args);
return ;
}
int WebServer::ServerPostFunction(int cli_fd,char *path,char *args)
{
ServerExecuteCGI(cli_fd,path,args);
return ;
} int WebServer::ServerExecuteCGI(int cli_fd,char *path,char *args)
{
char query_env[];
char type[]="text/html";
pid_t pid;
int status;
int cgi_output[];
int cgi_input[]; if(pipe(cgi_output)<)
{
Page_500(cli_fd);
return ;
}
if(pipe(cgi_input)<)
{
Page_500(cli_fd);
return ;
} if((pid=fork())<)
{
Page_500(cli_fd);
return ;
}
if(pid==)//child
{
dup2(cgi_output[],);//cgi的输出端绑定文件描述符为1的输出端
dup2(cgi_input[],);
close(cgi_output[]);
close(cgi_input[]);
sprintf(query_env,"QUERY_STRING=%s",args);
putenv(query_env);
execl(path,path,args);
exit();
}
else //parent
{
char c;
close(cgi_output[]);//取消绑定
close(cgi_input[]);
Page_Headers(cli_fd,type,);
while(read(cgi_output[],&c,)>)
send(cli_fd,&c,,);
close(cgi_output[]);
close(cgi_input[]);
}
waitpid(pid,&status,);
return ;
}
然后我们写一个hello.c的文件然后编译成hello可执行文件(要保证权限是可执行的)
#include <stdio.h>
#include <stdlib.h> int main(int argc,char **args)
{
char *data;
printf("Hello\n");
printf("%s:%s\n",args[],args[]);
data=getenv("QUERY_STRING");
printf("query_string::%s\n",data);
return ;
}
然后在浏览器输入以下网址,然后查看执行结果
成功的执行c程序了。下面就做一个完整的例子,做一个用于登录的例子。
<html>
<head>
<title>Test</title>
<meta http-equiv="Content-Type" content="text/html ; charset=utf-8">
<link rel="stylesheet" href="style.css" type="text/css"/>
<script language="javascript" src="javascript.js"></script>
</head> <body>
<div class="ceshi">图片</div><img src="ab.jpg"></img>
<input name="button" type="button" value="Click!" onclick=hi();></input> <hr>
<br>使用post方式<br>
<form method="post" name="frm1" action="hello">
<label>用户名:</label>
<input type="text" name="username" />
<br>
<label>密码:</label>
<input type="password" name="password" />
<br>
<input type="submit" name="commit" value="登陆"/>
<br>
</form>
<hr>
<br>使用get方式<br>
<form method="get" name="frm1" action="hello">
<label>用户名:</label>
<input type="text" name="username" />
<br>
<label>密码:</label>
<input type="password" name="password" />
<br>
<input type="submit" name="commit" value="登陆"/>
<br>
</form>
</body>
</html>
然后在当前目录下有个hello.c程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h> int split(char **arr,char *str,const char*del)
{
char *s=NULL;
int i=;
s=strtok(str,del);
while(s!=NULL)
{
*arr++=s;
s=strtok(NULL,del);
i++;
}
return i;
} void split_key(char *ch,char *key,char *value)
{
int len;
int i;
int j;
len=strlen(ch);
j=;
for(i=;i<len;i++)
{
if(ch[i]=='=')
{
i++;
break;
}
key[j]=ch[i];
j++;
}
key[j]=;
j=;
for(;i<len;i++)
{
value[j]=ch[i];
j++;
}
value[j]=;
return ;
} int main(int argc,char **args)
{
char *data;
char *myargs[];
int cnt=;
int i;
char key[],value[];
char username[],password[];
memset(myargs,,sizeof(myargs));
cnt=split(myargs,args[],"&"); for(i=;i<cnt;i++)
{
split_key(myargs[i],key,value);
if(strcmp(key,"username")==)
strcpy(username,value);
if(strcmp(key,"password")==)
strcpy(password,value);
} //这里可以写上完整的网页
if(strcmp(username,"admin")== && strcmp(password,"")==)
{
printf("<p>登陆成功</p>");
}
else
{
printf("<p>登陆失败</p>");
}
return ;
}
ServerRequest函数修改了一些BUG后的代码
int WebServer::ServerRequest(int cli_fd)
{
char buf[];
int size=;
int i,j;
char method[];//用于保存请求方式
char url[];
char path[];
char args[];
struct stat st;
int cgi;//cgi 为0 表示get普通方法 1表示get带参方法 2表示post方法
pid_t pid;
memset(buf,,sizeof(buf));
cgi=;
//获取第一行请求信息 一般格式为: GET / HTTP/1.1
// POST / HTTP/1.1
size=get_line(cli_fd,buf,sizeof(buf));
//cout<<"\t\t"<<buf<<endl;
i=,j=;
//截取第一个单词
while(!isspace(buf[j]) && (i<sizeof(method)-))
{
method[i]=buf[j];
i++;j++;
}
method[i]='\0';
//取第一个与第二个单词之间的空格
while(isspace(buf[j]) && (j<sizeof(buf)))
j++; if(strcasecmp(method,"GET") && strcasecmp(method,"POST"))
{
Page_501(cli_fd);
return -;
} if(strcasecmp(method,"GET")==)
{
// cout<<"此次请求的方式是GET方法"<<endl;
cgi=;
}
else if(strcasecmp(method,"POST")==)
{
// cout<<"此次请求的方式是POST方法"<<endl;
cgi=;
} //截取第二个单词
i=;
int flag=;
while(!isspace(buf[j]) && (i<sizeof(url)-) && (j<sizeof(buf)))
{
if(buf[j]=='?')
{
flag=;
j++;
url[i]='\0';
i=;
cgi=(cgi==?:);
continue;
}
if(flag==)
{
url[i]=buf[j];
i++;j++;
}
else if(flag==)
{
args[i]=buf[j];
i++;j++;
}
}
if(flag==)
url[i]='\0';
else
args[i]='\0'; sprintf(path,"www%s",url);//这个是web服务器的主目录,这个以后可以处理成读取配置文件,这里就先写固定的www目录
if(path[strlen(path)-]=='/')
strcat(path,"index.html");//同上 //cout<<"============>此次请求的地址为:"<<path<<":"<<args<<endl; //根据文件名,获取该文件的文件信息。如果为-1,表示获取该文件失败
if(stat(path,&st)==-)
{
while((size>) && strcmp("\n",buf))//去除掉多余的请求头信息
size=get_line(cli_fd,buf,sizeof(buf));
Page_404(cli_fd);
}
else
{
if(S_ISDIR(st.st_mode))//判断url地址,如果是个目录,那么就访问该目录的index.html
{
strcat(path,"/index.html");
if(stat(path,&st)==-)
{
Page_404(cli_fd);
}
}
if(!S_ISDIR(st.st_mode)&&((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH)))//判断该url地址所对应的文件是否是可执行,并且是否有权限
{
//是一个cgi程序
if(strcasecmp(method,"GET")==)
cgi=;
else if(strcasecmp(method,"POST")==)
cgi=;
else
cgi=;
}
cout<<"访问:"<<path<<endl;
if(cgi==)//如果cgi为0,那么就表示该url所对应的文件不是cgi程序,而是一个简单的静态页面
{
pid = fork();
if(pid==)
{
ServerCatHttpPage(cli_fd,path,st.st_size);
}
}
else if(cgi==)//get方法带参数
{
pid=fork();
if(pid==)
{
while((size>) && strcmp("\n",buf))//去除掉多余的请求头信息
size=get_line(cli_fd,buf,sizeof(buf));
ServerGetFunction(cli_fd,path,urldecode(args));
}
}
else if(cgi==)//post方法
{
pid=fork();
if(pid==)
{
int content_length=;
while((size>) && strcmp("\n",buf))//去除掉多余的请求头信息
{
size=get_line(cli_fd,buf,sizeof(buf));
buf[]='\0';
if(strcasecmp(buf,"Content-Length:")==)
{
content_length=atoi(&(buf[]));
}
}
if(content_length==)
{
Page_400(cli_fd);
return ;
}
char c;
j=;
for(int i=;i<content_length;i++)
{
recv(cli_fd,&c,,);
args[j]=c;
j++;
}
args[j]=;
ServerPostFunction(cli_fd,path,urldecode(args));
}
}
}
close(cli_fd);
return ;
}
运行时的界面
用户名密码正确时
用户名密码错误时
同理GET方法的请求也是可以了。
上面实现的程序是使用c原来来写的实现cgi,听说perl的cgi很出名,那么接下来就实现对perl-cgi的支持。首先要安装perl,一般的Linux都有自带,接下来就需要一个perl的cgi库,安装的方式为 yum install perl-CGI 进行安装。 安装成功与否运行下面脚本就知道了。
#!/usr/bin/perl -Tw use strict;
use CGI; my($cgi) = new CGI; #print $cgi->header('text/html');
print $cgi->start_html(-title => "Example CGI script",
-BGCOLOR => 'red');
print $cgi->h1("CGI Example");
print $cgi->p, "This is an example of CGI\n";
print $cgi->p, "Parameters given to this script:\n";
print "<UL>\n";
foreach my $param ($cgi->param)
{
print "<LI>", "$param ", $cgi->param($param), "\n";
}
print "</UL>";
print $cgi->end_html, "\n";
注意如果要保证我们的Webserver可以通过execl进行调用的话,还要修改cgi脚本程序的执行权限(chomd)
下面这个是带参数的结果图
如果想要c语言的语法,但是对于网页格式有太多的没有必要的HTML标签,这里可是使用一个cgi的库,可以加快cgi程序的开发,Fastcgi模块(http://www.fastcgi.com/devkit/doc/fastcgi-prog-guide/ap_guida.htm)
到这里我们的web服务器易筋经实现最基本的功能了,可以做很多事了。我们的webserver编译后大小才22K而已。