WebServer代码解读(5)【处理HTTP请求】【响应POST/GET请求】

1 - 处理HTTP请求

这里只处理两种简单的HTTP请求,POST和GET

首先来了解一下GET与POST

  • GET是想获取server数据,将请求的数据添加到URL中,以?分割URL和传输数据,参数值之间以&相连,因此GET不安全。GET产生一个TCP数据包,浏览器将HTTP header和data一起发送给server,server响应状态码200(请求正常处理完毕)(返回数据)
  • POST是想修改server数据,将数据放在HTTP的包体内(request body)。POST产生2个TCP数据包,浏览器先发送header,server响应状态码100(请求正在处理)后,浏览器才会发送data,然后server返回状态码200

下面介绍一下HTTP响应的基本知识:

  1. 状态码(Status Code):描述了响应的状态。可以用来检查是否成功的完成了请求。请求失败的情况下,状态码可用来找出失败的原因。
  2. HTTP头部(HTTP Header):它们包含了更多关于响应的信息。比如:头部可以指定认为响应过期的过期日期,或者是指定用来给用户安全的传输实体内容的编码格式。
  3. 主体(Body):它包含了响应的内容。它可以包含HTML代码,图片,等等。主体是由传输在HTTP消息中紧跟在头部后面的数据字节组成的。

img

参考:HTTP响应的结构

1-1 处理POST请求

int requestData::analysisRequest()
{
    
    
    if (method == METHOD_POST)
    {
    
    
        //get content
        char header[MAX_BUFF];//响应header信息:包含响应行、响应头
        sprintf(header, "HTTP/1.1 %d %s\r\n", 200, "OK");//向响应行中写入数据,200:请求成功
        //server根据请求头获取client信息
        // 查看是否需要持久连接。(HTTP 1.1默认进行持久连接)
        if(headers.find("Connection") != headers.end() && headers["Connection"] == "keep-alive")
        {
    
    
            keep_alive = true;
            sprintf(header, "%sConnection: keep-alive\r\n", header);
            sprintf(header, "%sKeep-Alive: timeout=%d\r\n", header, EPOLL_WAIT_TIME);
        }
        //cout << "content=" << content << endl;
        // test char*

        //响应体
        char *send_content = "I have receiced this.";
        //向header中写入body长度
        sprintf(header, "%sContent-length: %zu\r\n", header, strlen(send_content));
        sprintf(header, "%s\r\n", header);

        //发送header
        size_t send_len = (size_t)writen(fd, header, strlen(header));
        if(send_len != strlen(header))
        {
    
    
            perror("Send header failed");
            return ANALYSIS_ERROR;
        }
        //发送响应体
        send_len = (size_t)writen(fd, send_content, strlen(send_content));
        if(send_len != strlen(send_content))
        {
    
    
            perror("Send content failed");
            return ANALYSIS_ERROR;
        }
        cout << "content size ==" << content.size() << endl;
        vector<char> data(content.begin(), content.end());
        //opencv
        Mat test = imdecode(data, CV_LOAD_IMAGE_ANYDEPTH|CV_LOAD_IMAGE_ANYCOLOR);
        imwrite("receive.bmp", test);
        return ANALYSIS_SUCCESS;
    }
    //处理其他请求
}

处理POST请求的步骤在代码中已经注释,关于opencv再来解释一下:

Mat类 (Matrix的缩写) 是OpenCV用于处理图像而引入的一个封装类

imencodeimdecode,用于网络传输图片,使用流程为:(1)程序首先读入一个图片。然后encode,之后把encode后的内容写入文件(实际应用可以发送到网络)。(2)从文件读取encode的内容。然后解码decode。转换为mat格式,显示出来。

opencv3中的imwrite函数是用来输出图像到文件,相当于保存图片

关于向socket中写数据,用到了一个writen函数,该函数封装write函数,一直向socket中写数据,直到写完所有数据,过程中可能多次调用write函数

///封装write函数,一直向socket中写数据,直到写完所有数据,过程中可能多次调用write函数
ssize_t writen(int fd, void *buff, size_t n)
{
    
    
    size_t nleft = n;
    ssize_t nwritten = 0;
    ssize_t writeSum = 0;
    char *ptr = (char*)buff;
    while (nleft > 0)
    {
    
    
        if ((nwritten = write(fd, ptr, nleft)) <= 0)
        {
    
    
            if (nwritten < 0)
            {
    
    
                if (errno == EINTR || errno == EAGAIN)
                {
    
    
                    nwritten = 0;
                    continue;
                }
                else
                    return -1;
            }
        }
        writeSum += nwritten;
        nleft -= nwritten;
        ptr += nwritten;
    }
    return writeSum;
}

1-2 处理GET请求

因为GET请求用来从server获取数据,因此server需要解析GET请求的具体资源类型(以MIME区分)

int requestData::analysisRequest()
{
    
    
//处理POST请求
else if (method == METHOD_GET)
    {
    
    
        char header[MAX_BUFF];
        sprintf(header, "HTTP/1.1 %d %s\r\n", 200, "OK");
        if(headers.find("Connection") != headers.end() && headers["Connection"] == "keep-alive")
        {
    
    
            keep_alive = true;
            sprintf(header, "%sConnection: keep-alive\r\n", header);
            sprintf(header, "%sKeep-Alive: timeout=%d\r\n", header, EPOLL_WAIT_TIME);
        }
        int dot_pos = file_name.find('.');
        const char* filetype;//文件类型,以文件后缀区分
        if (dot_pos < 0) 
            filetype = MimeType::getMime("default").c_str();
        else
            filetype = MimeType::getMime(file_name.substr(dot_pos)).c_str();
        struct stat sbuf;
        if (stat(file_name.c_str(), &sbuf) < 0)
        {
    
    
            handleError(fd, 404, "Not Found!");
            return ANALYSIS_ERROR;
        }
        //将包体信息写入首部
        sprintf(header, "%sContent-type: %s\r\n", header, filetype);
        // 通过Content-length返回文件大小
        sprintf(header, "%sContent-length: %ld\r\n", header, sbuf.st_size);

        sprintf(header, "%s\r\n", header);
        //发送响应报文首部
        size_t send_len = (size_t)writen(fd, header, strlen(header));
        if(send_len != strlen(header))
        {
    
    
            perror("Send header failed");
            return ANALYSIS_ERROR;
        }
        //取出GET方法请求的文件
        int src_fd = open(file_name.c_str(), O_RDONLY, 0);
        char *src_addr = static_cast<char*>(mmap(NULL, sbuf.st_size, PROT_READ, MAP_PRIVATE, src_fd, 0));
        close(src_fd);
    
        // 发送文件并校验完整性
        send_len = writen(fd, src_addr, sbuf.st_size);
        if(send_len != sbuf.st_size)
        {
    
    
            perror("Send file failed");
            return ANALYSIS_ERROR;
        }
        munmap(src_addr, sbuf.st_size);
        return ANALYSIS_SUCCESS;
    }
    else
        return ANALYSIS_ERROR;
}

介绍一下MIME:

MIME(Multipurpose Internet Mail Extensions)多用途互联网邮件扩展类型。是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式。

常见的MIME类型有:

文件后缀 MIME类型
普通文本 .txt text/plain
超文本标记语言文本 .html text/html
PDF文档 .pdf application/pdf
PNG图像 .png image/png

使用MimeType::getMime函数根据传入的文件后缀获取对应的MIME类型

std::string MimeType::getMime(const std::string &suffix)
{
    
    
    if (mime.size() == 0)
    {
    
    
        pthread_mutex_lock(&lock);
        if (mime.size() == 0)
        {
    
    
            mime[".html"] = "text/html";
            mime[".avi"] = "video/x-msvideo";
            mime[".bmp"] = "image/bmp";
            mime[".c"] = "text/plain";
            mime[".doc"] = "application/msword";
            mime[".gif"] = "image/gif";
            mime[".gz"] = "application/x-gzip";
            mime[".htm"] = "text/html";
            mime[".ico"] = "application/x-ico";
            mime[".jpg"] = "image/jpeg";
            mime[".png"] = "image/png";
            mime[".txt"] = "text/plain";
            mime[".mp3"] = "audio/mp3";
            mime["default"] = "text/html";
        }
        pthread_mutex_unlock(&lock);
    }
    if (mime.find(suffix) == mime.end())
        return mime["default"];
    else
        return mime[suffix];
}

stat函数声明为:

///通过文件名filename获取文件信息,并保存在buf所指的结构体stat中
///执行成功则返回0,失败返回-1,错误代码存于errno
int stat(const char *file_name, struct stat *buf);

如果server没有GET请求的资源的话,使用handleError函数处理,并返回状态码404(Not Found)表示server没有该资源

void requestData::handleError(int fd, int err_num, string short_msg)
{
    
    
    short_msg = " " + short_msg;
    char send_buff[MAX_BUFF];
    string body_buff, header_buff;
    body_buff += "<html><title>TKeed Error</title>";
    body_buff += "<body bgcolor=\"ffffff\">";
    body_buff += to_string(err_num) + short_msg;
    body_buff += "<hr><em> LinYa's Web Server</em>\n</body></html>";

    header_buff += "HTTP/1.1 " + to_string(err_num) + short_msg + "\r\n";
    header_buff += "Content-type: text/html\r\n";
    header_buff += "Connection: close\r\n";
    header_buff += "Content-length: " + to_string(body_buff.size()) + "\r\n";
    header_buff += "\r\n";
    sprintf(send_buff, "%s", header_buff.c_str());
    writen(fd, send_buff, strlen(send_buff));
    sprintf(send_buff, "%s", body_buff.c_str());
    writen(fd, send_buff, strlen(send_buff));
}

Guess you like

Origin blog.csdn.net/weixin_44484715/article/details/117637153