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//请求正文
  1. 起始行以一个方法符号开头,以空格分开,后面跟着请求的URI和协议的版本,格式如下:
Method Request-URI HTTP-Version CRLF其中 Method表示请求方法;Request-URI是一个统一资源标识符;HTTP-Version表示请求的HTTP协议版本;CRLF表示回车和换行(除了作为结尾的CRLF外,不允许出现单独的CRLF字符)。
  1. 请求方法(所有方法全为大写)有多种,各个方法的解释如下:

    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整体流程

  1. 服务器启动,等待客户端请求到来;

  2. 客户端请求到来,创建新线程处理该请求;

  3. 读取httpHeader中的method,截取url,其中GET方法需要记录url问号之后的参数串;

  4. 根据url构造完整路径,如果是/结尾,则指定为该目录下的index.html;

  5. 获取文件信息,如果找不到文件,返回404,找到文件则判断文件权限;

  6. 如果是GET请求并且没有参数,或者文件不可执行,则直接将文件内容构造http信息返回给客户端;

  7. 如果是GET带参数,POST,文件可执行,则执行CGI;

  8. GET请求略过httpHeader,POST方法需要记录httpHeader中的Content-Length:xx;

  9. 创建管道用于父子进程通信,fork产生子进程;

  10. 子进程设置环境变量,将标准输入和输出与管道相连,并且通过exec执行CGI;

  11. 如果是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. 项目效果展示

这里写图片描述