Socket网络编程--简单Web服务器(5)
这一小节我们将实现服务器对get和post的请求进行对cgi程序的调用。对于web服务器以前的章节已经实现了对get和post请求的调用接口,接下来给出对应接口的实现。
1 int WebServer::ServerGetFunction(int cli_fd,char *path,char *args) 2 { 3 ServerExecuteCGI(cli_fd,path,args); 4 return 0; 5 } 6 int WebServer::ServerPostFunction(int cli_fd,char *path,char *args) 7 { 8 ServerExecuteCGI(cli_fd,path,args); 9 return 0; 10 } 11 12 int WebServer::ServerExecuteCGI(int cli_fd,char *path,char *args) 13 { 14 char query_env[1024]; 15 char type[16]="text/html"; 16 pid_t pid; 17 int status; 18 int cgi_output[2]; 19 int cgi_input[2]; 20 21 if(pipe(cgi_output)<0) 22 { 23 Page_500(cli_fd); 24 return 0; 25 } 26 if(pipe(cgi_input)<0) 27 { 28 Page_500(cli_fd); 29 return 0; 30 } 31 32 if((pid=fork())<0) 33 { 34 Page_500(cli_fd); 35 return 0; 36 } 37 if(pid==0)//child 38 { 39 dup2(cgi_output[1],1);//cgi的输出端绑定文件描述符为1的输出端 40 dup2(cgi_input[0],0); 41 close(cgi_output[0]); 42 close(cgi_input[1]); 43 sprintf(query_env,"QUERY_STRING=%s",args); 44 putenv(query_env); 45 execl(path,path,args); 46 exit(0); 47 } 48 else //parent 49 { 50 char c; 51 close(cgi_output[1]);//取消绑定 52 close(cgi_input[0]); 53 Page_Headers(cli_fd,type,0); 54 while(read(cgi_output[0],&c,1)>0) 55 send(cli_fd,&c,1,0); 56 close(cgi_output[0]); 57 close(cgi_input[1]); 58 } 59 waitpid(pid,&status,0); 60 return 0; 61 }
然后我们写一个hello.c的文件然后编译成hello可执行文件(要保证权限是可执行的)
1 #include <stdio.h> 2 #include <stdlib.h> 3 4 int main(int argc,char **args) 5 { 6 char *data; 7 printf("Hello\n"); 8 printf("%s:%s\n",args[0],args[1]); 9 data=getenv("QUERY_STRING"); 10 printf("query_string::%s\n",data); 11 return 0; 12 }
然后在浏览器输入以下网址,然后查看执行结果
成功的执行c程序了。下面就做一个完整的例子,做一个用于登录的例子。
1 <html> 2 <head> 3 <title>Test</title> 4 <meta http-equiv="Content-Type" content="text/html ; charset=utf-8"> 5 <link rel="stylesheet" href="style.css" type="text/css"/> 6 <script language="javascript" src="javascript.js"></script> 7 </head> 8 9 <body> 10 <div class="ceshi">图片</div><img src="ab.jpg"></img> 11 <input name="button" type="button" value="Click!" onclick=hi();></input> 12 13 <hr> 14 <br>使用post方式<br> 15 <form method="post" name="frm1" action="hello"> 16 <label>用户名:</label> 17 <input type="text" name="username" /> 18 <br> 19 <label>密码:</label> 20 <input type="password" name="password" /> 21 <br> 22 <input type="submit" name="commit" value="登陆"/> 23 <br> 24 </form> 25 <hr> 26 <br>使用get方式<br> 27 <form method="get" name="frm1" action="hello"> 28 <label>用户名:</label> 29 <input type="text" name="username" /> 30 <br> 31 <label>密码:</label> 32 <input type="password" name="password" /> 33 <br> 34 <input type="submit" name="commit" value="登陆"/> 35 <br> 36 </form> 37 </body> 38 </html>
然后在当前目录下有个hello.c程序
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 5 int split(char **arr,char *str,const char*del) 6 { 7 char *s=NULL; 8 int i=0; 9 s=strtok(str,del); 10 while(s!=NULL) 11 { 12 *arr++=s; 13 s=strtok(NULL,del); 14 i++; 15 } 16 return i; 17 } 18 19 void split_key(char *ch,char *key,char *value) 20 { 21 int len; 22 int i; 23 int j; 24 len=strlen(ch); 25 j=0; 26 for(i=0;i<len;i++) 27 { 28 if(ch[i]=='=') 29 { 30 i++; 31 break; 32 } 33 key[j]=ch[i]; 34 j++; 35 } 36 key[j]=0; 37 j=0; 38 for(;i<len;i++) 39 { 40 value[j]=ch[i]; 41 j++; 42 } 43 value[j]=0; 44 return ; 45 } 46 47 int main(int argc,char **args) 48 { 49 char *data; 50 char *myargs[32]; 51 int cnt=0; 52 int i; 53 char key[32],value[32]; 54 char username[32],password[32]; 55 memset(myargs,0,sizeof(myargs)); 56 cnt=split(myargs,args[1],"&"); 57 58 for(i=0;i<cnt;i++) 59 { 60 split_key(myargs[i],key,value); 61 if(strcmp(key,"username")==0) 62 strcpy(username,value); 63 if(strcmp(key,"password")==0) 64 strcpy(password,value); 65 } 66 67 //这里可以写上完整的网页 68 if(strcmp(username,"admin")==0 && strcmp(password,"123456")==0) 69 { 70 printf("<p>登陆成功</p>"); 71 } 72 else 73 { 74 printf("<p>登陆失败</p>"); 75 } 76 return 0; 77 }
ServerRequest函数修改了一些BUG后的代码
1 int WebServer::ServerRequest(int cli_fd) 2 { 3 char buf[1024]; 4 int size=1024; 5 int i,j; 6 char method[255];//用于保存请求方式 7 char url[512]; 8 char path[1024]; 9 char args[1024]; 10 struct stat st; 11 int cgi;//cgi 为0 表示get普通方法 1表示get带参方法 2表示post方法 12 pid_t pid; 13 memset(buf,0,sizeof(buf)); 14 cgi=0; 15 //获取第一行请求信息 一般格式为: GET / HTTP/1.1 16 // POST / HTTP/1.1 17 size=get_line(cli_fd,buf,sizeof(buf)); 18 //cout<<"\t\t"<<buf<<endl; 19 i=0,j=0; 20 //截取第一个单词 21 while(!isspace(buf[j]) && (i<sizeof(method)-1)) 22 { 23 method[i]=buf[j]; 24 i++;j++; 25 } 26 method[i]='\0'; 27 //取第一个与第二个单词之间的空格 28 while(isspace(buf[j]) && (j<sizeof(buf))) 29 j++; 30 31 if(strcasecmp(method,"GET") && strcasecmp(method,"POST")) 32 { 33 Page_501(cli_fd); 34 return -1; 35 } 36 37 if(strcasecmp(method,"GET")==0) 38 { 39 // cout<<"此次请求的方式是GET方法"<<endl; 40 cgi=0; 41 } 42 else if(strcasecmp(method,"POST")==0) 43 { 44 // cout<<"此次请求的方式是POST方法"<<endl; 45 cgi=2; 46 } 47 48 //截取第二个单词 49 i=0; 50 int flag=0; 51 while(!isspace(buf[j]) && (i<sizeof(url)-1) && (j<sizeof(buf))) 52 { 53 if(buf[j]=='?') 54 { 55 flag=1; 56 j++; 57 url[i]='\0'; 58 i=0; 59 cgi=(cgi==0?1:2); 60 continue; 61 } 62 if(flag==0) 63 { 64 url[i]=buf[j]; 65 i++;j++; 66 } 67 else if(flag==1) 68 { 69 args[i]=buf[j]; 70 i++;j++; 71 } 72 } 73 if(flag==0) 74 url[i]='\0'; 75 else 76 args[i]='\0'; 77 78 sprintf(path,"www%s",url);//这个是web服务器的主目录,这个以后可以处理成读取配置文件,这里就先写固定的www目录 79 if(path[strlen(path)-1]=='/') 80 strcat(path,"index.html");//同上 81 82 //cout<<"============>此次请求的地址为:"<<path<<":"<<args<<endl; 83 84 //根据文件名,获取该文件的文件信息。如果为-1,表示获取该文件失败 85 if(stat(path,&st)==-1) 86 { 87 while((size>0) && strcmp("\n",buf))//去除掉多余的请求头信息 88 size=get_line(cli_fd,buf,sizeof(buf)); 89 Page_404(cli_fd); 90 } 91 else 92 { 93 if(S_ISDIR(st.st_mode))//判断url地址,如果是个目录,那么就访问该目录的index.html 94 { 95 strcat(path,"/index.html"); 96 if(stat(path,&st)==-1) 97 { 98 Page_404(cli_fd); 99 } 100 } 101 if(!S_ISDIR(st.st_mode)&&((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP) || (st.st_mode & S_IXOTH)))//判断该url地址所对应的文件是否是可执行,并且是否有权限 102 { 103 //是一个cgi程序 104 if(strcasecmp(method,"GET")==0) 105 cgi=1; 106 else if(strcasecmp(method,"POST")==0) 107 cgi=2; 108 else 109 cgi=0; 110 } 111 cout<<"访问:"<<path<<endl; 112 if(cgi==0)//如果cgi为0,那么就表示该url所对应的文件不是cgi程序,而是一个简单的静态页面 113 { 114 pid = fork(); 115 if(pid==0) 116 { 117 ServerCatHttpPage(cli_fd,path,st.st_size); 118 } 119 } 120 else if(cgi==1)//get方法带参数 121 { 122 pid=fork(); 123 if(pid==0) 124 { 125 while((size>0) && strcmp("\n",buf))//去除掉多余的请求头信息 126 size=get_line(cli_fd,buf,sizeof(buf)); 127 ServerGetFunction(cli_fd,path,urldecode(args)); 128 } 129 } 130 else if(cgi==2)//post方法 131 { 132 pid=fork(); 133 if(pid==0) 134 { 135 int content_length=0; 136 while((size>0) && strcmp("\n",buf))//去除掉多余的请求头信息 137 { 138 size=get_line(cli_fd,buf,sizeof(buf)); 139 buf[15]='\0'; 140 if(strcasecmp(buf,"Content-Length:")==0) 141 { 142 content_length=atoi(&(buf[16])); 143 } 144 } 145 if(content_length==0) 146 { 147 Page_400(cli_fd); 148 return 0; 149 } 150 char c; 151 j=0; 152 for(int i=0;i<content_length;i++) 153 { 154 recv(cli_fd,&c,1,0); 155 args[j]=c; 156 j++; 157 } 158 args[j]=0; 159 ServerPostFunction(cli_fd,path,urldecode(args)); 160 } 161 } 162 } 163 close(cli_fd); 164 return 0; 165 }
运行时的界面
用户名密码正确时
用户名密码错误时
同理GET方法的请求也是可以了。
上面实现的程序是使用c原来来写的实现cgi,听说perl的cgi很出名,那么接下来就实现对perl-cgi的支持。首先要安装perl,一般的Linux都有自带,接下来就需要一个perl的cgi库,安装的方式为 yum install perl-CGI 进行安装。 安装成功与否运行下面脚本就知道了。
1 #!/usr/bin/perl -Tw 2 3 use strict; 4 use CGI; 5 6 my($cgi) = new CGI; 7 8 #print $cgi->header('text/html'); 9 print $cgi->start_html(-title => "Example CGI script", 10 -BGCOLOR => 'red'); 11 print $cgi->h1("CGI Example"); 12 print $cgi->p, "This is an example of CGI\n"; 13 print $cgi->p, "Parameters given to this script:\n"; 14 print "<UL>\n"; 15 foreach my $param ($cgi->param) 16 { 17 print "<LI>", "$param ", $cgi->param($param), "\n"; 18 } 19 print "</UL>"; 20 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而已。
作者:无脑仔的小明 出处:http://www.cnblogs.com/wunaozai/ 本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。 如果文中有什么错误,欢迎指出。以免更多的人被误导。有需要沟通的,可以站内私信,文章留言,或者关注“无脑仔的小明”公众号私信我。一定尽力回答。 |