小型HTTP服务器

来源:互联网 发布:秒赞网哪一个源码好 编辑:程序博客网 时间:2024/05/11 16:57
#include "httpd.h"  void usage(const char *proc)  {      printf("Usage : %s [PORT]\n", proc);  }  static void not_found(int client)  {  }  void print_debug(const char * msg)  {  #ifdef _DEBUG_      printf("%s\n", msg);  #endif  }  static void bad_request(int client)  {      print_debug("enter our fault...\n");      char buf[1024];      sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");      send(client, buf, strlen(buf), 0);      sprintf(buf, "Content-type: text/html\r\n");      send(client, buf, strlen(buf), 0);      sprintf(buf, "\r\n");      send(client, buf, strlen(buf), 0);      sprintf(buf, "<html></br><p>your enter message is a bad request</p></br></html>\r\n");      send(client, buf, strlen(buf), 0);  }  void print_log(const char *fun, int line, int err_no,  const char *err_str)  {      printf("[%s: %d] [%d] [%s]\n", fun, line, err_no, err_str);  }  void clear_header(int client)  {      char buf[1024];      memset(buf, '\0', sizeof(buf));      int ret = 0;      do{          ret = get_line(client, buf, sizeof(buf));      }while(ret > 0 && strcmp(buf, "\n") != 0 );  }  //return num char, success  //return <= 0, failed  int get_line(int sock, char *buf, int max_len)  {      if( !buf || max_len < 0){          return -1;      }      int i = 0;      int n = 0;      char c = '\0';      while( i < max_len-1 && c != '\n' ){          n = recv(sock, &c, 1, 0);          if(n > 0){//success              if( c == '\r' ){                  n = recv(sock, &c, 1,MSG_PEEK);                  if( n > 0 && c == '\n' ){//windows                      recv(sock, &c, 1, 0);//delete                  }else{                      c = '\n';                  }              }              buf[i++] = c;          }else{//failed              c = '\n';          }      }      buf[i] = '\0';      return i;  }  void echo_error_to_client(int client, int error_code)  {      switch(error_code){          case 400://request error              bad_request(client);              break;          case 404://not found              not_found(client);              break;          case 500://server error      //      server_error(client);              break;          case 503://server unavailable  //          server_unavailable(client);              break;          //.....................          default:  //          default_error(client);              break;      }  }  void echo_html(int client, const char *path, unsigned int file_size)  {      if( !path ){          return;      }      int in_fd = open(path, O_RDONLY);//以只读的形式打开      if(in_fd < 0){          print_debug("open index.html error");          //echo_error_to_client();          return;      }      print_debug("open index.html success");      char echo_line[1024];      memset(echo_line, '\0', sizeof(echo_line));      strncpy(echo_line, HTTP_VERSION, strlen(HTTP_VERSION)+1);      strcat(echo_line, " 200 OK");      strcat(echo_line, "\r\n\r\n");      send(client, echo_line,strlen(echo_line), 0);      print_debug("send echo head success");      if( sendfile(client, in_fd, NULL, file_size) < 0 ){          print_debug("send_file error");          //echo_error_to_client();          close(in_fd);          return;      }      print_debug("sendfile success");      close(in_fd);  }  void exe_cgi(int sock_client, const char *path, const char *method,const char *query_string)  {      print_debug("enter cgi\n");      char buf[_COMM_SIZE_];      int numchars = 0;      int content_length = -1;      //pipe      int cgi_input[2] = {0, 0};      int cgi_output[2] = {0, 0};      //child proc      pid_t id;      print_debug(method);      if(strcasecmp(method, "GET") == 0){//GET          clear_header(sock_client);      }else{//POST          do{              memset(buf, '\0', sizeof(buf));              numchars = get_line(sock_client, buf, sizeof(buf));              if(strncasecmp(buf, "Content-Length:", strlen("Content-Length:")) == 0){                  //函数定义:int strncasecmp(const char *s1, const char *s2, size_t n)                  //函数说明:strncasecmp()用来比较参数s1和s2字符串前n个字符,比较时会自动忽略大小写的差异。                  content_length = atoi(&buf[16]);              }          }while(numchars > 0 && strcmp(buf, "\n") != 0);//一行一行的比较直到寻找content_length          if( content_length == -1 ){              //echo_error_to_client();              return;          }      }      memset(buf, '\0', sizeof(buf));      strcpy(buf, HTTP_VERSION);      strcat(buf, " 200 OK\r\n\r\n");      send(sock_client, buf, strlen(buf), 0);      if( pipe(cgi_input) == -1 ){//pipe error          //echo_error_to_client();          return;      }      if( pipe(cgi_output) == -1 ){          close(cgi_input[0]);          close(cgi_input[1]);          //echo_error_to_client();          return;      }      if( (id = fork()) < 0){//fork error          close(cgi_input[0]);          close(cgi_input[1]);          close(cgi_output[0]);          close(cgi_output[1]);          //echo_error_to_client();          return;      }else if( id == 0 ){//child          char query_env[_COMM_SIZE_/10];          char method_env[_COMM_SIZE_];          char content_len_env[_COMM_SIZE_];          memset(method_env, '\0', sizeof(method_env));          memset(query_env, '\0', sizeof(query_env));          memset(content_len_env, '\0', sizeof(content_len_env));          close(cgi_input[1]);          close(cgi_output[0]);          //redir          dup2(cgi_input[0], 0);          dup2(cgi_output[1], 1);          sprintf(method_env, "REQUEST_METHOD=%s", method);          putenv(method_env);          if(strcasecmp("GET", method) == 0){//POST              sprintf(query_env, "QUERY_STRING=%s", query_string);              putenv(query_env);          }else{//POST              sprintf(content_len_env, "CONTENT_LENGTH=%d", content_length);              putenv(content_len_env);          }          execl(path, path, NULL);//exec函数族          exit(1);      }else{//father          close(cgi_input[0]);          close(cgi_output[1]);          int i = 0;          char c = '\0';          if(strcasecmp("POST", method) == 0){              for(; i < content_length; i++ ){                  recv(sock_client, &c, 1, 0);                  write(cgi_input[1], &c, 1);              }          }          while( read(cgi_output[0], &c, 1) > 0 ){              send(sock_client, &c, 1, 0);          }          close(cgi_input[1]);          close(cgi_output[0]);          waitpid(id, NULL, 0);      }  }  //GET && POST  void *accept_request(void *arg)//这个arg实际上是由socket强转过来的那个参数  {      print_debug("get a new connect...\n");      pthread_detach(pthread_self());//detach //let the thread free automaticaly      int sock_client = (int)arg;//将客户端套接字恢复回来      //for test      //echo_error_to_client(sock_client, 400);      //close(sock_client);      //return;      int  cgi = 0;      char *query_string = NULL;      char method[_COMM_SIZE_/10];      char url[_COMM_SIZE_];      char buffer[_COMM_SIZE_];      char path[_COMM_SIZE_];      memset(method, '\0', sizeof(method));      memset(url, '\0', sizeof(url));      memset(buffer, '\0', sizeof(buffer));      memset(path, '\0', sizeof(path));  //#ifdef _DEBUG_  //  //success  > 0  //  //else <= 0  //  while(get_line(sock_client, buffer, sizeof(buffer)) > 0){  //      printf("%s", buffer);  //      fflush(stdout);  //  }  //  printf("\n");  //#endif      if(get_line(sock_client, buffer, sizeof(buffer)) < 0){//从客户端一行一行读取数据          //echo_error_to_client();          return NULL;      }      int i = 0;       int j = 0;//buffer line index      while( !isspace(buffer[j]) &&\//从消息中获取方法保存到method              i < sizeof(method)-1 &&\              j < sizeof(buffer)){          method[i] = buffer[j];          i++, j++;      }      if( strcasecmp(method, "GET") && strcasecmp(method, "POST")){          //echo_error_to_client();          return NULL;      }      //clear space point useful url start      while( isspace(buffer[j]) &&\//如果发现是空行就跳到下一行,指向url开始的地方              j < sizeof(buffer)){          j++;      }      //get url      i = 0;      while(!isspace(buffer[j]) &&\//获取url              i < sizeof(url)-1 &&\              j < sizeof(buffer)){          url[i] = buffer[j];          i++;j++;      }      print_debug(method);//打印方法和路径      print_debug(url);      if(strcasecmp(method, "POST") == 0){//如果是POST方法,就让cgi=1,是不是POST方法就直接可用呢          cgi = 1;      }      if(strcasecmp(method, "GET") == 0){//那我们就可以稍微处理一下啦          query_string = url;          while( *query_string != '?' && *query_string != '\0'){              query_string++;          }          if( *query_string == '?' ){//url = /XXX/XXX + arg              *query_string = '\0';              query_string++;              cgi = 1;          }      }      sprintf(path, "htdocs%s", url);//将url所在文件包装成路径保存在path中      if(path[strlen(path)-1] == '/'){          strcat(path, MAIN_PAGE);//最后在附上主页就好了      }      print_debug(path);      struct stat st;      if( stat(path, &st) < 0 ){ //failed, does not exist  // stat()用来将参数path 所指的文件状态, 复制到参数st所指的结构中          print_debug("miss cgi");          clear_header(sock_client);          //echo_error_to_client();      }else{//file exist!          if(S_ISDIR(st.st_mode)){//如果是个目录文件追加/              strcat(path, "/");              strcat(path, MAIN_PAGE);          }else if(st.st_mode & S_IXUSR ||\                   st.st_mode & S_IXGRP ||\                   st.st_mode & S_IXOTH){   //文件S_IRUSR (S_IREAD) 00400 文件所有者具可读取权限                                           //S_IWUSR (S_IWRITE)00200 文件所有者具可写入权限                                          //S_IXUSR (S_IEXEC) 00100 文件所有者具可执行权限              cgi = 1;          }else{              //do nothing          }          if(cgi){              exe_cgi(sock_client, path, method, query_string);          }else{              clear_header(sock_client);              print_debug("begin enter our echo_html");              echo_html(sock_client, path, st.st_size);          }      }      close(sock_client);      return NULL;  }  //if success return sock  //else exit process  int start(short port)  {      int listen_sock = socket(AF_INET, SOCK_STREAM, 0);      if(listen_sock == -1){          print_log(__FUNCTION__, __LINE__, errno, strerror(errno));          exit(1);      }      //reuse port//定义一个地址空间      int flag = 1;      setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));      struct sockaddr_in local;      local.sin_family = AF_INET;      local.sin_port   = htons(port);//host -> net      local.sin_addr.s_addr   = htonl(INADDR_ANY);      socklen_t len = sizeof(local);  //将地址空间和定义的套接字绑定在一起      if(bind(listen_sock, (struct sockaddr*)&local, len) == -1){          print_log(__FUNCTION__, __LINE__, errno, strerror(errno));          exit(2);      }  //不断监听处理套接字的请求      if(listen(listen_sock, _BACK_LOG_) == -1){          print_log(__FUNCTION__, __LINE__, errno, strerror(errno));          exit(3);      }  //监听成功后返回监听套接字      return listen_sock; //sucess  }  //./httpd port  int main(int argc, char *argv[])  {      if(argc != 2){          usage(argv[0]);          exit(1);      }      //daemon();      int port = atoi(argv[1]);      int sock = start(port);//listen socket      struct sockaddr_in client;      socklen_t len = 0;      while(1){          int new_sock = accept(sock, (struct sockaddr*)&client, &len);//这个返回的新的套接字称为链接套接字,不同客户端的请求返回的                                                                  //新socket自然不一样          if( new_sock < 0 ){//accept error              print_log(__FUNCTION__, __LINE__, errno, strerror(errno));              continue;          }          pthread_t new_thread;//一旦有请求就定义一个新的线程去解决问题          pthread_create(&new_thread, NULL, accept_request, (void*)new_sock);//转成void*后变成参数传给accept_request      }      return 0;  }