此项目适合二次开发,可以实现自己想实现的CGI应用程序。往下拉,是CGI部分,自己可以自行更改。
仅供参考!!!
mhttp.h
#ifndef _MHTTP_H_
#define _MHTTP_H_
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/epoll.h>
#include<sys/wait.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/sendfile.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>
#define MAX 1024
const char* status_line="HTTP/1.0 200 OK\r\n";
const char* blank_line="\r\n";
void DoConnect(int sock,int epollfd);
void hander_request(int sock,int epollfd);
void err_404(int sock);
int echo_www(int sock,const char* path,int size);
int exe_cgi(int sock,char *method,char *path,char *arg);
int startup(const char* ip,int port);
void SetNoblock(int sock);
void clear_header(int sock);
int getaline(int sock,char buf[],int size);
#endif
mhttp.c
#include"mhttp.h"
void SetNoblock(int fd){
int fl=fcntl(fd,F_GETFL);
if(fl<0){
perror("fcntl");
return;
}
fcntl(fd,F_SETFL,fl | O_NONBLOCK);
}
void DoConnect(int conn_fd,int epollfd){
struct sockaddr_in client;
socklen_t len=sizeof(client);
int listen_sock=accept(conn_fd,(struct sockaddr*)&client,&len);
printf("监听到client %d \n",listen_sock);
if(listen_sock<0){
perror("accept");
}
struct epoll_event conn_ev;
conn_ev.events=EPOLLIN;
conn_ev.data.fd=listen_sock;
int ret=epoll_ctl(epollfd,EPOLL_CTL_ADD,listen_sock,&conn_ev);
if(ret<0){
perror("epoll_ctl");
return;
}
return;
}
int startup(const char*ip,int port){
int sock=socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_addr.s_addr=inet_addr(ip);
local.sin_port=htons(port);
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0){
perror("bind");
return -2;
}
if(listen(sock,10)<0){
perror("listen");
return -3;
}
return sock;
}
int getaline(int sock,char buf[],int size){
int i=0;
char c='\0';
while(i<size-1&&c!='\n'){
ssize_t r=recv(sock,&c,1,0);
if(r>0){
if(c=='\r'){
recv(sock,&c,1,MSG_PEEK); //MSG_PEEK保留已读的字节
if(c=='\n'){
recv(sock,&c,1,0);//此处\r\n ->\n
}else{
c='\n';//\r ->\n
}
}
buf[i++]=c;
}else{
return -1;
}
}
buf[i]=0;
return i;
}
void clear_header(int sock){
char buf[MAX];
int ret=-1;
do{
ret=getaline(sock,buf,sizeof(buf));
}while(ret>0&&strcmp(buf,"\n")!=0);
}
int exe_cgi(int sock,char *method,char *path,char *arg){
char METHOD[MAX/10];
char QUERY_STRING[MAX];
char CONTENT_LENGTH[MAX];
int content_len=-1;
if(strcasecmp(method,"GET")==0){
clear_header(sock); //读完空行之前的数据
}
else{ //POST 方法
char buf[MAX];
int ret=-1;
while(1){
ret=getaline(sock,buf,sizeof(buf));
if(strncasecmp(buf,"Content-Length: ",16)==0){
content_len=atoi(&buf[16]);
}
if(1==strlen(buf)){
break;
}
}
if(content_len==-1){
err_404(sock);
return -1;
}
//getaline(sock,postbuf,content_len);
//printf("POST参数:%s\n",postbuf);
}
//如果是post已获得content length
//开始处理参数,并且响应
//这里用子进程处理参数,父进程将参数传给子进程,又子进程来完成cgi程序,父子进程间通信,使用管道。
int input[2];
int output[2];
if(pipe(input)<0|| pipe(output)<0){
err_404(404);
return -2;
}
send(sock,status_line,strlen(status_line),0);
send(sock,blank_line,strlen(blank_line),0);
int cpid=fork();
if(cpid<0){
perror("fork");
return -3;
}
if(cpid==0){ //子进程
close(input[1]);
close(output[0]);
sprintf(METHOD,"METHOD=%s",method);
putenv(METHOD);
if(strcasecmp(method,"GET")==0){
sprintf(QUERY_STRING,"QUERY_STRING=%s",arg);
putenv(QUERY_STRING);
}
else{//POST
printf("进入POST 传参\n");
sprintf(CONTENT_LENGTH,"CONTENT_LENGTH=%d",content_len);
putenv(CONTENT_LENGTH);
printf("传参完成\n");
}
dup2(input[0],0);
dup2(output[1],1);
//进程替换
execl(path,path,NULL);
exit(1);
}
else{ //父进程 通过管道传递数据
close(input[0]);
close(output[1]);
if(strcasecmp(method,"POST")==0){
int i=0;
char c='\0';
for(;i<content_len;++i){
recv(sock,&c,1,0);
write(input[1],&c,1);
}
}
while(1){
char c='\0';
ssize_t ret=read(output[0],&c,1);
if(ret>0){
send(sock,&c,1,0);
}else{
break;
}
}
waitpid(cpid,NULL,0);
close(input[1]);
close(output[0]);
}
return 0;
}
int echo_www(int sock,const char* path,int size){
printf("打开路径%s\n",path);
int fd=open(path,O_RDONLY);
if(fd<0){
perror("open");
return 404;
}
printf("打开文件描述符%d\n",fd);
send(sock,status_line,strlen(status_line),0);
send(sock,blank_line,strlen(blank_line),0);
printf("开始响应页面,页面大小%d\n",size);
sendfile(sock,fd,NULL,size);
close(fd);
return 200;
}
void err_404(int sock){
printf("开始错误解析:\n");
char err_path[1024]={0};
if(err_path[0]=='\0')
strcat(err_path,"mengtokaloroot/api/v1/err404.html");
printf("错误的路径: %s\n",err_path);
struct stat st;
stat(err_path,&st);
int size=st.st_size;
echo_www(sock,err_path,size);
}
void hander_request(int sock,int epollfd){
char buf[MAX];
char method[MAX/10];
char url[MAX];
char path[MAX];
int cgi=0;
char* query_string=NULL;
int errno_code=200;
size_t i=0,j=0;
if(getaline(sock,buf,sizeof(buf))<=0){
errno_code=404;
goto end;
}
printf("%s\n",buf);
while(i<sizeof(method)-1&&j<sizeof(buf)&&!isspace(buf[j])){
method[i]=buf[j];
++i,++j;
}
// 获取方法
method[i]=0;
i=0;
while(isspace(buf[j])){
++j;
}
while(i<sizeof(url)&&j<sizeof(buf)&&!isspace(buf[j])){
url[i]=buf[j];
++i,++j;
}
//获取url
url[i]=0;
// metod, url
if(strcasecmp(method,"POST")&&strcasecmp(method,"GET")){
errno_code=404;
goto end;
}
if(strcasecmp(method,"POST")==0){
cgi=1;
}else{
query_string=url;
while(*query_string){
if(*query_string=='?'){ //如果是get方法,只有带有参数的GET方法才是CGI,而URL里?号后面是参数
*query_string='\0'; //分割 将url 分为两半
query_string++;//参数保存在 query_string中
cgi=1;
break;
}
query_string++;
}
}
// mentokaloroot/a/b
sprintf(path,"mengtokaloroot/api/v1%s",url);
printf("path : %s \n",path);
if(path[strlen(path)-1]=='/'){
strcat(path,"index.html");
}
printf("请求的资源路径 %s \n",path);
struct stat st;
if(stat(path,&st)<0){
errno_code=404;
printf("文件错误\n");
goto end;
}else{
if(S_ISDIR(st.st_mode)){ //判断是否为目录
strcat(path,"/index.html");
}else if(st.st_mode& S_IXUSR|| \
st.st_mode & S_IXOTH|| \
st.st_mode & S_IXUSR){
cgi=1;
printf("确认为CGI\n");
}else{
}
//method,path,cgi,query_string
if(cgi){
printf("进入CGI\n");
int res=exe_cgi(sock,method,path,query_string);
if(res==0){
printf("cgi 执行完毕!\n");
}
}else{
clear_header(sock);
//not cgi,GET, 参数NULL ,path,size,
errno_code= echo_www(sock,path,st.st_size);
}
}
end:
if(errno_code!=200){
err_404(sock);
}
close(sock);
epoll_ctl(epollfd,EPOLL_CTL_DEL,sock,NULL);
return;
}
main.c
#include"mhttp.h"
#include"mhttp.c"
//采用多路转接模型EPOLL来解决并发问题,工作模式为LT模式
int main(int argc,char*argv[]){
if(argc!=3){
printf("please input ip and port");
return 1;
}
int listen_sock=startup(argv[1],atoi(argv[2]));
int epoll_fd=epoll_create(10);
if(epoll_fd<0){
perror("epoll_create");
return 2;
}
SetNoblock(listen_sock);
struct epoll_event event;
event.events=EPOLLIN | EPOLLET;
event.data.fd=listen_sock;
if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_sock,&event)<0){
perror("epoll_ctl");
return 3;
}
while(1){
struct epoll_event events[10];
int size=epoll_wait(epoll_fd,events,sizeof(events)/sizeof(events[0]),-1);
if(size<0){
perror("epoll_wait");
continue;
}
if(size==0){
printf("epoll timeout\n");
continue;
}
int i=0;
for(;i<size;++i){
if(!(events[i].events& EPOLLIN)){
continue;
}
if(events[i].data.fd==listen_sock){
DoConnect(listen_sock,epoll_fd);
}
else{
hander_request(events[i].data.fd,epoll_fd);
}
}
}
return 0;
}
根目录应该包含,服务器拥有的资源,以及相应的CGI程序等等。这题提供一个GET方法解析获得参数的CGI程序,和POST方法解析获得BODY里参数的CGI程序。
POST方法:
#include<stdlib.h>
#include"bookapi.h"
#include"bookapi.cpp"
int main(){
cout<<"<a href=\"/index.html\">BACK</a>"<<endl;
char* len=getenv("CONTENT_LENGTH");
int lens=atoi(len);
char postbuf[1024]={0};
//从stdin里获得Body里参数。因为我在主程序中,将Body里的参数,重定向到了stdin里,配合CONTENT_LENGTH,就可以解析出来。
fgets(postbuf,lens+1,stdin);
string bname;
string binfo;
int i=6;
bool turn=false;
while(postbuf[i]!='\0'){
if(postbuf[i]=='&'){
turn=true;
i+=7;
}
if(!turn){
bname+=postbuf[i];
}else{
binfo+=postbuf[i];
}
++i;
}
if(bname.size()>19){
cout<<"length of bookname is less then 19"<<endl;
return -1;
}
bookapi bookin;
if(bookin.insertbook(bname,binfo)==0){
cout<<"success !"<<endl;
}else{
cout<<"defualt! "<<endl;
}
return 0;
}
GET方法: 相对简单,只需要使用环境变量就行。
#include<stdlib.h>
#include"bookapi.h"
#include"bookapi.cpp"
void urldecoder(char *p)
{
int i=0;
while(*(p+i))
{
if ((*p=*(p+i)) == '%')
{
*p=*(p+i+1) >= 'A' ? ((*(p+i+1) & 0XDF) - 'A') + 10 : (*(p+i+1) - '0');
*p=(*p) * 16;
*p+=*(p+i+2) >= 'A' ? ((*(p+i+2) & 0XDF) - 'A') + 10 : (*(p+i+2) - '0');
i+=2;
}
else if (*(p+i)=='+')
{
*p=' ';
}
p++;
}
*p='\0';
}
int main(){
cout<<"<a href=\"/index.html\">BACK</a>"<<endl;
char * str=getenv("QUERY_STRING");
urldecoder(str);
bookapi in;
cout<<str<<endl;
//search=1111
while(*str!='='){
str++;
}
++str;
string bname(str);
if(bname.size()>19){
cout<<"<br />"<<endl;
cout<<"default"<<endl;
cout<<"<br />"<<endl;
cout<<"the length of bookname must be less then 19"<<endl;
return -1;
}
in.selectbook(bname);
return 0;
}