自主web服务器

描述
采用C/S模型,编写支持中小型应用的http,并结合mysql,理解常见互联网应用行为,做完该项目,你可以从技术上完全理解从上网开始,到关闭浏览器的所有操作中的技术细节。

www

WWW是环球信息网的缩写,(亦作“Web”、“WWW”、“‘W3’”,英文全称为“World Wide Web”),中文名字为“万维网”,"环球网"等,常简称为Web。
分为Web客户端和Web服务器程序。 WWW可以让Web客户端(常用浏览器)访问浏览Web服务器上的页面。 是一个由许多互相链接的超文本组成的系统,通过互联网访问。在这个系统中,每个有用的事物,称为一样“资源”;并且由一个全局“统一资源标识符”(URI)标识;这些资源通过超文本传输协议(Hypertext Transfer Protocol)传送给用户,而后者通过点击链接来获得资源。

万维网联盟(英语:World Wide Web Consortium,简称W3C),又称W3C理事会。1994年10月在麻省理工学院(MIT)计算机科学实验室成立。万维网联盟的创建者是万维网的发明者蒂姆·伯纳斯-李。

http分层概念

在这里插入图片描述

特点

客户/服务器模式(B/S,C/S)

  • 简单快速,HTTP服务器的程序规模小,因而通信速度很快。
  • 灵活,HTTP允许传输任意类型的数据对象,正在传输的类型由Content-Type加以标记。
  • 无连接,每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。(http/1.0具有的功能,http/1.1兼容)
  • 无状态

http协议每当有新的请求产生,就会有对应的新响应产生。协议本身并不会保留你之前的一切请求或者响应,这是为了更快的处理大量的事务,确保协议的可伸缩性。

可是,随着web的发展,因为无状态而导致业务处理变的棘手起来。比如保持用户的登陆状态。http/1.1虽然也是无状态的协议,但是为了保持状态的功能,引入了cookie技术。

URI & URL & URN

  • URI,是uniform resource identifier,统一资源标识符,用来唯一的标识一个资源
  • URL,是uniform resource locator,统一资源定位符,它是一种具体的URI,即URL可以用来标识一个资源,而且还指明了如何locate这个资源。
  • URN,uniform resource name,统一资源命名,是通过名字来标识资源

URI是以一种抽象的,高层次概念定义统一资源标识,而URL和URN则是具体的资源标识的方式。URL和URN都是一种URI。

URL是 URI 的子集。任何东西,只要能够唯一地标识出来,都可以说这个标识是 URI 。如果这个标识是一个可获取到上述对象的路径,那么同时它也可以是一个 URL ;但如果这个标识不提供获取到对象的路径,那么它就必然不是URL 。
URI: /hello/index.html
URL: www.xxx.com:/hello/index.html

浏览器URL格式

  • HTTP(超文本传输协议)是基于TCP的连接方式进行网络连
  • HTTP/1.1版本中给出一种持续连接的机制(长链接)
  • 绝大多数的Web开发,都是构建在HTTP协议之上的Web应用

HTTP URL (URL是一种特殊类型的URI,包含了如何获取指定资源)的格式如下:

http://host[":"port][abs_path]
  • http表示要通过HTTP协议来定位网络资源
  • host表示合法的Internet主机域名或者IP地址,本主机IP:127.0.0.1
  • port指定一个端口号,为空则使用缺省端口80
  • abs_path指定请求资源的URI
  • 如果URL中没有给出abs_path,那么当它作为请求URI时,必须以“/”的形式给出,通常浏览器自动完成。

如果用户的url没有指明要访问的某种资源(路径),虽然浏览器会自动添加/ ,但是浏览器还是不知道需要访问什么资源,此时,浏览器默认返回对应服务的首页

HTTP请求与响应

在这里插入图片描述
在这里插入图片描述

利用套接字获取请求报文

Makefile:

bin=httpserver
cc=g++
LD_FLAGS=-std=c++11 -lpthread
src=main.cc 
$(bin):$(src)
		$(cc) -o $@ $^ $(LD_FLAGS)
.PHONY:clean
clean:
		rm -f  $(bin)

TcpServer.hpp

#pragma once 
#include <iostream>
#include <sys/socket.h>
#include <cstdlib>
#include <cstring>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
#define Backlog 5
class TcpServer
{
  private:
    int port;
    int listen_sock;
    static TcpServer *svr;
  private://把tcpserver设置为单例模式
    TcpServer(int _port):port(_port),listen_sock(-1)
    {}
    TcpServer(const TcpServer &s){}
  public:
    static TcpServer *getinstance(int port)//获取单例
    {
      static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

      if(svr == nullptr)
      {
        pthread_mutex_lock(&lock);
        if(svr == nullptr)
        {
          svr = new TcpServer(port);
          svr->InitServer();
        }
        pthread_mutex_unlock(&lock);
      }
      return svr;
    }

    void InitServer()
    {
      Socket();
      Bind();
      Listen();


    }
    void Socket()
    {
      listen_sock = socket(AF_INET,SOCK_STREAM,0);
      if(listen_sock < 0)
      {
        exit(1);
      }
      //socket 地址复用:服务器崩了,连接还在,此时服务器重启不了,因为监听套接字对应的端口号
      //默认是绑定的,端口没有被完全释放,导致服务器无法立即重启,所以需要地址复用
      int opt = 1;
      setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
    }
    void Bind()
    {
      
      struct sockaddr_in local;
      memset(&local,0,sizeof(local));
      local.sin_family = AF_INET;
      local.sin_port = htons(port);
      local.sin_addr.s_addr = INADDR_ANY;//云服务器不能直接绑定公网ip
      
      if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local)) <0)
      {
        std::cout<<"bind error"<<std::endl;
        exit(2);

      }
     
    }
    void Listen()
    {
      if(listen(listen_sock,Backlog) < 0)
      {
        exit(3);
      }

    }
    int Sock()
    {
      return listen_sock;
    }
    ~TcpServer()
    {
      if(listen_sock >= 0)
	     close(listen_sock);
    }
};

HttpServer.hpp

#pragma once 
#include "TcpServer.hpp"
#include "Protocol.hpp"
#include <iostream>
#include <pthread.h>
#define PORT 8081
class HttpServer
{
  private:
    int port;
    TcpServer *tcp_server;
    bool stop;
  public:
    HttpServer(int _port = PORT):port(_port),tcp_server(nullptr),stop(false)
    {}
    void InitServer()
    {
      tcp_server = TcpServer::getinstance(port);
    }
    void Loop()
    {
      int listen_sock = tcp_server->Sock();
      //std::cout<<"HttpServer::Loop sock "<<listen_sock<<std::endl;
      while(!stop)
      {
        
          struct sockaddr_in peer;
          socklen_t len = sizeof(peer);
          int sock = accept(listen_sock,(struct sockaddr*)&peer,&len);
          if(sock < 0)
          {
            continue;
          }

          int  *_sock = new int(sock);
          pthread_t tid;
          pthread_create(&tid,nullptr,Entrance::HandlerRequest,_sock);
          pthread_detach(tid);


      }
    }
    
    ~HttpServer()
    {}

};

Protocol.hpp


#pragma once 
#include <iostream>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
class Entrance
{
  public:
    static void *HandlerRequest(void *_sock)//处理线程
    {
      int sock = *(int *)_sock;
      delete (int*)_sock;
      std::cout<<"get a new link..."<<sock<<std::endl;
#ifndef DEGUB 
#define DEBUG
      char buffer[4096];
      recv(sock,buffer,sizeof(buffer),0);
      std::cout<<"-----------begin-----------------"<<std::endl;
      std::cout<<buffer << std::endl;
      std::cout<<"-----------end-----------------"<<std::endl;
#endif 
      close(sock);
      return nullptr;
    }


};

main.cc

#include <iostream>
#include <string>
#include <memory>
#include "HttpServer.hpp"

//#include "TcpServer.hpp"
static void Usage(std::string proc)
{
  std::cout<<"Usage: "<<proc << " port "<<std::endl;
}
int main(int argc,char *argv[])
{
  if(argc != 2)
  {
    Usage(argv[0]);
    exit(4);
  }
  //std::cout<<"hello http"<<std::endl;
  int port = atoi(argv[1]);
  std::shared_ptr<HttpServer> http_server(new HttpServer(port));
  http_server->InitServer();
  http_server->Loop();
  for(;;)
  {

  }
  return 0;
}

在这里插入图片描述

CGI

CGI(Common Gateway Interface) 是WWW技术中最重要的技术之一,有着不可替代的重要地位。CGI是外部应用程序(CGI程序)与WEB服务器之间的接口标准,是在CGI程序和Web服务器之间传递信息的过程。

首先需要理解GET方法和POST方法的区别

  • GET方法从浏览器传参数给http服务器时,是需要将参数跟到URI后面的
  • POST方法从浏览器传参数给http服务器时,是需要将参数放的请求正文的。
  • GET方法,如果没有传参,http按照一般的方式进行,返回资源即可
  • GET方法,如果有参数传入,http就需要按照CGI方式处理参数,并将执行结果(期望资源)返回给浏览器
  • POST方法,一般都需要使用CGI方式来进行处理

在这里插入图片描述

如何看待子CGI程序?
子CGI的标准输出是浏览器
子CGI的标准输入是浏览器

具体实现

在这里插入图片描述

Protocol.hpp

#pragma once 
#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string>
#include <sstream>
#include <unordered_map>
#include <sys/stat.h>
#include <fcntl.h>
#include "Util.hpp"
#include "Log.hpp"
#include <algorithm>
#include <sys/wait.h>
#include <sys/sendfile.h>
#define SEP ": "
#define OK 200
#define NOT_FOUND 404
#define BAD_REQUEST 400
#define WEB_ROOT "wwwroot"
#define HOME_PAGE "index.html"
#define HTTP_VERSION "HTTP/1.0"
#define LINE_END "\r\n"
#define PAGE_404 "404.html"
#define SERVER_ERROR 500
/*
class Code2Desc
{
  private:
    std::unordered_map<int,std::string> Code;
  public:
    Code2Desc()
    {}
    void InitCode2Desc()
    {
      Code.insert({200,"OK"});
    }
    ~Code2Desc()
    {}
};
*/

static std::string Code2Desc(int code)
{
  std::string desc;
  switch(code)
  {
    case 200:
      desc="OK";break;
    case 404:
      desc = "Not Found";break;
    default:
      break;
  }
  return desc;
}
static std::string Suffix2Desc(const std::string &suffix)
{
  static std::unordered_map<std::string,std::string> Suffix2desc = {
    {".html","text/html"},
    {".css","text/css"},
    {".js","appplication/javascript"},
    {".jpg","application/x-jpg"},
  };
  auto iter =  Suffix2desc.find(suffix);
  if(iter != Suffix2desc.end())
  {
    return iter->second;
  }
  return ".text/html";
}
class HttpRequest 
{
  public:
    std::string request_line;//请求行
    std::vector<std::string> request_header;//请求报头
    std::string blank;//空行
    std::string request_body;//正文

    //解析完毕之后的结果
    std::string method;//请求方法 GET:有正文 POST:需要读取正文
    std::string uri;//请求资源 path?args
    std::string version;//请求版本

    std::unordered_map<std::string,std::string> header_kv;//HOST: XXX.XXX Connection: XXX
    int content_length;//正文长度
    std::string path;//想访问的资源
    std::string query_string;//path后面的参数
    std::string suffix;//请求资源的后缀
    bool cgi;
    int size;
  public:
    HttpRequest():content_length(0),cgi(false)
    {}
    ~HttpRequest()
    {}
};
class HttpResponse
{
  public:
    std::string status_line;//状态行
    std::vector<std::string> response_header;//响应报头
    std::string blank;//空行
    std::string response_body;//正文

    int status_code;//状态码
    int fd;
    int size;//想访问的资源的大小
  public:
    HttpResponse():blank(LINE_END),status_code(OK),fd(-1){}
    ~HttpResponse(){}
};

//读取请求,分析请求,构建响应
//IO通信
class Endpoit
{
  //对端:完成业务逻辑
  private:
    int sock;
    HttpRequest http_request;
    HttpResponse http_response;
  private:
    void RecvHttpRequestLine()
    {
      auto &line = http_request.request_line;
      Util::Readline(sock,line);
      line.resize(line.size()-1);
      LOG(INFO,http_request.request_line);
    }
    void RecvHttpRequestHeader()
    {
      //如何保证请求报头已经读取完毕
      //1、空格结束 2、都是按照行进行陈列的
      std::string line;
      while(true)
      {
        line.clear();
        Util::Readline(sock,line);
        if(line == "\n")
        {
        http_request.blank = line;
          break;
        }
        line.resize(line.size()-1);//去掉\n
        http_request.request_header.push_back(line);
        LOG(INFO,line);
      }    
    }
    void ParseHttprequestLine()
    {
      auto &line = http_request.request_line;
      // 方法 URI HTTP/版本
      //stringstream :默认按照空格把字符串打散切分
      std::stringstream ss(line);
      ss >> http_request.method >> http_request.uri >> http_request.version;
      auto &method = http_request.method;
      std::transform(method.begin(),method.end(),method.begin(),::toupper);
      LOG(INFO,line);

    }
    void ParseHttprequestHeader()
    {
      std::string key;
      std::string value;
      for(auto &iter : http_request.request_header)
      {
        
        if(Util::CutString(iter,key,value,SEP))
        {
          http_request.header_kv.insert({key,value});
        }
      }

    }
    bool IsNeedRecvHttpRequestBody()
    {
      auto method = http_request.method;
      if(method == "POST")
      {
        auto &header_kv = http_request.header_kv;
        auto iter =  header_kv.find("Content-Length");
        if(iter != header_kv.end())
        {
          //找到了
          LOG(INFO,"Post Method,Content-Length"+iter->second);
          http_request.content_length = atoi(iter->second.c_str());
          return true;
        }
      }
      return false;
    }
    void RecvHttpRequestBody()
    {
      if(IsNeedRecvHttpRequestBody())
      {
        int content_length = http_request.content_length;
        auto &body = http_request.request_body;
        char ch = 0;
        while(content_length)
        {
          ssize_t s = recv(sock,&ch,1,0);
          if(s > 0)
          {
            body.push_back(ch);
            content_length--;
          }
          else 
          {
            break;
          }
          
        }
        //LOG(INFO,body);
      }
    }
    int ProcessNonCGI()
    {
      //eg:  HTTP/1.0 200 OK
      http_response.fd = open(http_request.path.c_str(),O_RDONLY);
      if(http_response.fd >= 0)
      {
        LOG(INFO,http_request.path+"open success!");
        /*
        //获取信息成功
        http_response.status_line = HTTP_VERSION;
        http_response.status_line += " ";
        http_response.status_line +=std::to_string(http_response.status_code);
        http_response.status_line += " ";
        http_response.status_line += Code2Desc(http_response.status_code);
        http_response.status_line += LINE_END;
        http_response.size = size;

        std::string header_line = "Content-Type: ";
        header_line += Suffix2Desc(http_request.suffix);
        header_line += LINE_END;
        http_response.response_header.push_back(header_line);
        header_line = "Content-Length: ";
        header_line += std::to_string(size);
        header_line += LINE_END;    
        http_response.response_header.push_back(header_line);
*/
        return OK;
      }


      //http_response.response_body是用户层的缓冲区 :磁盘-》内核层-》用户层
      //sendfile:把数据从一个文件描述符拷贝给另一给文件描述符,只在内核进行
      return NOT_FOUND;
    }
    int ProcessCGI()
    {
      LOG(INFO,"process cgi");
      int code = OK;
      auto &method = http_request.method;
      auto &query_string = http_request.query_string;//GET
      auto &body_text = http_request.request_body;//POST
      int content_length = http_request.content_length;

      auto &response_body = http_response.response_body;
      //新线程,但是从头到尾只有一个进程:httpserver
      //如何用一个进程去执行另一个进程-》程序替换:exec*
      //不能直接替换(替换的是httpserver)-》可以子线程实现
      auto &bin = http_request.path;//要让子进程执行的目标程序
      //站在父进程角度
      int input[2];//创建管道:父子间进程通信
      int output[2];
      std::string query_string_env;//环境变量
      std::string method_env;//方法的环境变量
      std::string content_length_env;


      if(pipe(input) < 0)
      {
        //error
        LOG(ERROR,"input errpr");
        code = SERVER_ERROR;
        return code;
      }
      if(pipe(output) < 0)
      {
        //error
        LOG(ERROR,"output error");
        code = SERVER_ERROR;
        return code;
      }
      pid_t pid = fork();
      if(pid == 0)//child
      {
        //exec*
        close(input[0]);
        close(output[1]);
        method_env = "METHOD=";
        method_env += method;

        std::cout<<"cgi: "<<method_env<<std::endl;

        putenv((char*)method_env.c_str());

        if(method == "GET")
        {
          //父进程    -》数据-》    子进程
          //通过:  管道    环境变量   
          //GET需要导环境变量
          //环境变量是具有全局属性的(可以被子进程继承下去,不受exec*的影响)
          query_string_env = "QUERY_STRING=";
          query_string_env += query_string;
          putenv((char*)query_string_env.c_str());
          //子进程如何区分是从标准输入中读取还是从环境变量里面拿到数据?通过知道请求方法
        
          LOG(INFO,"Get Method,Add Query_string Env");
        }

        else if(method == "POST")
        {
          content_length_env = "CONTENT_LENGTH=";
          content_length_env += std::to_string(content_length);
          putenv((char*)content_length_env.c_str());
          LOG(INFO,"Post Method,Add Content-Length Env");
        }
        else 
        {
          //do nothing
        }
        
/*
        //替换成功之后,目标子进程如何得知对应的读写文件描述符是多少呢?
        //在层序替换之后,数据没有了单数曾经打开文件pipe还在(程序替换只替换代码和数据,并不替换内核进程相关的数据结构包括文件描述符表)
        //-》让目标被替换之后的进程,读取管道等价于读取标准输入,写入管道等于写到标准输出
        //->重定向技术,可以在exec*系列函数被执行前进行重定向 
*/      
        //站在子进程角度:
        //inout[1]:write output[0]:read 
        std::cout<<"bin "<<bin<<std::endl;
        dup2(output[0],0);
        dup2(input[1],1);
        
        
        execl(bin.c_str(),bin.c_str(),nullptr);
        exit(1);
      }
      else if(pid < 0)
      {
        //创建子进程失败
        LOG(ERROR,"fork error");
        return 404;
      }
      else 
      {
        //father
        close(input[1]);//0:read 1:write
        close(output[0]);
        if(method == "POST")
        {
          const char* start = body_text.c_str();
          int total = 0;
          int size = 0;
          while((total< content_length) &&(size = write(output[1],total+start,body_text.size()-total)) > 0)
          {
              total += size;
            
          }
        }

        //让父进程获得子进程的输出结果
        char c;
        while(read(input[0],&c,1) > 0)
        {
          //CGI执行完之后的结果,并不可以直接返回给浏览器,因为这部分内容只是响应的正文
          response_body.push_back(c);
        }
        int status = 0;
        pid_t ret = waitpid(pid,&status,0);//等待子进程
        if(ret == pid)
        {
          //wait success
          if(WIFEXITED(status))//检验进程退出是否正常
          {
            if(WEXITSTATUS(status) == 0)//获得退出码
            {
              code = OK;
            }
            else 
            {
              code = BAD_REQUEST;
            }
          }
          else 
          {
            code = SERVER_ERROR;
          }
        }
      
        close(input[1]);//0:read 1:write
        close(output[0]);
      }

      return code;
    }
    void HandlerERROR(std::string page)
    {
      http_request.cgi = false;
      http_response.fd = open(page.c_str(),O_RDONLY);
      if(http_response.fd > 0)
      {
        struct stat st;
        stat(page.c_str(),&st);
        http_request.size = st.st_size;
        std::string line = "Content-Type: text/html";
        line += LINE_END;
        http_response.response_header.push_back(line);
        line = "Content-Length: ";
        line += std::to_string(st.st_size);
        line += LINE_END;
        http_response.response_header.push_back(line);
        
      }
    }
    void BuildOKResponse()
    {
      std::string line = "Content-Type: ";
      line +=Suffix2Desc(http_request.suffix);
   
      line += LINE_END;
      http_response.response_header.push_back(line);
      line += "Content-Length: ";
      if(http_request.cgi)
      {
        line += std::to_string(http_response.response_body.size());

      }
      else 
      {
        line += std::to_string(http_request.size);//GET

      }
      line += LINE_END;
      http_response.response_header.push_back(line);
    }
    void BuildHttpresponseHelper()
    {
      
      auto &code = http_response.status_code;
      //构建状态行
      auto &line = http_response.status_line;
      line += HTTP_VERSION;
      line += " ";
      line += std::to_string(code);
      line += " ";
      line += Code2Desc(code);
      line += LINE_END;
      //构建响应正文
      std::string path = WEB_ROOT;
      path +="/";
      switch(code)
      {
        case OK:
          BuildOKResponse();break;
        case NOT_FOUND:
          path += PAGE_404;
          HandlerERROR(path);break;
        case BAD_REQUEST:
          break;
        case SERVER_ERROR:
          break;
        
        default:
          break;
      }
    }
  public:
    Endpoit(int _sock):sock(_sock)
    {}
    void RecvHttprequest()
    {
      //读取请求
      //读取的基本单位按照行来读取:需要兼容各种行分隔符
      //XXXX\r\n XXXX\r XXXX\n
      RecvHttpRequestLine(); 
      RecvHttpRequestHeader();
    }
    void ParseHttprequest()
    {
      //分析请求
      ParseHttprequestLine();
      ParseHttprequestHeader();
      //是否有正文需要读取 method:GET:没有正文 POST:需要读取正文
      //确认正文中有多少个字节读取:Content-Length决定
      RecvHttpRequestBody();
    }
    void BuildHttpresponse()
    {
      //构建响应
      
      std::string _path;
      struct stat st;
      //知道文件的大小
      //int size = 0;
      std::size_t found= 0;
      auto &code = http_response.status_code;
      if(http_request.method != "GET" && http_request.method != "POST")
      {
        //非法请求
        
        LOG(WARNING,"method is not right");
        code = BAD_REQUEST;
        goto END;
        
      }
      /*
      //状态码决定报文的处理方式
      //目前所有的上网行为宏观上只有两种:
      //1、浏览器想从服务器拿下来某种资源(打开网页,下载等)GET
      //2、浏览器想将自己是数据上传至服务器(上传文件,登录注册)POST通过正文传参,GET:URL进行传参
      //客户端上传数据是为了让http或相关程序进行数据处理
      //所以情况2:first:拿到数据 second:数据处理
      */ 
      //所以,此时需判断是否带参
       if(http_request.method == "GET")
       {
         size_t pos = http_request.uri.find("?");
         if(pos != std::string::npos)
         {
            //带参
           Util::CutString(http_request.uri,http_request.path,http_request.query_string,"?"); 
           http_request.cgi = true;
         }
         else 
         {
           //不带参
           http_request.path = http_request.uri;
         }

      }
      else if(http_request.method == "POST")
      {
        //POST
        http_request.cgi = true;
        http_request.path = http_request.uri;
      }
      else 
      {
        //do nothing
      }

       //path路径表明了服务器上的某种资源,是从根目录开始吗?不一定需要指明web根目录,根目录下一定有一个默认的首页
       //我们需要把path路径转化成我们的web根目录/... 
       //相当于给path添加前缀
       
      _path = http_request.path; 
      http_request.path = WEB_ROOT;
      http_request.path += _path;

    
      if(http_request.path[http_request.path.size()-1] == '/')
      {
        //需要添加首页信息
        http_request.path += HOME_PAGE;
      }
      //path路径对应的资源判断是否存在
      //stat:指明路径下获取一个文件的属性,如果成功获得属性,说明路径在
      
    
      if(stat(http_request.path.c_str(),&st) == 0)
      {
        //资源存在,但是存在不一定可以访问资源,有可能是一个目录
        if(S_ISDIR(st.st_mode))
        {
          //所求的资源是一个目录,不被允许,需要做相关处理:添加目录首页
          //虽然是目录,但不会以 /结尾
          http_request.path += "/";
          http_request.path += HOME_PAGE;
          stat(http_request.path.c_str(),&st);
          
        }
        if((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP)|| (st.st_mode & S_IXOTH))
        {
          //请求的是一个可执行程序
          //什么时候需要CGI进行数据处理?只要用户有数据上传上来
          http_request.cgi = true;
          
        }
        http_request.size = st.st_size;//拿到对应的文件大小
        

        
      }
      else 
      {
        //资源不存在
        LOG(WARNING,http_request.path +" Not Found");
        code = NOT_FOUND;
        goto END;
      }
  
      //把后缀提取出来
      
        //请求资源是什么类型取决于资源的后缀
      found = http_request.path.rfind(".");
      if(found == std::string::npos)
      {
        http_request.suffix = ".html";
      }
      else 
      {
        http_request.suffix = http_request.path.substr(found);
      }
      if(http_request.cgi == true)
      {
        code = ProcessCGI();//执行目标程序,拿到结果:http_response.response_body
      }
      else 
      {
        //目标网页一定存在
        //返回并不是返回网页,而是构建http响应
        code = ProcessNonCGI();//简单的网页返回,返回静态网页,只需要打开即可
      }
       //std::cout<<"debug:url "<<http_request.path<<"?"<<http_request.query_string<<std::endl;
END:

       //响应
        BuildHttpresponseHelper();//状态行,响应报头,空行,正文都有了

      
      //return;
    }
    void SendHttpresponse()
    {
    
      send(sock,http_response.status_line.c_str(),http_response.status_line.size(),0);
      for(auto iter : http_response.response_header)
      {
        send(sock,iter.c_str(),iter.size(),0);
      }
      send(sock,http_response.blank.c_str(),http_response.blank.size(),0);

      if(http_request.cgi)
      {
        //发body
        auto &response_body =  http_response.response_body;
        size_t  size = 0;
        size_t  total = 0;
        const char *start = response_body.c_str();
        while((total < response_body.size()) && (size = send(sock,start+total,response_body.size()-total,0)) > 0)
        {
          total += size;
        }
      }
      else 
      {
        sendfile(sock,http_response.fd,nullptr,http_request.size);
        close(http_response.fd);
      }
      
    }
    ~Endpoit()
    {
      close(sock);
    }


};
class Entrance
{
  public:
    static void *HandlerRequest(void *_sock)//处理线程
    {
      LOG(INFO,"Handler Request Begin");
      int sock = *(int *)_sock;
      delete (int*)_sock;
      //std::cout<<"get a new link..."<<sock<<std::endl;
#ifdef DEBUG 
      //for test 
      char buffer[4096];
      recv(sock,buffer,sizeof(buffer),0);
      std::cout<<"-----------begin-----------------"<<std::endl;
      std::cout<<buffer << std::endl;
      std::cout<<"-----------end-----------------"<<std::endl;
#else
      Endpoit *ep = new Endpoit(sock);
      ep->RecvHttprequest();
      ep->ParseHttprequest();
      ep->BuildHttpresponse();
      ep->SendHttpresponse();
      delete ep;
#endif 
/*
     std::string line;
      Util::Readline(sock,line);
      std::cout<<line<<std::endl;
      close(sock);
      return nullptr;
*/ 

      LOG(INFO,"Handler Request End");
      return nullptr;
      }


};

HttpServer.hpp

#pragma once 
#include "Log.hpp"
#include "TcpServer.hpp"
#include "Protocol.hpp"
#include <iostream>
#include <pthread.h>
#define PORT 8081
class HttpServer
{
  private:
    int port;
    TcpServer *tcp_server;
    bool stop;
  public:
    HttpServer(int _port = PORT):port(_port),tcp_server(nullptr),stop(false)
    {}
    void InitServer()
    {
      tcp_server = TcpServer::getinstance(port);
    }
    void Loop()
    {
      LOG(INFO,"Loop Begin!");
      int listen_sock = tcp_server->Sock();
      //std::cout<<"HttpServer::Loop sock "<<listen_sock<<std::endl;
      while(!stop)
      {
        
          struct sockaddr_in peer;
          socklen_t len = sizeof(peer);
          int sock = accept(listen_sock,(struct sockaddr*)&peer,&len);
          if(sock < 0)
          {
            continue;
          }

          LOG(INFO,"Get a new link!");
          int  *_sock = new int(sock);
          pthread_t tid;
          pthread_create(&tid,nullptr,Entrance::HandlerRequest,_sock);
          pthread_detach(tid);


      }
    }
    
    ~HttpServer()
    {}

};

TcpServer.hpp

#pragma once 
#include <iostream>
#include <sys/socket.h>
#include <cstdlib>
#include <cstring>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
#include "Log.hpp"

#define Backlog 5
class TcpServer
{
  private:
    int port;
    int listen_sock;
    static TcpServer *svr;
  private://把tcpserver设置为单例模式
    TcpServer(int _port):port(_port),listen_sock(-1)
    {}
    TcpServer(const TcpServer &s){}
  public:
    static TcpServer *getinstance(int port)//获取单例
    {
      static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

      if(svr == nullptr)
      {
        pthread_mutex_lock(&lock);
        if(svr == nullptr)
        {
          svr = new TcpServer(port);
          svr->InitServer();
        }
        pthread_mutex_unlock(&lock);
      }
      return svr;
    }

    void InitServer()
    {
      Socket();
      Bind();
      Listen();
      LOG(INFO,"tcp_server init success!");

    }
    void Socket()
    {
      listen_sock = socket(AF_INET,SOCK_STREAM,0);
      if(listen_sock < 0)
      {
        LOG(FATAL,"socket error");
        exit(1);
      }
      //socket 地址复用:服务器崩了,连接还在,此时服务器重启不了,因为监听套接字对应的端口号
      //默认是绑定的,端口没有被完全释放,导致服务器无法立即重启,所以需要地址复用
      int opt = 1;
      setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
      LOG(INFO,"socket success!");
    }
    void Bind()
    {
      
      struct sockaddr_in local;
      memset(&local,0,sizeof(local));
      local.sin_family = AF_INET;
      local.sin_port = htons(port);
      local.sin_addr.s_addr = INADDR_ANY;//云服务器不能直接绑定公网ip
      
      if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local)) <0)
      {
        LOG(FATAL,"bind error");
        exit(2);

      }
      LOG(INFO,"bind success!");
     
    }
    void Listen()
    {
      if(listen(listen_sock,Backlog) < 0)
      {
        LOG(FATAL,"listen error");
        exit(3);
      }
      LOG(INFO,"listen success!");

    }
    int Sock()
    {
      return listen_sock;
    }
    ~TcpServer()
    {
      if(listen_sock >= 0)
        close(listen_sock);
    }
};

TcpServer* TcpServer::svr = nullptr;

Log.hpp

//日志
#pragma once
#include <iostream>
#include <string>
#include <ctime>

#define INFO     1
#define WARNING  2
#define ERROR    3
#define FATAL    4

#define LOG(level,message) Log(#level,message,__FILE__,__LINE__)//#level:可以把宏参转化成字符串
void Log(std::string level,std::string message,std::string file_name,int line)
{
  //[日志级别][时间戳][日志信息][错误文件名称][行数]
  //级别:INFO:正常输出WARNING:警告ERROR:错误FATAL:致命信息,程序应立即终止
  //时间戳:ctime time(nullptr)
  //信息:错误,风险,提示等
  //错误文件名: __FILE__
  //行数:__LINE__
  

  std::cout<<"["<<level<<"]"<<"["<<time(nullptr)<<"]"<<"["<<message<<"]"<<"["<<file_name<<"]"<<"["<<line<<"]"<<std::endl;

}

Util.hpp

#pragma once 
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>

//工具类
class Util
{
  public:
    static int Readline(int sock,std::string &out)
    {
      //按行读取
      //通过out,把读到的行拿走
      char ch = 'X';//随便初始化一个值(不能是行分隔符)
    
      while(ch != '\n')
      {
        ssize_t s = recv(sock,&ch,1,0);
        if(s > 0)
        {
          if(ch == '\r')
          {
            // \r->\n  \r\n->\n
            
            //recv 直接读取\r后面的内容,把数据从接收缓冲区中取走
            //MSG_PEEK:recv最后一个参数,查看\r后面的内容,但并不取走-》数据窥探
            recv(sock,&ch,1,MSG_PEEK);
            if(ch == '\n')
            {
              //\r\n->\n
              recv(sock,&ch,1,0);
            }
            else 
            {
              //\r->\n
              ch = '\n';
            }
          }
          //1、普通字符 2、\n
          out.push_back(ch);

        }
        else if(s == 0) 
        {
          //对端将连接关闭
          std::cout<<"client close"<<std::endl;
          return 0;
        }
        else 
        {
          std::cout<<"error"<<std::endl;
          return -1;
        }
      }
      return out.size();
    }
    static bool CutString(std::string &target,std::string &sub1_out,std::string &sub2_out,std::string sep)
    {
      //进行字符串切分
      size_t pos = target.find(sep);
      if(pos != std::string::npos)
      {
        //该位置是存在的
        // AAA: BBBB
        // [0,pos)
        sub1_out = target.substr(0,pos);//截取
        sub2_out = target.substr(pos+sep.size());
        return true;
      }
      return false;
    }

};

main.cc

#include <iostream>
#include <string>
#include <memory>
#include "HttpServer.hpp"
#include "Log.hpp"

//#include "TcpServer.hpp"
static void Usage(std::string proc)
{
  std::cout<<"Usage: "<<proc << " port "<<std::endl;
}
int main(int argc,char *argv[])
{

  if(argc != 2)
  {
    Usage(argv[0]);
    exit(4);
  }

  int port =atoi(argv[1]);
  std::shared_ptr<HttpServer> http_server(new HttpServer(port));
  http_server->InitServer();
  http_server->Loop();
  return 0;
}

在这里插入图片描述
test_cgi.cc

#include <iostream>
#include <cstdlib>
#include <unistd.h>
using namespace std;
bool GetQueryString(std::string &query_string)
{
  bool result = false;
  std::string method = getenv("METHOD");
  if(method == "GET")
  {
    query_string = getenv("QUERY_STRING");
    result = true;
  }
  else if(method == "POST")
  {
    //CGI如何得知需要从标准输入中读取多少个字节?

    int content_length = atoi(getenv("CONTENT_LENGTH"));
    char c = 0;
    while(content_length)
    {
      read(0,&c,1);
      query_string.push_back(c);
      content_length--;
    }
    result = true;
  }
  else 
  {
    result = false;
  }
  return result;
}
void CutString(std::string &in,const std::string &sep,std::string &out1,std::string &out2)
{
  auto pos = in.find(sep);
  if(pos != std::string::npos)
  {
    out1 = in.substr(0,pos);
    out2 = in.substr(pos+sep.size());
  }
}
int main()
{
  std::string query_string;
  GetQueryString(query_string);
  std::string str1;
  std::string str2;
  std::string name1;
  std::string value1;
  CutString(query_string,"&",str1,str2);
  CutString(str1,"=",name1,value1);
  std::string name2;
  std::string value2;
  CutString(str2,"=",name2,value2);

  std::cout<<name1 <<": "<<value1<<std::endl;
  std::cout<<name2 <<": "<<value2<<std::endl;
  std::cerr<<name1 <<": "<<value1<<std::endl;
  std::cerr<<name2 <<": "<<value2<<std::endl;
  return 0;
}

在这里插入图片描述
Makefile

bin=httpserver
cgi=test_cgi 
cc=g++
LD_FLAGS=-std=c++11 -lpthread
curr=$(shell pwd)
src=main.cc
ALL:$(bin) $(cgi)
.PHONY:ALL
$(bin):$(src)
		$(cc) -o $@ $^ $(LD_FLAGS)
$(cgi):cgi/test_cgi.cc
		$(cc) -o $@ $^ -std=c++11
.PHONY:clean
clean:
		rm -f  $(bin) $(cgi)
		rm -rf output
		
.PHONY:output 
output:
		mkdir -p output 
		cp $(bin) output 
		cp -rf wwwroot output 
		cp $(cgi) output/wwwroot


build.sh

#!/bin/bash 

make clean
make 
make output

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

线程池介入

解决问题:

  • 大量链接过来导致服务器内部进程或者线程暴增,进而导致服务器效率严重降低或者挂掉
  • 节省链接请求到来时,创建线程的时间成本
  • 让服务器的效率在一个恒定的稳定区间内(线程个数不增多,CPU调度成本不变)

ThreadPool.hpp

#pragma once 
#include <iostream>
#include <queue>
#include <pthread.h>
#include "Task.hpp"
#include "Log.hpp"
#define NUM 3
class ThreadPool
{
  private:
    int num;
    bool stop; 
    std::queue<Task> task_queue;//任务队列
    pthread_mutex_t lock;
    pthread_cond_t cond;
    ThreadPool(int _num = NUM):num(_num),stop(false)
    {
      pthread_mutex_init(&lock,nullptr);//锁初始化
      pthread_cond_init(&cond,nullptr);//条件变量初始化
    }
    ThreadPool(const ThreadPool &){}
    static ThreadPool *single_instance;
  public:
    static ThreadPool* getinstance()
    {
      //设置单例:http_server想用tcp功能就用tcp,想用线程池就用线程池
      static pthread_mutex_t _mutex = PTHREAD_MUTEX_INITIALIZER;
      if(single_instance == nullptr)
      {
        pthread_mutex_lock(&_mutex);
        if(single_instance == nullptr)
        {
          single_instance = new ThreadPool();
          single_instance->InitThreadPool();
        }
        pthread_mutex_unlock(&_mutex);
      }
      return single_instance;
    }
    bool IsStop()
    {
      return stop;
    }
    void Lock()
    {
      pthread_mutex_lock(&lock);
    }
    void Unlock()
    {
      pthread_mutex_unlock(&lock);
    }
    bool TaskQueueIsEmpty()
    {
      return task_queue.size()==0 ? true : false;
    }
    void ThreadWait()
    {
      pthread_cond_wait(&cond,&lock);
    }
    void ThreadWakeUp()
    {
      pthread_cond_signal(&cond);//唤醒
    }
    static void *ThreadRoutine(void *args)//线程例程
    {
      ThreadPool *tp = (ThreadPool*)args;
      while(true)
      {
        Task t;
        tp->Lock();
        while(tp->TaskQueueIsEmpty())
        {
          tp->ThreadWait();//当线程醒来的时候一定有互斥锁
        }
        tp->PopTask(t);//拿出任务
        tp->Unlock();
        t.ProcessOn();
      }
    }
    bool InitThreadPool()
    {
      for(int i = 0;i < num;i++)
      {
        pthread_t tid;
        if(pthread_create(&tid,nullptr,ThreadRoutine,this) != 0)
        {
          //创建失败
          LOG(FATAL,"cretae threadpool error");
          return false;
        }
      }
      LOG(INFO,"create threadpool success");
      return true;
    }
    void PushTask(const Task &task)//in 
    {
      Lock();
      task_queue.push(task);
      Unlock();
      ThreadWakeUp();
    }
    void PopTask(Task &task)//out 
    {
      task = task_queue.front();
      task_queue.pop();
    }
    ~ThreadPool()
    {
      pthread_mutex_destroy(&lock);
      pthread_cond_destroy(&cond);
    }


};
ThreadPool* ThreadPool::single_instance = nullptr;

Task.hpp

#pragma once 
#include <iostream>
#include "Protocol.hpp"
class Task
{
  private:
    int sock;
    CallBack handler;//设置回调
  public:
    
    Task(){}
    Task(int _sock):sock(_sock)
    {}
    //处理任务
    void ProcessOn()
    {
      handler(sock);
    }
    ~Task()
    {}

};

TcpServer.hpp

#pragma once 
#include <iostream>
#include <sys/socket.h>
#include <cstdlib>
#include <cstring>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <unistd.h>
#include "Log.hpp"

#define Backlog 5
class TcpServer
{
  private:
    int port;
    int listen_sock;
    static TcpServer *svr;
  private://把tcpserver设置为单例模式
    TcpServer(int _port):port(_port),listen_sock(-1)
    {}
    TcpServer(const TcpServer &s){}
  public:
    static TcpServer *getinstance(int port)//获取单例
    {
      static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

      if(svr == nullptr)
      {
        pthread_mutex_lock(&lock);
        if(svr == nullptr)
        {
          svr = new TcpServer(port);
          svr->InitServer();
        }
        pthread_mutex_unlock(&lock);
      }
      return svr;
    }

    void InitServer()
    {
      Socket();
      Bind();
      Listen();
      LOG(INFO,"tcp_server init success!");

    }
    void Socket()
    {
      listen_sock = socket(AF_INET,SOCK_STREAM,0);
      if(listen_sock < 0)
      {
        LOG(FATAL,"socket error");
        exit(1);
      }
      //socket 地址复用:服务器崩了,连接还在,此时服务器重启不了,因为监听套接字对应的端口号
      //默认是绑定的,端口没有被完全释放,导致服务器无法立即重启,所以需要地址复用
      int opt = 1;
      setsockopt(listen_sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
      LOG(INFO,"socket success!");
    }
    void Bind()
    {
      
      struct sockaddr_in local;
      memset(&local,0,sizeof(local));
      local.sin_family = AF_INET;
      local.sin_port = htons(port);
      local.sin_addr.s_addr = INADDR_ANY;//云服务器不能直接绑定公网ip
      
      if(bind(listen_sock,(struct sockaddr*)&local,sizeof(local)) <0)
      {
        LOG(FATAL,"bind error");
        exit(2);

      }
      LOG(INFO,"bind success!");
     
    }
    void Listen()
    {
      if(listen(listen_sock,Backlog) < 0)
      {
        LOG(FATAL,"listen error");
        exit(3);
      }
      LOG(INFO,"listen success!");

    }
    int Sock()
    {
      return listen_sock;
    }
    ~TcpServer()
    {
      if(listen_sock >= 0)
        close(listen_sock);
    }
};

TcpServer* TcpServer::svr = nullptr;

HttpSever.hpp

#pragma once 
#include "Log.hpp"
#include "TcpServer.hpp"
#include <iostream>
#include <pthread.h>
#include <signal.h>
#include "Task.hpp"
#include "ThreadPool.hpp"
#define PORT 8081
class HttpServer
{
  private:
    int port;
    bool stop;
  public:
    HttpServer(int _port = PORT):port(_port),stop(false)
    {}
    void InitServer()
    {
      signal(SIGPIPE,SIG_IGN);//信号SIGPIPE需要进行忽略,如果没忽略,可能在写入时直接崩溃

    }
    void Loop()
    {
      TcpServer *tsvr = TcpServer::getinstance(port);
      LOG(INFO,"Loop Begin!");
 
      while(!stop)
      {
          struct sockaddr_in peer;
          socklen_t len = sizeof(peer);
          int sock = accept(tsvr->Sock(),(struct sockaddr*)&peer,&len);
          if(sock < 0)
          {
            continue;
          }
          
          LOG(INFO,"Get a new link!");
          Task task(sock);
          ThreadPool::getinstance()->PushTask(task);
      }
    }
    
    ~HttpServer()
    {}

};

Protocol.hpp

#pragma once 
#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string>
#include <sstream>
#include <unordered_map>
#include <sys/stat.h>
#include <fcntl.h>
#include "Util.hpp"
#include "Log.hpp"
#include <algorithm>
#include <sys/wait.h>
#include <sys/sendfile.h>
#define SEP ": "
#define OK 200
#define NOT_FOUND 404
#define BAD_REQUEST 400
#define WEB_ROOT "wwwroot"
#define HOME_PAGE "index.html"
#define HTTP_VERSION "HTTP/1.0"
#define LINE_END "\r\n"
#define PAGE_404 "404.html"
#define SERVER_ERROR 500
static std::string Code2Desc(int code)
{
  std::string desc;
  switch(code)
  {
    case 200:
      desc="OK";break;
    case 404:
      desc = "Not Found";break;
    default:
      break;
  }
  return desc;
}
static std::string Suffix2Desc(const std::string &suffix)
{
  static std::unordered_map<std::string,std::string> Suffix2desc = {
    {".html","text/html"},
    {".css","text/css"},
    {".js","appplication/javascript"},
    {".jpg","application/x-jpg"},
  };
  auto iter =  Suffix2desc.find(suffix);
  if(iter != Suffix2desc.end())
  {
    return iter->second;
  }
  return ".text/html";
}
class HttpRequest 
{
  public:
    std::string request_line;//请求行
    std::vector<std::string> request_header;//请求报头
    std::string blank;//空行
    std::string request_body;//正文

    //解析完毕之后的结果
    std::string method;//请求方法 GET:有正文 POST:需要读取正文
    std::string uri;//请求资源 path?args
    std::string version;//请求版本

    std::unordered_map<std::string,std::string> header_kv;//HOST: XXX.XXX Connection: XXX
    int content_length;//正文长度
    std::string path;//想访问的资源
    std::string query_string;//path后面的参数
    std::string suffix;//请求资源的后缀
    bool cgi;
    int size;
  public:
    HttpRequest():content_length(0),cgi(false)
    {}
    ~HttpRequest()
    {}
};
class HttpResponse
{
  public:
    std::string status_line;//状态行
    std::vector<std::string> response_header;//响应报头
    std::string blank;//空行
    std::string response_body;//正文

    int status_code;//状态码
    int fd;
    int size;//想访问的资源的大小
  public:
    HttpResponse():blank(LINE_END),status_code(OK),fd(-1){}
    ~HttpResponse(){}
};

//读取请求,分析请求,构建响应
//IO通信
class Endpoit
{
  //对端:完成业务逻辑
  private:
    int sock;
    HttpRequest http_request;
    HttpResponse http_response;
    bool stop;
  private:
    bool RecvHttpRequestLine()
    {
      auto &line = http_request.request_line;
      if(Util::Readline(sock,line) > 0)
      {
        line.resize(line.size()-1);
        LOG(INFO,http_request.request_line);
      }
      else 
      {
        stop = true;
      }
      return stop;
    }
    bool RecvHttpRequestHeader()
    {
      //如何保证请求报头已经读取完毕
      //1、空格结束 2、都是按照行进行陈列的
      std::string line;
      while(true)
      {
        line.clear();
        if(Util::Readline(sock,line) <= 0)
        {
          stop = true;
          break;
        }
        if(line == "\n")
        {
        http_request.blank = line;
          break;
        }
        line.resize(line.size()-1);//去掉\n
        http_request.request_header.push_back(line);
        LOG(INFO,line);
      }
      return stop;
    }
    void ParseHttprequestLine()
    {
      auto &line = http_request.request_line;
      // 方法 URI HTTP/版本
      //stringstream :默认按照空格把字符串打散切分
      std::stringstream ss(line);
      ss >> http_request.method >> http_request.uri >> http_request.version;
      auto &method = http_request.method;
      std::transform(method.begin(),method.end(),method.begin(),::toupper);
      LOG(INFO,line);

    }
    void ParseHttprequestHeader()
    {
      std::string key;
      std::string value;
      for(auto &iter : http_request.request_header)
      {
        
        if(Util::CutString(iter,key,value,SEP))
        {
          http_request.header_kv.insert({key,value});
        }
      }

    }
    bool IsNeedRecvHttpRequestBody()
    {
      auto method = http_request.method;
      if(method == "POST")
      {
        auto &header_kv = http_request.header_kv;
        auto iter =  header_kv.find("Content-Length");
        if(iter != header_kv.end())
        {
          //找到了
          LOG(INFO,"Post Method,Content-Length"+iter->second);
          http_request.content_length = atoi(iter->second.c_str());
          return true;
        }
      }
      return false;
    }
    bool RecvHttpRequestBody()
    {
      if(IsNeedRecvHttpRequestBody())
      {
        int content_length = http_request.content_length;
        auto &body = http_request.request_body;
        char ch = 0;
        while(content_length)
        {
          ssize_t s = recv(sock,&ch,1,0);
          if(s > 0)
          {
            body.push_back(ch);
            content_length--;
          }
          else 
          {
            stop = true;
            break;
          }
        }
      }
      return stop;
    }
    int ProcessNonCGI()
    {
      //eg:  HTTP/1.0 200 OK
      http_response.fd = open(http_request.path.c_str(),O_RDONLY);
      if(http_response.fd >= 0)
      {
        LOG(INFO,http_request.path+"open success!");
        return OK;
      }


      //http_response.response_body是用户层的缓冲区 :磁盘-》内核层-》用户层
      //sendfile:把数据从一个文件描述符拷贝给另一给文件描述符,只在内核进行
      return NOT_FOUND;
    }
    int ProcessCGI()
    {
      LOG(INFO,"process cgi");
      int code = OK;
      auto &method = http_request.method;
      auto &query_string = http_request.query_string;//GET
      auto &body_text = http_request.request_body;//POST
      int content_length = http_request.content_length;

      auto &response_body = http_response.response_body;
      //新线程,但是从头到尾只有一个进程:httpserver
      //如何用一个进程去执行另一个进程-》程序替换:exec*
      //不能直接替换(替换的是httpserver)-》可以子线程实现
      auto &bin = http_request.path;//要让子进程执行的目标程序
      //站在父进程角度
      int input[2];//创建管道:父子间进程通信
      int output[2];
      std::string query_string_env;//环境变量
      std::string method_env;//方法的环境变量
      std::string content_length_env;


      if(pipe(input) < 0)
      {
        //error
        LOG(ERROR,"input errpr");
        code = SERVER_ERROR;
        return code;
      }
      if(pipe(output) < 0)
      {
        //error
        LOG(ERROR,"output error");
        code = SERVER_ERROR;
        return code;
      }
      pid_t pid = fork();
      if(pid == 0)//child
      {
        //exec*
        close(input[0]);
        close(output[1]);
        method_env = "METHOD=";
        method_env += method;

        std::cout<<"cgi: "<<method_env<<std::endl;

        putenv((char*)method_env.c_str());

        if(method == "GET")
        {
          //父进程    -》数据-》    子进程
          //通过:  管道    环境变量   
          //GET需要导环境变量
          //环境变量是具有全局属性的(可以被子进程继承下去,不受exec*的影响)
          query_string_env = "QUERY_STRING=";
          query_string_env += query_string;
          putenv((char*)query_string_env.c_str());
          //子进程如何区分是从标准输入中读取还是从环境变量里面拿到数据?通过知道请求方法
        
          LOG(INFO,"Get Method,Add Query_string Env");
        }

        else if(method == "POST")
        {
          content_length_env = "CONTENT_LENGTH=";
          content_length_env += std::to_string(content_length);
          putenv((char*)content_length_env.c_str());
          LOG(INFO,"Post Method,Add Content-Length Env");
        }
        else 
        {
          //do nothing
        }
        
/*
        //替换成功之后,目标子进程如何得知对应的读写文件描述符是多少呢?
        //在层序替换之后,数据没有了单数曾经打开文件pipe还在(程序替换只替换代码和数据,并不替换内核进程相关的数据结构包括文件描述符表)
        //-》让目标被替换之后的进程,读取管道等价于读取标准输入,写入管道等于写到标准输出
        //->重定向技术,可以在exec*系列函数被执行前进行重定向 
*/      
        //站在子进程角度:
        //inout[1]:write output[0]:read 
        std::cout<<"bin "<<bin<<std::endl;
        dup2(output[0],0);
        dup2(input[1],1);
        
        
        execl(bin.c_str(),bin.c_str(),nullptr);
        exit(1);
      }
      else if(pid < 0)
      {
        //创建子进程失败
        LOG(ERROR,"fork error");
        return 404;
      }
      else 
      {
        //father
        close(input[1]);//0:read 1:write
        close(output[0]);
        if(method == "POST")
        {
          const char* start = body_text.c_str();
          int total = 0;
          int size = 0;
          while((total< content_length) &&(size = write(output[1],total+start,body_text.size()-total)) > 0)
          {
              total += size;
            
          }
        }

        //让父进程获得子进程的输出结果
        char c;
        while(read(input[0],&c,1) > 0)
        {
          //CGI执行完之后的结果,并不可以直接返回给浏览器,因为这部分内容只是响应的正文
          response_body.push_back(c);
        }
        int status = 0;
        pid_t ret = waitpid(pid,&status,0);//等待子进程
        if(ret == pid)
        {
          //wait success
          if(WIFEXITED(status))//检验进程退出是否正常
          {
            if(WEXITSTATUS(status) == 0)//获得退出码
            {
              code = OK;
            }
            else 
            {
              code = BAD_REQUEST;
            }
          }
          else 
          {
            code = SERVER_ERROR;
          }
        }
      
        close(input[1]);//0:read 1:write
        close(output[0]);
      }

      return code;
    }
    void HandlerERROR(std::string page)
    {
      http_request.cgi = false;
      http_response.fd = open(page.c_str(),O_RDONLY);
      if(http_response.fd > 0)
      {
        struct stat st;
        stat(page.c_str(),&st);
        http_request.size = st.st_size;
        std::string line = "Content-Type: text/html";
        line += LINE_END;
        http_response.response_header.push_back(line);
        line = "Content-Length: ";
        line += std::to_string(st.st_size);
        line += LINE_END;
        http_response.response_header.push_back(line);
        
      }
    }
    void BuildOKResponse()
    {
      std::string line = "Content-Type: ";
      line +=Suffix2Desc(http_request.suffix);
   
      line += LINE_END;
      http_response.response_header.push_back(line);
      line += "Content-Length: ";
      if(http_request.cgi)
      {
        line += std::to_string(http_response.response_body.size());

      }
      else 
      {
        line += std::to_string(http_request.size);//GET

      }
      line += LINE_END;
      http_response.response_header.push_back(line);
    }
    void BuildHttpresponseHelper()
    {
      
      auto &code = http_response.status_code;
      //构建状态行
      auto &line = http_response.status_line;
      line += HTTP_VERSION;
      line += " ";
      line += std::to_string(code);
      line += " ";
      line += Code2Desc(code);
      line += LINE_END;
      //构建响应正文
      std::string path = WEB_ROOT;
      path +="/";
      switch(code)
      {
        case OK:
          BuildOKResponse();break;
        case NOT_FOUND:
          path += PAGE_404;
          HandlerERROR(path);break;
        case BAD_REQUEST:
          break;
        case SERVER_ERROR:
          break;
        
        default:
          break;
      }
    }
  public:
    Endpoit(int _sock):sock(_sock),stop(false)
    {}
    bool IsStop()
    {
      return stop;
    }
    void RecvHttprequest()
    {
      //读取请求
      //读取的基本单位按照行来读取:需要兼容各种行分隔符
      //XXXX\r\n XXXX\r XXXX\n
      if((!RecvHttpRequestLine()) && (!RecvHttpRequestHeader()))
      {
        ParseHttprequestLine();
        ParseHttprequestHeader();
        RecvHttpRequestBody();
      } 
    }
    void BuildHttpresponse()
    {
      //构建响应
      
      std::string _path;
      struct stat st;
      //知道文件的大小
      //int size = 0;
      std::size_t found= 0;
      auto &code = http_response.status_code;
      if(http_request.method != "GET" && http_request.method != "POST")
      {
        //非法请求
        
        LOG(WARNING,"method is not right");
        code = BAD_REQUEST;
        goto END;
        
      }
      /*
      //状态码决定报文的处理方式
      //目前所有的上网行为宏观上只有两种:
      //1、浏览器想从服务器拿下来某种资源(打开网页,下载等)GET
      //2、浏览器想将自己是数据上传至服务器(上传文件,登录注册)POST通过正文传参,GET:URL进行传参
      //客户端上传数据是为了让http或相关程序进行数据处理
      //所以情况2:first:拿到数据 second:数据处理
      */ 
      //所以,此时需判断是否带参
       if(http_request.method == "GET")
       {
         size_t pos = http_request.uri.find("?");
         if(pos != std::string::npos)
         {
            //带参
           Util::CutString(http_request.uri,http_request.path,http_request.query_string,"?"); 
           http_request.cgi = true;
         }
         else 
         {
           //不带参
           http_request.path = http_request.uri;
         }

      }
      else if(http_request.method == "POST")
      {
        //POST
        http_request.cgi = true;
        http_request.path = http_request.uri;
      }
      else 
      {
        //do nothing
      }

       //path路径表明了服务器上的某种资源,是从根目录开始吗?不一定需要指明web根目录,根目录下一定有一个默认的首页
       //我们需要把path路径转化成我们的web根目录/... 
       //相当于给path添加前缀
       
      _path = http_request.path; 
      http_request.path = WEB_ROOT;
      http_request.path += _path;

    
      if(http_request.path[http_request.path.size()-1] == '/')
      {
        //需要添加首页信息
        http_request.path += HOME_PAGE;
      }
      //path路径对应的资源判断是否存在
      //stat:指明路径下获取一个文件的属性,如果成功获得属性,说明路径在
      
    
      if(stat(http_request.path.c_str(),&st) == 0)
      {
        //资源存在,但是存在不一定可以访问资源,有可能是一个目录
        if(S_ISDIR(st.st_mode))
        {
          //所求的资源是一个目录,不被允许,需要做相关处理:添加目录首页
          //虽然是目录,但不会以 /结尾
          http_request.path += "/";
          http_request.path += HOME_PAGE;
          stat(http_request.path.c_str(),&st);
          
        }
        if((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP)|| (st.st_mode & S_IXOTH))
        {
          //请求的是一个可执行程序
          //什么时候需要CGI进行数据处理?只要用户有数据上传上来
          http_request.cgi = true;
        }
        http_request.size = st.st_size;//拿到对应的文件大小
      }
      else 
      {
        //资源不存在
        LOG(WARNING,http_request.path +" Not Found");
        code = NOT_FOUND;
        goto END;
      }
  
      //把后缀提取出来
      
        //请求资源是什么类型取决于资源的后缀
      found = http_request.path.rfind(".");
      if(found == std::string::npos)
      {
        http_request.suffix = ".html";
      }
      else 
      {
        http_request.suffix = http_request.path.substr(found);
      }
      if(http_request.cgi == true)
      {
        code = ProcessCGI();//执行目标程序,拿到结果:http_response.response_body
      }
      else 
      {
        //目标网页一定存在
        //返回并不是返回网页,而是构建http响应
        code = ProcessNonCGI();//简单的网页返回,返回静态网页,只需要打开即可
      }
       
END:

       //响应
        BuildHttpresponseHelper();//状态行,响应报头,空行,正文都有了

      
      //return;
    }
    void SendHttpresponse()
    {
    
      send(sock,http_response.status_line.c_str(),http_response.status_line.size(),0);
      for(auto iter : http_response.response_header)
      {
        send(sock,iter.c_str(),iter.size(),0);
      }
      send(sock,http_response.blank.c_str(),http_response.blank.size(),0);

      if(http_request.cgi)
      {
        //发body
        auto &response_body =  http_response.response_body;
        size_t  size = 0;
        size_t  total = 0;
        const char *start = response_body.c_str();
        while((total < response_body.size()) && (size = send(sock,start+total,response_body.size()-total,0)) > 0)
        {
          total += size;
        }
      }
      else 
      {
        sendfile(sock,http_response.fd,nullptr,http_request.size);
        close(http_response.fd);
      }
      
    }
    ~Endpoit()
    {
      close(sock);
    }


};
class CallBack 
{
  public:
    CallBack()
    {}
    void operator()(int sock)//仿函数
    {
      HandlerRequest(sock);
    }
    void *HandlerRequest(int sock)//处理线程
    {
      LOG(INFO,"Handler Request Begin");
#ifdef DEBUG 
      //for test 
      char buffer[4096];
      recv(sock,buffer,sizeof(buffer),0);
      std::cout<<"-----------begin-----------------"<<std::endl;
      std::cout<<buffer << std::endl;
      std::cout<<"-----------end-----------------"<<std::endl;
#else
      Endpoit *ep = new Endpoit(sock);
      ep->RecvHttprequest();
      if(!ep->IsStop())
      {
        LOG(INFO,"Recv NO ERROR, Build and Send");
        ep->BuildHttpresponse();
        ep->SendHttpresponse();

      }
      else{
        LOG(WARNING,"Recv Error,Stop Build and Send");
      }
      delete ep;
#endif 
      LOG(INFO,"Handler Request End");
      return nullptr;
      }
      ~CallBack()
      {}

};

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
  </head>
  <body>
    <form action="/test_cgi" method="GET">
      x:<input type="text" name="data_x" value="0">
      <br>
      y:<input type="text" name="data_y" value="0">
      <br>
      <input type="submit" value="Submit">
    </form> 
    <p>点击提交,会将数据提交给test_cgi进行后台数据处理</p>
  </body>
</html>

(其余代码不变)
在这里插入图片描述
test_cgi.cc

#include <iostream>
#include <cstdlib>
#include <unistd.h>
using namespace std;
bool GetQueryString(std::string &query_string)
{
  bool result = false;
  std::string method = getenv("METHOD");
  if(method == "GET")
  {
    query_string = getenv("QUERY_STRING");
    result = true;
  }
  else if(method == "POST")
  {
    //CGI如何得知需要从标准输入中读取多少个字节?

    int content_length = atoi(getenv("CONTENT_LENGTH"));
    char c = 0;
    while(content_length)
    {
      read(0,&c,1);
      query_string.push_back(c);
      content_length--;
    }
    result = true;
  }
  else 
  {
    result = false;
  }
  return result;
}
void CutString(std::string &in,const std::string &sep,std::string &out1,std::string &out2)
{
  auto pos = in.find(sep);
  if(pos != std::string::npos)
  {
    out1 = in.substr(0,pos);
    out2 = in.substr(pos+sep.size());
  }
}
int main()
{
  std::string query_string;
  GetQueryString(query_string);
  std::string str1;
  std::string str2;
  std::string name1;
  std::string value1;
  CutString(query_string,"&",str1,str2);
  CutString(str1,"=",name1,value1);
  std::string name2;
  std::string value2;
  CutString(str2,"=",name2,value2);

  std::cout<<name1 <<": "<<value1<<std::endl;
  std::cout<<name2 <<": "<<value2<<std::endl;
  std::cerr<<name1 <<": "<<value1<<std::endl;
  std::cerr<<name2 <<": "<<value2<<std::endl;
  
  //可能进行计算(计算搜索,登录等)/某种存储(注册)
  int x = atoi(value1.c_str());
  int y = atoi(value2.c_str());
  std::cout<< "<html>";
  std::cout<< "<head><meta charset=\"UTF-8\"></head>";
  std::cout<< "<body>";
  std::cout<< "<h3>"<< value1<< "+ "<< value2 << "=" << x + y<<"</h3>";
  std::cout<< "</body>";
  std::cout<< "</html>";
  return 0;
}

在这里插入图片描述

Get通过uri传参,from提交的时候会自动拼接url
POST通过正文传参
Get通过uri传参,大小一般有限制(通过环境变量传给子进程),提交的数据比较公开

通过CGI访问数据库

连接数据库
rz -e 数据库压缩包
tar  xzf 数据库压缩包

其中 include 包含所有的方法声明, lib 包含所有的方法实现(打包成库)

gcc -o mysql_conn mysql_conn.cc -I ./include -L ./lib -lmysqlclient

export LD_LIBRARY_PATH=./lib #动态库查找路径
---------------------------
#或者
gcc -o mysql_conn mysql_conn.cc -std=c++11 -I ./include -L ./lib -lmysqlclient -plthread -ldl -static #静态链接

ldd mysql_conn//找库

通过 mysql_get_client_info() 函数

mysql_get_client_info() //查看
------------------------
mysql client Version: 6.1.6

创建新用户

create user '用户名'@'localhost' identified by '密码'
mysql接口介绍
初始化mysql_init()

要使用库,必须先进行初始化! MYSQL *mysql_init(MYSQL *mysql);
如:MYSQL *mfp = mysql_init(NULL)

链接数据库mysql_real_connect

初始化完毕之后,必须先链接数据库,在进行后续操作。(mysql网络部分是基于TCP/IP的)

MYSQL *mysql_real_connect(MYSQL *mysql, const char *host,
		const char *user,
		const char *passwd,
		const char *db,
		unsigned int port,
		const char *unix_socket,
		unsigned long clientflag);
  • mysql:需要链接的数据库
  • host :127.0.0.1
  • user: 用户名
  • password 密码
  • db:数据库
  • Port:端口号
  • 套接字指针:通常设置为nullptr
  • clientflag:连接标志通常设置为0

下发mysql命令mysql_query

int mysql_query(MYSQL *mysql, const char *q);
//第二个参数为要执行的sql语句,如“select * from table”。

获取执行结果mysql_store_result

MYSQL_RES *mysql_store_result(MYSQL *mysql);

**关闭mysql链接mysql_close
**

void mysql_close(MYSQL *sock);

设置编码格式

mysql_set_character_set();
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值