乐于分享
好东西不私藏

从0实现HTTP服务器(5)-核心源码

从0实现HTTP服务器(5)-核心源码

/** * @bref:通道创建子进程,执行CGI程序,提供特定服务 * @client:与客户端建立的socket 套件子 * @path:请求的cgi程序的路径 * @method:HTTP请求的方法 * @query_string:HTTP请求中携带的数据 *  */    void executeCgi(int client,const char *path,const char *method, const char *query_string){    char buf[1024];    const char *field_key = "Content-Length:";    int content_length = 0;    //处理HTTP请求头部    //如果是GET方法,直接丢弃头部,query_string中携带了请求数据    //如果是POST方法,需要从请求头部中解析出content-length的值,读取请求体获取请求数据if(strcasecmp(method,"GET") == 0)    {        int numchars = 0;        //读取并处理头部(直接丢弃)do{            memset(buf,0,sizeof buf);            numchars = getLine(client,buf,sizeof buf);        }while(numchars > 0 && strcmp(buf,"\n"));     }elseif(strcasecmp(method,"POST") == 0)    {        int numchars = 0;do{            memset(buf,0,sizeof buf);            numchars = getLine(client,buf,sizeof buf);if(strncmp(buf,field_key,strlen(field_key)) == 0)            {                content_length = atoi(&buf[strlen(field_key)]);            }        }while(numchars > 0 && strcmp(buf,"\n"));if(content_length <= 0)        {            badRequest(client);return;        }    }else//其他的方法    {    }    //通过管道实现父子进程间文件数据的传输    int cgi_output[2];if(pipe(cgi_output) < 0)    {        cannotExecute(client); return;    }    int cgi_input[2];if(pipe(cgi_input) < 0)    {        cannotExecute(client);return;    }    pid_t pid = -1;if((pid = fork()) < 0)    {        cannotExecute(client);return;    }    memset(buf,0,sizeof buf);    char meth_env[64];    char query_env[64];    char length_env[64];if(pid == 0)//子进程,返回的pid = 0    {       //子进程关闭标准输出文件描述符,将管道写端重定向到标准输出文件描述,即printf会默认打印到管道写端       dup2(cgi_output[1],STDOUT_FILENO);       //关闭读端        close(cgi_output[0]);       //子进程关闭标准输入文件描述符,将管道读端重定向到标准输入文件描述,即scanf会默认读管道读端       dup2(cgi_input[0],STDIN_FILENO);       //关闭写端       close(cgi_input[1]);       //通过环境变量,跨进程传递HTTP 请求携带的请求数据(不包括请求体的数据)       //请求体通过管道发送请求体数据       memset(meth_env,0,sizeof meth_env);       sprintf(meth_env,"REQUEST_METHOD=%s",method);       putenv(meth_env);if(strcasecmp(method,"GET") == 0)       {            //get方法会通过url携带请求数据,所以cgi程序不用读取请求体           memset(query_env,0,sizeof(query_env));           sprintf(query_env,"QUERY_STRING=%s",query_string);           putenv(query_env);       }else       {            //post方法,请求数据通过请求体携带请求数据,需要cgi子进程通过管道读取请求体获取请求数据            //content-length包含了请求体数据的长度           memset(length_env,0, sizeof length_env);           sprintf(length_env,"CONTENT_LENGTH=%d",content_length);           putenv(length_env);       }       execl(path,path,(char*)NULL);exit(0);    }else    {        //关闭不需要的管道端口        close(cgi_output[1]);        close(cgi_input[0]);        //如果是POST方法,读取请求体,通过管道将请求体发送给cgi子进程        char c = '\0';if(strcasecmp(method,"POST") == 0)        {for(int i = 0; i < content_length; i++)            {               recv(client,&c,1,0);               write(cgi_input[1],&c,1);            }         }        //读取cgi子进程发送的数据,转发给客户端while(read(cgi_output[0],&c,1))        {            send(client,&c,1,0);            //printf("%c",c);        }        close(cgi_output[0]);        close(cgi_input[1]);        int status;        waitpid(pid,&status,0);    }}void *  acceptRequest(void *arg){    int client = (uintptr_t)arg;    char buf[1024];    memset(buf,0,sizeof(buf));    //读取请求行    int numchars = getLine(client,buf,sizeof(buf));if(numchars == 0)    {        close(client);return 0;    }    //解析请求行中的请求方法    char method[8];    memset(method,0,sizeof(method));    int i = 0;     int offset = 0;    //isspace 除了判断空格,也会判断换行符while(!ISspace(buf[i]) && (i < sizeof(method) - 1))    {        method[i++] = buf[offset++];    }     method[i] = '\0';    //仅处理GET和POST方法的请求if(strcasecmp(method,"GET") && strcasecmp(method,"POST"))    {        unimplemented(client);        close(client);return 0;    }    //变量cgi是是否需要调用CGI程序的flag    int cgi = 0;if(strcasecmp(method,"POST") == 0)    {       cgi = 1;     }    //[POST|GET][空格][url][空格][协议]    //跳过空格while(offset < numchars && ISspace(buf[offset]))    {        ++offset;    }    //解析请求行中url    char url[255];    memset(url,0,sizeof(url));    i = 0; while((i < sizeof(url) - 1) && !ISspace(buf[offset]))    {        url[i++] = buf[offset++];    }    url[i] = '\0';    //判断是否通过GET方法通过明文方式发送了携带数据的请求    char * query_string = url; if(strcasecmp(method,"GET") == 0)    {while(*query_string != '?'&& *query_string != '\0')        {            query_string++;        }if(*query_string == '?')        {            *query_string = '\0';            cgi = 1;            query_string++;        }    }    //解析请求的url    char path[255];    memset(path,0,sizeof(path));    sprintf(path,"./htdocs%s",url);    //如果请求的url是‘/’跟目录,默认返回index_jpg界面;if(path[strlen(path) - 1] == '/')    {        strcat(path,"index_jpg.html");    }printf("%s\n",path);    struct stat fst;    memset(&fst,0,sizeof fst);    //获取url的属性信息if(stat(path,&fst) == -1)    {        //读取并丢弃头部while((numchars > 0) && strcmp("\n",buf))        {           memset(buf,0,sizeof(buf));           numchars = getLine(client,buf,sizeof(buf));         }        notFound(client);    }else    {        //如果是目录,默认返回其目录下的index.htmlif((fst.st_mode & S_IFMT) == S_IFDIR)        {            strcat(path,"/index.html");        }        //如果请求的url是一个可执行文件,则假定是cgi程序文件。if((fst.st_mode & S_IXUSR) ||           (fst.st_mode & S_IXGRP) ||           (fst.st_mode & S_IXOTH))        {            cgi = 1;        }if(!cgi)        {            //不用调用cgi程序,直接返回服务器上文件内容。            serveFile(client,path);        }else        {            //通过cgi程序,提供特使功能的服务            executeCgi(client,path,method,query_string);        }    }    close(client);return 0;}int startup(uint16_t* port){    //sys/socket.h:地址簇,协议    //创建基于ipv4,tcp的socket    int server_socket_fd = socket(AF_INET,SOCK_STREAM,0);if(server_socket_fd < 0)    {        //创建套接字出错return -1;    }    int on = 1;    //端口重用,    //地址重用,当服务器以为宕机后,避免端口暂时不可用,可以快速重启服务器。    int ret = setsockopt(server_socket_fd,                      SOL_SOCKET,                    SO_REUSEADDR,                     &on,                     sizeof on);if(ret < 0)    {        //设置套接字属性出错return -2;    }    //netinet/in.h:    struct sockaddr_in server_addr;    server_addr.sin_family = AF_INET;    server_addr.sin_port = htons(*port);    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);    //socket绑定ip+端口    ret = bind(server_socket_fd,             (struct sockaddr *)&server_addr,              sizeof(server_addr));if(ret < 0)    {        //绑定地址出错return -3;    }    //服务端应该不这么做if(*port == 0)    {        socklen_t server_addr_len = sizeof(server_addr);        ret = getsockname(server_socket_fd,                        (struct sockaddr*)&server_addr,                         &server_addr_len);if(ret < 0)        {            //获取自动分配端口错误return -4;        }        *port = ntohs(server_addr.sin_port);    }    //开始监听端口    //三次握手之前的连接缓冲队列    ret = listen(server_socket_fd,5);if(ret < 0)    {return -5;    }return server_socket_fd;}int main(int argc,char** argv){    //stdint.h/cstdint    //设置服务器监听端口    uint16_t server_listen_port = 5001;    //创建tcp服务器,监听client连接    int server_socket_fd = startup(&server_listen_port);if(server_socket_fd < 0)    {       perror("启动服务器失败!"); return 1;    }while(1)    {        struct sockaddr_in client_addr;        socklen_t client_addr_len = sizeof client_addr;        //阻塞等待客户端链接        int client_socket_fd = accept(server_socket_fd,                                    (struct sockaddr*)&client_addr,                                &client_addr_len);if(client_socket_fd < 0)        {           perror("建立连接失败!");           close(server_socket_fd);return 1;        }        pthread_t work_thread_id;        //创建新的线程,服务客户端        int ret = pthread_create(&work_thread_id,                                      NULL,                             acceptRequest,                                       (void *)(uintptr_t)client_socket_fd);if(ret != 0)        {            perror("创建工作线程错误");return 1;        }    }    close(server_socket_fd);return 0;}
本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 从0实现HTTP服务器(5)-核心源码

评论 抢沙发

4 + 7 =
  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
×
订阅图标按钮