7. 错误提示页面
github地址:https://github.com/Skycrab/Linux-C-Web-Server
二、设计原理
首先介绍一些HTTP协议基本知识。
#1.GET/POST
本实现支持GET/POST方法,都是HTTP协议需要支持的标准方法。
GET方法主要是通过URL发送请求和传送数据,而POST方法在请求头空一格之后传送数据,所以POST方法比GET方法安全性高,因为GET方法可以直接看到传送的数据。另外一个区别就是GET方法传输的数据较小,而POST方法很大。所以一般表单,登陆页面等都是通过POST方法。
#2.MIME类型
当服务器获取客户端的请求的文件名,将分析文件的MIME类型,然后告诉浏览器改文件的MIME类型,浏览器通过MIME类型解析传送过来的数据。具体来说,浏览器请求一个主页面,该页面是一个HTML文件,那么服务器将”text/html”类型发给浏览器,浏览器通过HTML解析器识别发送过来的内容并显示。
下面将描述一个具体情景。
客户端使用浏览器通过URL发送请求,服务器获取请求。
如浏览器URL为:127.0.0.1/postAuth.html,
那么服务器获取到的请求为:GET /postAuth.html HTTP/1.1
意思是需要根目录下postAuth.html文件的内容,通过GET方法,使用HTTP/1.1协议(1.1是HTTP的版本号)。这是服务器将分析文件名,得知postAuth.html是一个HTML文件,所以将”text/html”发送给浏览器,然后读取postAuth.html内容发给浏览器。
实现简单的MIME类型识别代码如下:
主要就是通过文件后缀获取文件类型。
static void get_filetype(const 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, ".jpg")) strcpy(filetype, "image/jpeg"); else if (strstr(filename, ".png")) strcpy(filetype, "image/png"); else strcpy(filetype, "text/plain"); }
static void serve_static(int fd, char *filename, int filesize) { int srcfd; char *srcp, filetype[MAXLINE], buf[MAXBUF]; /* Send response headers to client */ get_filetype(filename, filetype); sprintf(buf, "HTTP/1.0 200 OK\r\n"); sprintf(buf, "%sServer: Tiny Web Server\r\n", buf); sprintf(buf, "%sContent-length: %d\r\n", buf, filesize); sprintf(buf, "%sContent-type: %s\r\n\r\n", buf, filetype); /* Send response body to client */ srcfd = Open(filename, O_RDONLY, 0); srcp = Mmap(0, filesize, PROT_READ, MAP_PRIVATE, srcfd, 0); Close(srcfd); #ifdef HTTPS if(ishttps) { SSL_write(ssl, buf, strlen(buf)); SSL_write(ssl, srcp, filesize); } else #endif { Rio_writen(fd, buf, strlen(buf)); Rio_writen(fd, srcp, filesize); } Munmap(srcp, filesize); }
static int parse_uri(char *uri, char *filename, char *cgiargs) { char *ptr; char tmpcwd[MAXLINE]; strcpy(tmpcwd,cwd); strcat(tmpcwd,"/"); if (!strstr(uri, "cgi-bin")) { /* Static content */ strcpy(cgiargs, ""); strcpy(filename, strcat(tmpcwd,Getconfig("root"))); strcat(filename, uri); if (uri[strlen(uri)-1] == '/') strcat(filename, "home.html"); return 1; } else { /* Dynamic content */ ptr = index(uri, '?'); if (ptr) { strcpy(cgiargs, ptr+1); *ptr = '\0'; } else strcpy(cgiargs, ""); strcpy(filename, cwd); strcat(filename, uri); return 0; } }
void get_dynamic(int fd, char *filename, char *cgiargs) { char buf[MAXLINE], *emptylist[] = { NULL },httpsbuf[MAXLINE]; int p[2]; /* Return first part of HTTP response */ sprintf(buf, "HTTP/1.0 200 OK\r\n"); sprintf(buf, "%sServer: Web Server\r\n",buf); #ifdef HTTPS if(ishttps) SSL_write(ssl,buf,strlen(buf)); else #endif Rio_writen(fd, buf, strlen(buf)); #ifdef HTTPS if(ishttps) { Pipe(p); if (Fork() == 0) { /* child */ Close(p[0]); setenv("QUERY_STRING", cgiargs, 1); Dup2(p[1], STDOUT_FILENO); /* Redirect stdout to p[1] */ Execve(filename, emptylist, environ); /* Run CGI program */ } Close(p[1]); Read(p[0],httpsbuf,MAXLINE); /* parent read from p[0] */ SSL_write(ssl,httpsbuf,strlen(httpsbuf)); } else #endif { if (Fork() == 0) { /* child */ /* Real server would set all CGI vars here */ setenv("QUERY_STRING", cgiargs, 1); Dup2(fd, STDOUT_FILENO); /* Redirect stdout to client */ Execve(filename, emptylist, environ); /* Run CGI program */ } } }
static void post_dynamic(int fd, char *filename, int contentLength,rio_t *rp) { char buf[MAXLINE],length[32], *emptylist[] = { NULL },data[MAXLINE]; int p[2]; #ifdef HTTPS int httpsp[2]; #endif sprintf(length,"%d",contentLength); memset(data,0,MAXLINE); Pipe(p); /* The post data is sended by client,we need to redirct the data to cgi stdin. * so, child read contentLength bytes data from fp,and write to p[1]; * parent should redirct p[0] to stdin. As a result, the cgi script can * read the post data from the stdin. */ /* https already read all data ,include post data by SSL_read() */ if (Fork() == 0) { /* child */ Close(p[0]); #ifdef HTTPS if(ishttps) { Write(p[1],httpspostdata,contentLength); } else #endif { Rio_readnb(rp,data,contentLength); Rio_writen(p[1],data,contentLength); } exit(0) ; } /* Send response headers to client */ sprintf(buf, "HTTP/1.0 200 OK\r\n"); sprintf(buf, "%sServer: Tiny Web Server\r\n",buf); #ifdef HTTPS if(ishttps) SSL_write(ssl,buf,strlen(buf)); else #endif Rio_writen(fd, buf, strlen(buf)); Dup2(p[0],STDIN_FILENO); /* Redirct p[0] to stdin */ Close(p[0]); Close(p[1]); setenv("CONTENT-LENGTH",length , 1); #ifdef HTTPS if(ishttps) /* if ishttps,we couldnot redirct stdout to client,we must use SSL_write */ { Pipe(httpsp); if(Fork()==0) { Dup2(httpsp[1],STDOUT_FILENO); /* Redirct stdout to https[1] */ Execve(filename, emptylist, environ); } Read(httpsp[0],data,MAXLINE); SSL_write(ssl,data,strlen(data)); } else #endif { Dup2(fd,STDOUT_FILENO); /* Redirct stdout to client */ Execve(filename, emptylist, environ); } }
static void serve_dir(int fd,char *filename) { DIR *dp; struct dirent *dirp; struct stat sbuf; struct passwd *filepasswd; int num=1; char files[MAXLINE],buf[MAXLINE],name[MAXLINE],img[MAXLINE],modifyTime[MAXLINE],dir[MAXLINE]; char *p; /* * Start get the dir * for example: /home/yihaibo/kerner/web/doc/dir -> dir[]="dir/"; */ p=strrchr(filename,'/'); ++p; strcpy(dir,p); strcat(dir,"/"); /* End get the dir */ if((dp=opendir(filename))==NULL) syslog(LOG_ERR,"cannot open dir:%s",filename); sprintf(files, "<html><title>Dir Browser</title>"); sprintf(files,"%s<style type=""text/css""> a:link{text-decoration:none;} </style>",files); sprintf(files, "%s<body bgcolor=""ffffff"" font-family=Arial color=#fff font-size=14px>\r\n", files); while((dirp=readdir(dp))!=NULL) { if(strcmp(dirp->d_name,".")==0||strcmp(dirp->d_name,"..")==0) continue; sprintf(name,"%s/%s",filename,dirp->d_name); Stat(name,&sbuf); filepasswd=getpwuid(sbuf.st_uid); if(S_ISDIR(sbuf.st_mode)) { sprintf(img,"<img src=""dir.png"" width=""24px"" height=""24px"">"); } else if(S_ISFIFO(sbuf.st_mode)) { sprintf(img,"<img src=""fifo.png"" width=""24px"" height=""24px"">"); } else if(S_ISLNK(sbuf.st_mode)) { sprintf(img,"<img src=""link.png"" width=""24px"" height=""24px"">"); } else if(S_ISSOCK(sbuf.st_mode)) { sprintf(img,"<img src=""sock.png"" width=""24px"" height=""24px"">"); } else sprintf(img,"<img src=""file.png"" width=""24px"" height=""24px"">"); sprintf(files,"%s<p><pre>%-2d%s""<a href=%s%s"">%-15s</a>%-10s%10d %24s</pre></p>\r\n",files,num++,img,dir,dirp->d_name,dirp->d_name,filepasswd->pw_name,(int)sbuf.st_size,timeModify(sbuf.st_mtime,modifyTime)); } closedir(dp); sprintf(files,"%s</body></html>",files); /* Send response headers to client */ sprintf(buf, "HTTP/1.0 200 OK\r\n"); sprintf(buf, "%sServer: Tiny Web Server\r\n", buf); sprintf(buf, "%sContent-length: %d\r\n", buf, strlen(files)); sprintf(buf, "%sContent-type: %s\r\n\r\n", buf, "text/html"); #ifdef HTTPS if(ishttps) { SSL_write(ssl,buf,strlen(buf)); SSL_write(ssl,files,strlen(files)); } else #endif { Rio_writen(fd, buf, strlen(buf)); Rio_writen(fd, files, strlen(files)); } exit(0); }
static long long ipadd_to_longlong(const char *ip) { const char *p=ip; int ge,shi,bai,qian; qian=atoi(p); p=strchr(p,'.')+1; bai=atoi(p); p=strchr(p,'.')+1; shi=atoi(p); p=strchr(p,'.')+1; ge=atoi(p); return (qian<<24)+(bai<<16)+(shi<<8)+ge; } int access_ornot(const char *destip) // 0 -> not 1 -> ok { //192.168.1/255.255.255.0 char ipinfo[16],maskinfo[16]; char *p,*ip=ipinfo,*mask=maskinfo; char count=0; char *maskget=Getconfig("mask"); const char *destipconst,*ipinfoconst,*maskinfoconst; if(maskget=="") { printf("ok:%s\n",maskget); return 1; } p=maskget; /* get ipinfo[] start */ while(*p!='/') { if(*p=='.') ++count; *ip++=*p++; } while(count<3) { *ip++='.'; *ip++='0'; ++count; } *ip='\0'; /* get ipinfo[] end */ /* get maskinfo[] start */ ++p; while(*p!='\0') { if(*p=='.') ++count; *mask++=*p++; } while(count<3) { *mask++='.'; *mask++='0'; ++count; } *mask='\0'; /* get maskinfo[] end */ destipconst=destip; ipinfoconst=ipinfo; maskinfoconst=maskinfo; return ipadd_to_longlong(ipinfoconst)==(ipadd_to_longlong(maskinfoconst)&ipadd_to_longlong(destipconst)); }
static char* getconfig(char* name) { /* pointer meaning: ...port...=...8000... | | | | | *fs | | | *be f->forward b-> back *fe | *bs s->start e-> end *equal */ static char info[64]; int find=0; char tmp[256],fore[64],back[64],tmpcwd[MAXLINE]; char *fs,*fe,*equal,*bs,*be,*start; strcpy(tmpcwd,cwd); strcat(tmpcwd,"/"); FILE *fp=getfp(strcat(tmpcwd,"config.ini")); while(fgets(tmp,255,fp)!=NULL) { start=tmp; equal=strchr(tmp,'='); while(isblank(*start)) ++start; fs=start; if(*fs=='#') continue; while(isalpha(*start)) ++start; fe=start-1; strncpy(fore,fs,fe-fs+1); fore[fe-fs+1]='\0'; if(strcmp(fore,name)!=0) continue; find=1; start=equal+1; while(isblank(*start)) ++start; bs=start; while(!isblank(*start)&&*start!='\n') ++start; be=start-1; strncpy(back,bs,be-bs+1); back[be-bs+1]='\0'; strcpy(info,back); break; } if(find) return info; else return NULL; }
IP地址信息如下:
Ubuntu的vboxnet0:
RedHateth0:
RedHat主机编译项目:
由于我们同事监听了8000和4444,所以有两个进程启动。
HTTP的首页:
目录显示功能:
HTTP GET页面:
HTTPGET响应:
从HTTP GET响应中我们观察URL,参数的确是通过URL传送过去的。
其中getAuth.c如下:
#include "wrap.h" #include "parse.h" int main(void) { char *buf, *p; char name[MAXLINE], passwd[MAXLINE],content[MAXLINE]; /* Extract the two arguments */ if ((buf = getenv("QUERY_STRING")) != NULL) { p = strchr(buf, '&'); *p = '\0'; strcpy(name, buf); strcpy(passwd, p+1); } /* Make the response body */ sprintf(content, "Welcome to auth.com:%s and %s\r\n<p>",name,passwd); sprintf(content, "%s\r\n", content); sprintf(content, "%sThanks for visiting!\r\n", content); /* Generate the HTTP response */ printf("Content-length: %d\r\n", strlen(content)); printf("Content-type: text/html\r\n\r\n"); printf("%s", content); fflush(stdout); exit(0); }
HTTPS的首页:由于我们的CA不可信,所以需要我们认可
认可后HTTPS首页:
HTTPS POST页面:
HTTPS POST响应:
从上我们可以看出,POST提交的参数的确不是通过URL传送的。