生成HTTP响应报文

上文解析HTTP请求报文(GET、POST),介绍了计划实现的WEB服务器的五个功能:展示主页(GET)用户登录(POST)用户注册(POST)获取图片(GET)获取视频(GET)。

通过正则表达式与状态机解析HTTP请求报文后,就获得了客户端请求资源的路径(path),回送响应报文的过程就是:根据请求写出相应的HTTP响应头,然后再将path上的文件置于响应体,通过socket发往客户端

首先是制作HTTP响应头

HTTP/2 200 OK\r\n
last-modified: Sat, 24 Dec 2022 10:30:31 GMT\r\n
content-encoding: br\r\n
content-type: application/javascript\r\n
Connection: keep-alive\r\n
server: tencent-cos\r\n
cache-control: max-age=31536000\r\n
content-length: 96194\r\n
\r\n
  • 第一行是状态行,由协议(HTTP/2)、状态码(200)、状态码的原因短语(OK)三部分构成

  • 其余是响应头,由头部字段名称(Connection) :值(keep-alive) 构成

  • 最后响应头与响应体之间还有一行\r\n的空行

本文实现的响应头,包含200、400、403、404四种状态码及其对应的原因短语

响应头部分有Connection、Content-type、Content-length三个字段

生成状态码的方法:

利用int stat(const char *path, struct stat *buf) 函数

//按location路径无法搜到文件,或搜到的是目录文件(404找不到)
if(stat(location.c_str(),&mmFileStat)<0||S_ISDIR(mmFileStat.st_mode))
{
    code=404;
}
else if (!(mmFileStat.st_mode&S_IROTH))//其他用户不可读(403禁止访问)
{
    code=403;
}

获取Content-type对应值的方法:

提取path的后缀,然后利用哈希表SUFFIX_TYPE,得到对应值(例如:文件后缀为html,则对应值应为text/html )

std::string HttpResponse::getFileType()
{
    size_t idx=path.find_last_of('.');
    if(idx==std::string::npos)
    {
        return "text/plain";
    }
    std::string suffix=path.substr(idx);
    if(SUFFIX_TYPE.count(suffix))
    {
        return SUFFIX_TYPE.find(suffix)->second;
    }
    return "text/plain";
} 

其次是制作响应体

响应体内应为path对应的文件,为了提高服务器从磁盘中读取响应文件的速度,采用mmap方法,建立私有的可读内存映射。

void* mmRet=mmap(0,mmFileStat.st_size,PROT_READ,MAP_PRIVATE,srcFd,0);

mmFileStat.st_size的值就是Content-length的对应值

HttpResponse类结构 httpresponse.h

#ifndef HTTPRESPONSE_H
#define HTTPRESPONSE_H

#include <unordered_map>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>

#include "../log/log.h"
#include "../buffer/buffer.h"

class HttpResponse
{
public:
    HttpResponse();
    ~HttpResponse();
    void init(const std::string& srcDir_,const std::string& path_,bool isKeepAlive_, int code_);
    void makeResponse(Buffer& buffer);
    void unmapFile();
    char* getfile();
    size_t getFileLen();

private:
    void addStateLine(Buffer& buffer);
    void addHeader(Buffer& buffer);
    void addContent(Buffer& buffer);
    
    void errorHtml();
    std::string getFileType();

    int code;
    bool isKeepAlive;
    std::string path;
    std::string srcDir;
    char* mmFile;
    struct stat mmFileStat;

    static const std::unordered_map<std::string,std::string> SUFFIX_TYPE;
    static const std::unordered_map<int,std::string> CODE_STATUS;
    static const std::unordered_map<int,std::string> CODE_PATH;
};


#endif // !HTTPRESPONSE_H

HttpResponse类实现 httpresponse.cpp

#include "httpresponse.h"

const std::unordered_map<std::string,std::string> HttpResponse::SUFFIX_TYPE=
{
    { ".html",  "text/html" },
    { ".xml",   "text/xml" },
    { ".xhtml", "application/xhtml+xml" },
    { ".txt",   "text/plain" },
    { ".rtf",   "application/rtf" },
    { ".pdf",   "application/pdf" },
    { ".word",  "application/nsword" },
    { ".png",   "image/png" },
    { ".gif",   "image/gif" },
    { ".jpg",   "image/jpeg" },
    { ".jpeg",  "image/jpeg" },
    { ".au",    "audio/basic" },
    { ".mpeg",  "video/mpeg" },
    { ".mpg",   "video/mpeg" },
    { ".avi",   "video/x-msvideo" },
    { ".mp4",   "video/mp4" },
    { ".gz",    "application/x-gzip" },
    { ".tar",   "application/x-tar" },
    { ".css",   "text/css "},
    { ".js",    "text/javascript "}
};
const std::unordered_map<int,std::string> HttpResponse::CODE_STATUS=
{
    { 200, "OK" },
    { 400, "Bad Request" },
    { 403, "Forbidden" },
    { 404, "Not Found" },
};
const std::unordered_map<int,std::string> HttpResponse::CODE_PATH=
{
    { 400, "/400.html" },
    { 403, "/403.html" },
    { 404, "/404.html" },
};

HttpResponse::HttpResponse()
{
    code=-1;
    isKeepAlive=false;
    mmFile=nullptr;
    mmFileStat={0};
    Log::getInstance()->init();
}

HttpResponse::~HttpResponse()
{
    unmapFile();
}

void HttpResponse::unmapFile()//关闭内存映射
{
    if(mmFile)
    {
        munmap(mmFile,mmFileStat.st_size);
        mmFile=nullptr;
    }
}

void HttpResponse::init(const std::string& srcDir_,const std::string& path_, bool isKeepAlive_, int code_)
{
    assert(srcDir_!="");
    if(mmFile)
    {
        unmapFile();
    }
    srcDir=srcDir_;
    code=code_;
    path=path_;
    isKeepAlive=isKeepAlive_;
}

void HttpResponse::makeResponse(Buffer& buffer)
{
    std::string location=srcDir+path;
    //按location路径无法搜到文件,或搜到的是目录文件(404找不到)
    if(stat(location.c_str(),&mmFileStat)<0||S_ISDIR(mmFileStat.st_mode))
    {
        code=404;
    }
    else if (!(mmFileStat.st_mode&S_IROTH))//其他用户不可读(403禁止访问)
    {
        code=403;
    }
    else if(code==-1)
    {
        code=400;
    }
    errorHtml();//如果状态码是CODE_PATH中的400、403、404,则更改回送资源的路径path
    addStateLine(buffer);//添加状态行
    addHeader(buffer);//添加响应头
    addContent(buffer);//添加响应体
}

void HttpResponse::errorHtml()//如果状态码是CODE_PATH中的400、403、404,则更改回送资源的路径path
{
    if(CODE_PATH.count(code))
    {
        path=CODE_PATH.find(code)->second;
        std::string location=srcDir+path;
        stat(location.c_str(),&mmFileStat);
    }
}

void HttpResponse::addStateLine(Buffer& buffer)//添加状态行
{
    std::string status;
    if(CODE_STATUS.count(code))
    {
        status=CODE_STATUS.find(code)->second;
    }
    else
    {
        code=400;
        status=CODE_STATUS.find(400)->second;
    }
    buffer.append("HTTP/1.1 "+std::to_string(code)+" "+status+"\r\n");
}

void HttpResponse::addHeader(Buffer& buffer)//添加响应头
{
    buffer.append("Connection: ");
    if(isKeepAlive)
    {
        buffer.append("keep-alive\r\n");
    }
    else
    {
        buffer.append("close\r\n");
    }
    buffer.append("Content-type: "+getFileType()+"\r\n");
}

void HttpResponse::addContent(Buffer& buffer)//添加响应体
{
    std::string location=srcDir+path;
    int srcFd=open(location.c_str(),O_RDONLY);
    if(srcFd<0)
    {
        LOG_ERROR("%s","Response File NotFind!");
        return ;
    }
    //建立私有的可读内存映射,提高读取磁盘文件的速率
    void* mmRet=mmap(0,mmFileStat.st_size,PROT_READ,MAP_PRIVATE,srcFd,0);
    if(mmRet==MAP_FAILED)
    {
        LOG_ERROR("%s","Response File NotFind!");
        return ;
    }
    mmFile=(char*)mmRet;
    close(srcFd);
    buffer.append("Content-length: "+std::to_string(mmFileStat.st_size)+"\r\n\r\n");
}

std::string HttpResponse::getFileType()//获取请求资源的类型
{
    size_t idx=path.find_last_of('.');
    if(idx==std::string::npos)
    {
        return "text/plain";
    }
    std::string suffix=path.substr(idx);
    if(SUFFIX_TYPE.count(suffix))
    {
        return SUFFIX_TYPE.find(suffix)->second;
    }
    return "text/plain";
} 

char* HttpResponse::getfile()//返回映射文件的句柄
{
    return mmFile;
}

size_t HttpResponse::getFileLen()//返回映射文件的大小
{
    return mmFileStat.st_size;
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值