http服务器小项目
来源:互联网 发布:阿里云智慧农业平台 编辑:程序博客网 时间:2024/06/04 19:40
1. http项目整体框架
1.1http协议格式
http请求由三部分组成,分别是:起始行、消息报头、请求正文
Request Line<CRLF>Header-Name: header-value<CRLF>Header-Name: header-value<CRLF>//一个或多个,均以<CRLF>结尾<CRLF>body//请求正文
- 起始行以一个方法符号开头,以空格分开,后面跟着请求的URI和协议的版本,格式如下:
Method Request-URI HTTP-Version CRLF其中 Method表示请求方法;Request-URI是一个统一资源标识符;HTTP-Version表示请求的HTTP协议版本;CRLF表示回车和换行(除了作为结尾的CRLF外,不允许出现单独的CR或LF字符)。
请求方法(所有方法全为大写)有多种,各个方法的解释如下:
GET 请求获取Request-URI所标识的资源POST 在Request-URI所标识的资源后附加新的数据HEAD 请求获取由Request-URI所标识的资源的响应消息报头PUT 请求服务器存储一个资源,并用Request-URI作为其标识DELETE 请求服务器删除Request-URI所标识的资源TRACE 请求服务器回送收到的请求信息,主要用于测试或诊断CONNECT 保留将来使用OPTIONS 请求查询服务器的性能,或者查询与资源相关的选项和需求应用举例: GET方法:在浏览器的地址栏中输入网址的方式访问网页时,浏览器采用GET方法向服务器获取资源,eg:GET /form.html HTTP/1.1 (CRLF)POST方法要求被请求服务器接受附在请求后面的数据,常用于提交表单。eg:POST /reg.jsp HTTP/ (CRLF)Accept:image/gif,image/x-xbit,... (CRLF)...HOST:www.guet.edu.cn (CRLF)Content-Length:22 (CRLF)Connection:Keep-Alive (CRLF)Cache-Control:no-cache (CRLF)(CRLF) //该CRLF表示消息报头已经结束,在此之前为消息报头user=jeffrey&pwd=1234 //此行以下为提交的数据
1.2整体流程
服务器启动,等待客户端请求到来;
客户端请求到来,创建新线程处理该请求;
读取httpHeader中的method,截取url,其中GET方法需要记录url问号之后的参数串;
根据url构造完整路径,如果是/结尾,则指定为该目录下的index.html;
获取文件信息,如果找不到文件,返回404,找到文件则判断文件权限;
如果是GET请求并且没有参数,或者文件不可执行,则直接将文件内容构造http信息返回给客户端;
如果是GET带参数,POST,文件可执行,则执行CGI;
GET请求略过httpHeader,POST方法需要记录httpHeader中的Content-Length:xx;
创建管道用于父子进程通信,fork产生子进程;
子进程设置环境变量,将标准输入和输出与管道相连,并且通过exec执行CGI;
如果是POST,父进程将读到post内容发送给子进程,并且接收子进程的输出,输出给客户端
2. 部分项目源码
项目完整代码见github:
https://github.com/excelentone/LinuxPractice/tree/master/http服务器
main.c
#include"thttpd.c"static void* msg_request(void *arg){ int sock=(int)arg; pthread_detach(pthread_self()); return (void*)handler_msg(sock);}int main(int argc,char* argv[]){ //daemon(1,0); if(argc!=3) { printf_log("Usage: [local_ip] [local_port]\n",FATAL); return 1; } int lis_sock=startup(argv[1],atoi(argv[2])); while(1) { struct sockaddr_in peer; socklen_t len=sizeof(peer); int sock=accept(lis_sock,(struct sockaddr*)&peer,&len); if(sock<0) { printf_log("accept failed",WRONING); continue; } pthread_t tid; if(pthread_create(&tid,NULL,msg_request,(void*)sock)>0) { printf_log("pthread_create failed",WRONING); close(sock); } } return 0;}
thttpd.c
#include"thttpd.h"//mysql 关系型数据库int startup(const char* _ip,int _port) //创建监听套接字{ assert(_ip); int sock=socket(AF_INET,SOCK_STREAM,0); if(sock<0) { printf_log("socket failed",FATAL); exit(2); } int opt=1; setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)); struct sockaddr_in local; local.sin_family=AF_INET; local.sin_port=htons(_port); local.sin_addr.s_addr=inet_addr(_ip); if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0) { printf_log("bind failed",FATAL); exit(3); } if(listen(sock,5)<0) { printf_log("listen failed",FATAL); exit(4); } return sock;}void printf_log(const char* msg,int level) //记录日志{ openlog("thttpd",LOG_PID|LOG_CONS|LOG_NDELAY,LOG_USER); switch(level) { case NORMAL: syslog(LOG_NOTICE,msg); break; case WRONING: syslog(LOG_WARNING,msg); break; case FATAL: syslog(LOG_ERR,msg); break; } closelog();}static int get_line(int sock,char* buf) //按行读取请求报头{ char ch='\0'; int i=0; ssize_t ret=0; while(i<SIZE && ch!='\n') { ret=recv(sock,&ch,1,0); if(ret>0&&ch=='\r') { ssize_t s=recv(sock,&ch,1,MSG_PEEK); if(s>0&&ch=='\n') { recv(sock,&ch,1,0); } else { ch='\n'; } } buf[i++]=ch; } buf[i]='\0'; return i;}static void clear_header(int sock) //清空消息报头{ char buf[SIZE]; int ret=0; do { ret=get_line(sock,buf); }while(ret!=1&&(strcmp(buf,"\n")!=0));}static void show_404(int sock) //404错误处理{ clear_header(sock); char* msg="HTTP/1.0 404 Not Found\r\n"; send(sock,msg,strlen(msg),0); //发送状态行 send(sock,"\r\n",strlen("\r\n"),0); //发送空行 struct stat st; stat("wwwroot/404.html",&st); int fd=open("wwwroot/404.html",O_RDONLY); sendfile(sock,fd,NULL,st.st_size); close(fd);}void echo_error(int sock,int err_code) //错误处理{ switch(err_code) { case 403: break; case 404: show_404(sock); break; case 405: break; case 500: break; defaut: break; }}static int echo_www(int sock,const char * path,size_t s) //处理非CGI的请求{ int fd=open(path,O_RDONLY); if(fd<0) { echo_error(sock,403); return 7; } char* msg="HTTP/1.0 200 OK\r\n"; send(sock,msg,strlen(msg),0); //发送状态行 send(sock,"\r\n",strlen("\r\n"),0); //发送空行 if(sendfile(sock,fd,NULL,s)<0) { echo_error(sock,500); return 8; } close(fd); return 0;}static int excu_cgi(int sock,const char* method,\ const char* path,const char* query_string) //处理CGI模式的请求{ char line[SIZE]; int ret=0; int content_len=-1; if(strcasecmp(method,"GET")==0) //如果是GET方法的CGI { //清空消息报头 clear_header(sock); } else //POST方法的CGI { //获取post方法的参数大小 do { ret=get_line(sock,line); if(strncmp(line,"Content-Length: ",16)==0) //post的消息体记录正文长度的字段 { content_len=atoi(line+16); //求出正文的长度 } }while(ret!=1&&(strcmp(line,"\n")!=0)); } //因为环境变量有上限,而GET方法传递的参数少,所以用环境变量 //POST方法,在正文内传参,参数多,所以用管道 //将method、query_string、content_len导出为环境变量 char method_env[SIZE/8]; char query_string_env[SIZE/8]; char content_len_env[SIZE/8]; sprintf(method_env,"METHOD=%s",method); //把是什么方法放到环境变量里,方便子进程执行的cGI判断是什么方法。 putenv(method_env); sprintf(query_string_env,"QUERY_STRING=%s",query_string); putenv(query_string_env); sprintf(content_len_env,"CONTENT_LEN=%d",content_len); putenv(content_len_env); int input[2]; //站在CGI程序的角度,创建两个管道 int output[2]; pipe(input); pipe(output); pid_t id=fork(); if(id<0) { printf_log("fork failed",FATAL); echo_error(sock,500); return 9; } else if(id==0) //子进程 { close(input[1]); //关闭input的写端 close(output[0]); //关闭output的读端 //将文件描述符重定向到标准输入标准输出 dup2(input[0],0); dup2(output[1],1); execl(path,path,NULL); //替换CGI程序 exit(1); } else //father { close(input[0]); //关闭input的读端 close(output[1]); //关闭output的写端 char ch='\0'; if(strcasecmp(method,"POST")==0) //如果是post方法,则父进程需要给子进程发送参数 { int i=0; for( i=0;i<content_len;i++) { recv(sock,&ch,1,0); //从sock里面一次读一个字符,总共读conten_len个字符 write(input[1],&ch,1); } } // char* msg="HTTP/1.0 200 OK\r\n\r\n"; // send(sock,msg,strlen(msg),0);// clear_header(sock); char* msg="HTTP/1.0 200 OK\r\n"; send(sock,msg,strlen(msg),0); //发送状态行 send(sock,"\r\n",strlen("\r\n"),0); //发送空行 //接收子进程的返回结果 while(read(output[0],&ch,1)) //如果CGI程序结束,写端关闭,则读端返回0 { send(sock,&ch,1,0);// printf("%c",ch); } waitpid(id,NULL,0); //回收子进程 } return 0;}int handler_msg(int sock) //浏览器请求处理函数{ char buf[SIZE]; int count=get_line(sock,buf); int ret=0; char method[32]; char url[SIZE]; char *query_string=NULL; int i=0; int j=0; int cgi=0; //获取请求方法和请求路径 while(j<count) { if(isspace(buf[j])) { break; } method[i]=buf[j]; i++; j++; } method[i]='\0'; while(isspace(buf[j])&&j<SIZE) //过滤空格 { j++; } //如果是POST方法一定要用到cgi //如果是GET方法带参数也要用到cgi if(strcasecmp(method,"POST")&&strcasecmp(method,"GET")) { printf_log("method failed",FATAL); echo_error(sock,405); ret=5; goto end; } if(strcasecmp(method,"POST")==0) { cgi=1; } i=0; while(j<count) { if(isspace(buf[j])) { break; } if(buf[j]=='?') { query_string=&url[i]; query_string++; url[i]='\0'; } else url[i]=buf[j]; j++; i++; } url[i]='\0'; if(strcasecmp(method,"GET")==0&&query_string!=NULL) { cgi=1; } //找到请求的资源路径 char path[SIZE]; sprintf(path,"wwwroot%s",url); struct stat st; if(stat(path,&st)<0) //获取客户端请求的资源的相关属性 { printf_log("stat path faile\n",FATAL); echo_error(sock,404); ret=6; goto end; } else { printf("S_ISDIR::%d\n",S_ISDIR(st.st_mode)); if(S_ISDIR(st.st_mode)) { strcat(path,"/index.html"); } else if(st.st_mode & (S_IXUSR | S_IXOTH | S_IXGRP)) { cgi=1; } } printf("cgi=%d\n",cgi); if(cgi) //请求的是可执行程序或者带参数用cgi程序处理 { ret=excu_cgi(sock,method,path,query_string); //处理CGI模式的请求 } else { clear_header(sock); ret=echo_www(sock,path,st.st_size); //如果是GET方法,而且没有参数,请求的也不是可执行程序,则直接返回资源 } end: close(sock); return ret;}
3. 项目效果展示
阅读全文
1 0
- http服务器小项目
- 项目二:HTTP服务器
- Node.js小Http服务器
- 【项目】简易http服务器流程图分析
- http项目笔记(多线程版 微型http服务器)
- 小项目心得体会.对HTTP协议格式更深的理解.
- 自己的小项目-WEB服务器IP统计
- Python小项目四:实现简单的web服务器
- http服务器
- HTTP 服务器:
- http服务器
- http服务器
- http服务器
- http服务器
- http服务器
- Http 服务器
- http服务器
- HTTP服务器/Web服务器
- Spring和hibernate整合时报错
- 3.1 NumPy介绍及导入
- IDUtils工具类
- 欧几里得算法
- 用java自带的工具类ResourceBundle类读取.properties配置文件的工具类
- http服务器小项目
- centos系统通过pm2启动nodejs项目
- Android测试一:Uiautomator——简介
- 上传图片模拟
- JavaWeb:在浏览器预览PDF的方法,超级简单
- 关于Activity的四种启动模式的总结
- 2017/7/17工作第一天 address:济南
- HTML以及CSS
- Material Design 系列(1)—初识