Linux_网络项目_WEB服务器 处理HTTP请求构建响应、CGI

1. 处理HTTP请求

需要注意的是,在处理HTTP请求和构建HTTP响应之前,首先要判断HTTP请求的合法性。这个项目只支持POST和GET方法。

当请求非法时直接发送错误响应即可。

宏观上,客户端访问服务器有两种情况

  1. 从服务器上拿取资源(打开网页,下载)
  2. 将自己的资源上传到服务器上。(登录、注册)

将自己的资源上传到服务器上可以使用GET通过URL传参,或者POST方法通过正文传参。
而且拿到数据只是第一步,还需要进行数据处理,这里先不考虑。同时如果HTTP请求服务器可执行程序时,也需要特殊处理,这里也先不考虑。

GET通过URL传参:检测URL中是否有?即可,?左边是资源路径,右边是参数

//HTTP响应报文
class HttpResponse{
  public:
    std::string StatusLine_HTTP;//状态行
    std::vector<std::string>ResponHeads;//首部字段
    std::string ResponBlank;//空行
    std::string ResponBody;//正文

    int status_code=OK;//响应状态码
};

//HTTP请求报文
class HttpRequest{
  public:
    std::string RequestLine_HTTP;//请求行
    std::vector<std::string>RequestHeads;//首部字段
    std::string RequestBlank;//空行
    std::string RequestBody;//正文

    //解析完请求报文后的结果
    std::string Method;
    std::string URI;//Path?Pararm
    std::string Version;

    //保存解析首部字段的map
    std::unordered_map<std::string,std::string>Head_KVS;

    int Content_Lenth=0;

    //访问资源的路径
    std::string Path;

    //如果是GET方法通过URL上传的参数
    std::string Param;
};

//读取请求,分析请求,构建响应,基本IO通信,实现基本业务逻辑
class EndPoint{
  private:
    int sock;
    HttpRequest http_request;//http请求
    HttpResponse http_response;//http响应
  public:
    EndPoint(int _sock):sock(_sock){}

    void MakeRespon_HTTP(){//构建响应
      //判断请求类型
      if(http_request.Method!="GET"&&http_request.Method!="POST"){
        ERRORLOG(WARNING,"error request");
        http_response.status_code=NOTFOUND;
        goto END;
      }
      //如果是GET方法需要处理URL,看URL是否有参数
      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.Param,"?");
        }
        else{
          //不是通过GET传参数
          http_request.Path=http_request.URI;
        }
      }
END:
      return;
    }

    void SendRespon_HTTP(){//发送响应

    }

    ~EndPoint(){}
};

GET方法不带参数时:
在这里插入图片描述

在这里插入图片描述

GET方法带参数时:
在这里插入图片描述
在这里插入图片描述
成功将URI分成路径和参数两部分。

之后要判断路径是否合法,路径表明了服务器上的某种资源。路径不合法时服务器也要向客户端发送错误响应。

  • 路径 / 不一定指服务器系统根目录,需要自己指定web根目录(取名为wwwroot文件夹)。并且如果URL路径为 / ,系统默认返回wwwroot文件夹下的index.html网页文件。(web根目录下,只要有一个路径都需要一个index.html作为每个路径下默认网页)
    在这里插入图片描述

所以上文中获取到URL的路径,不能直接使用,需要拼接上web根目录。
eg: /a/b/c -->wwwroot/a/b/c
才是客户端完整的请求资源路径,之后再对路径进行合法性判断。
最后结果应如下图:
在这里插入图片描述

在这里插入图片描述


判断一个路径下文件的属性:stat
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
同时也需要注意,这个文件无论是那个所属组,只要具备可执行都是可执行程序,都需要进行特殊处理,判断可执行需要&来判断

(m)指的是stat结构体中的st_mod字段

如果成功获得了http请求行路径的文件属性,说明这个路径是有效的。

函数执行成功返回1,失败返回-1

stat结构体中可以判断这个文件是文件夹还是文件,如果是文件夹就为HTTP请求路径添加上这个路径下的首页文件。
在这里插入图片描述

CGI

HTTP协议自带CGI机制,实现网站的可交互式,这种交互式需要使用CGI形式。注意CGI机制与CGI程序不同。
在这里插入图片描述
所以,当请求向服务器发送数据时或请求可执行程序时,需要CGI机制。这里先写出接口

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

2. 完整代码

#pragma once 


//已经存在套接字,线程通过套接字处理任务

#include<iostream>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include"Util.h"
#include<string>
#include<vector>
#include"Log.h"

#include<sstream>
#include<unordered_map>

#include<sys/stat.h>
#include<algorithm>

#define OK 200
#define NOTFOUND 404

#define WEB_ROOT "wwwroot"
#define HOME_PAGE "index.html"

//HTTP响应报文
class HttpResponse{
  public:
    std::string StatusLine_HTTP;//状态行
    std::vector<std::string>ResponHeads;//首部字段
    std::string ResponBlank;//空行
    std::string ResponBody;//正文

    int status_code=OK;//响应状态码
};

//HTTP请求报文
class HttpRequest{
  public:
    std::string RequestLine_HTTP;//请求行
    std::vector<std::string>RequestHeads;//首部字段
    std::string RequestBlank;//空行
    std::string RequestBody;//正文

    //解析完请求报文后的结果
    std::string Method;
    std::string URI;//Path?Pararm
    std::string Version;

    //保存解析首部字段的map
    std::unordered_map<std::string,std::string>Head_KVS;

    int Content_Lenth=0;

    //访问资源的路径
    std::string Path;

    //如果是GET方法通过URL上传的参数
    std::string Param;

    bool CGI=false;
};


//读取请求,分析请求,构建响应,基本IO通信,实现基本业务逻辑
class EndPoint{
  private:
    int sock;
    HttpRequest http_request;//http请求
    HttpResponse http_response;//http响应
  private:
    void GetHttpRequestLine(){//读请求行
      Util::ReadLine(sock,http_request.RequestLine_HTTP);//读取HTTP请求第一行
      http_request.RequestLine_HTTP.pop_back();
      ERRORLOG(INFO,http_request.RequestLine_HTTP);
    }

    void GetHttpRequstHeads(){//读取首部字段
      std::string line;
      while(true){
        Util::ReadLine(sock,line);
        if(line=="\n"){
          ERRORLOG(INFO,line);
          http_request.RequestBlank=line;
          break;
        }
        line.pop_back();//去掉每行的\n
        http_request.RequestHeads.push_back(line);
        ERRORLOG(INFO,line);
        line.clear();
      }
    }

    void AnalyQuestLine(){//解析请求行  方法 URL HTTP版本
      std::stringstream Str(http_request.RequestLine_HTTP);
      Str>>http_request.Method>>http_request.URI>>http_request.Version;
      //将方法统一转化成大写 Get->GET
      std::string& strtmp=http_request.Method;
      std::transform(strtmp.begin(),strtmp.end(),strtmp.begin(),::toupper);//写回strtmp首部
    }

    void AnalyuestHeadS(){
      std::string key;
      std::string value;
      for(auto&line:http_request.RequestHeads){
        if(Util::CutString(line,key,value,": ")){
          http_request.Head_KVS.insert(std::make_pair(key,value)); 

        }
        else{
          ERRORLOG(FATA,"AnalyuestHeadS error");
        }
      }
    }

    bool HaveHttpBody(){
      //判断是否是GET方法,GET方法没有正文
      std::string& Method=http_request.Method;
      if(Method=="POST"){
        std::unordered_map<std::string,std::string>::iterator iter=http_request.Head_KVS.find("Content-Lenth");
        if(iter!=http_request.Head_KVS.end()){
          http_request.Content_Lenth=atoi(iter->second.c_str());
          return true;
        }
      }
      return false;
    }

    void GetHttpBody(){
      if(HaveHttpBody()){
        int Content_Lenth=http_request.Content_Lenth;
        char ch=0;
        while(Content_Lenth>0){
          ssize_t size=recv(sock,&ch,1,0);
          if(size>0){
            http_request.RequestBody.push_back(ch);
            Content_Lenth--;
          }
          else{
            break;
          }
        }
      }
    }
  public:
    EndPoint(int _sock):sock(_sock){}

    void RecvQuest_HTTP(){//读取请求
      GetHttpRequestLine();
      GetHttpRequstHeads();
    }

    void AnalyQuest_HTTP(){//解析请求
      AnalyQuestLine();
      AnalyuestHeadS();
      GetHttpBody();
    }

    void MakeRespon_HTTP(){//构建响应
      //判断请求类型
      std::string tmpPath;
      if(http_request.Method!="GET"&&http_request.Method!="POST"){
        ERRORLOG(WARNING,"error request");
        http_response.status_code=NOTFOUND;
        goto END;
      }
      //如果是GET方法需要处理URL,看URL是否有参数
      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.Param,"?");
          http_request.CGI=true;
        }
        else{
          //不是通过GET传参数
          http_request.Path=http_request.URI;
        }
      }
      else if(http_request.Method=="POST"){
        //POST
        http_request.CGI=true;//需要CGI技术
      }
      //std::cout<<"Debug# URI: "<<http_request.URI<<" Path:"<<http_request.Path<<" Param:"<<http_request.Param<<std::endl;
      //拼接web根目录;
      tmpPath=http_request.Path;
      http_request.Path=WEB_ROOT;
      http_request.Path+=tmpPath;
      //std::cout<<"Debug# "<<http_request.Path<<std::endl;
      if(http_request.Path[http_request.Path.size()-1]=='/'){
        //默认访问index.html
        http_request.Path+=HOME_PAGE;
      }
      //std::cout<<"Debug# "<<http_request.Path<<std::endl;
      //判断路径是否合法
      struct stat stat_buff;
      if(stat(http_request.Path.c_str(),&stat_buff)==0){
        //资源存在,需要判断这个路径是否访问了路径下的某个资源,如果没有,直接将路径的默认网页响应回去
        if(S_ISDIR(stat_buff.st_mode)){
          //是目录
          http_request.Path+="/";
          http_request.Path+=HOME_PAGE;
          stat(http_request.Path.c_str(),&stat_buff)
        }
        if((stat_buff.st_mode &S_IXUSR)||(stat_buff.st_mode &S_IXGRP)||(stat_buff.st_mode& S_IXOTH)){
          //可执行文件,需要特殊处理
          http_request.CGI=true;
        }
      }
      else{
        //资源不存在状态码 404
        ERRORLOG(WARNING,http_request.Path+" Not Found!");
        http_response.status_code=NOTFOUND;
      }

      if(http_request.CGI==true){
        //ProceCGI();
      }
      else{
        //ProceNoCGI();//一定是GET方法,一定不带参,简单的文本网页返回
      }
END:
      return;
    }

    void SendRespon_HTTP(){//发送响应

    }

    ~EndPoint(){}
};

class Entry{//线程执行任务的入口
  public:
    static void*SolveQuest(void*_sock){
      ERRORLOG(INFO,"Processing Requests...");
      int sock=*(int*)_sock;
      delete(int*)_sock;
      //std::cout<<" Get a New Link: sock="<<sock<<std::endl;
      EndPoint* endpoint=new EndPoint(sock);
      endpoint->RecvQuest_HTTP();
      endpoint->AnalyQuest_HTTP();
      endpoint->MakeRespon_HTTP();
      endpoint->SendRespon_HTTP();
      delete endpoint;
      ERRORLOG(INFO,"Processing Request End!");
      return nullptr;
    }
};

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NUC_Dodamce

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值