从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;}
夜雨聆风
