• 应用层协议系列(两)——HTTPserver之http协议分析


    上一篇文章《抄nginx Httpserver设计与实现(一)——多进程和多通道IO现》中实现了一个仿照nginx的支持高并发的server。但仅仅是实现了port监听和数据接收。并没有实现对http协议的解析,以下就对怎样解析http协议进行说明。

    我们能够通过浏览器訪问之前所搭建的httpserver,能够看到终端输出例如以下:

    GET / HTTP/1.1
    Host: 127.0.0.1:8080
    Connection: keep-alive
    Cache-Control: max-age=0
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
    User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.131 Safari/537.36
    Accept-Encoding: gzip,deflate,sdch
    Accept-Language: zh-CN,zh;q=0.8

    參考一些网上的资料能够知道,http协议主要有三部分组成,即请求行、若干请求字段、请求体。请求行主要包含所使用的http方法,訪问的路径以及http的版本号。请求字段主要包含若干个具体说明本次http请求的字段。每一个字段由字段名+冒号+空格+字段值组成。

    请求体主要包含发送到client的数据。

    当中请求行和请求字段之间是连续的,而请求字段与请求体之间会有两个空白行( )分隔。

    在明白了这些内容之后,我们就能够開始对接收到的http请求进行解析了。本文将使用两个类,CHttpRequest和CHttpResponse来实现这一功能。

    以下首先改动上一篇文章中的

    handleRequest方法:

    //处理http请求
    bool handleRequest(int connFd) {
        if (connFd<=0) return false;
        //读取缓存
        char buff[4096];
        //读取http header
        int len = (int)recv(connFd, buff, sizeof(buff), 0);
        if (len<=0) {
            return false;
        }
        buff[len] = '';
        std::cout<<buff<<std::endl;
    
        CHttpRequest *httpRequest = new CHttpRequest();
        httpRequest->handleRequest(buff);
        CHttpResponse *httpResponse = new CHttpResponse(httpRequest);
        bool result = httpResponse->response(connFd);
        //返回是否须要中断连接
        std::string transformConnection(httpRequest->connection);
        std::transform(transformConnection.begin(), transformConnection.end(), transformConnection.begin(), ::tolower);
        return transformConnection == "Keep-Alive" && result;
    }

    该代码中採用了一个长度为4096的缓冲区接收http头,接收完毕之后,调用CHttpRequest进行解析。

    以下来看看CHttpRequest的代码:

    #include "CHttpRequest.h"
    #include "define.h"
    
    using namespace std;
    
    CHttpRequest::CHttpRequest() {
        connection = "Close";
        modifiedTime = "";
        fileStart = 0;
        fileEnd = 0;
        
        fieldMap[TS_HTTP_HEADER_CONNECTION] = &CHttpRequest::handleConnection;
        fieldMap[TS_HTTP_HEADER_AUTHORIZATION] = &CHttpRequest::handleAuthorization;
        fieldMap[TS_HTTP_HEADER_RANGE] = &CHttpRequest::handleRange;
        fieldMap[TS_HTTP_HEADER_IF_MOD_SINCE] = &CHttpRequest::handleIfModSince;
    }
    
    void CHttpRequest::handleRequest(char *header) {
        stringstream stream;
        stream<<header;
        
        int count = 0;
        while (1) {
            if (stream.eof()) {
                break;
            }
            char line[1024];
            stream.getline(line, sizeof(line));
            if (strcmp(line, "")==0) {
                continue;
            }
            
            stringstream lineStream;
            lineStream<<line;
            //first line
            if (count == 0) {
                lineStream>>method;
                lineStream>>path;
                lineStream>>version;
            }else {
                string fieldName;
                lineStream>>fieldName;
                //remove 
                line[strlen(line)-1] = '';
                void(CHttpRequest::*func)(char*) = fieldMap[fieldName];
                if (func!=NULL) {
                    (this->*func)(line+fieldName.length()+1);
                }
            }
            count++;
        }
    }
    
    void CHttpRequest::handleConnection(char *field) {
        if (ENABLE_KEEP_ALIVE) {
            connection = string(field);
        }
    }
    
    void CHttpRequest::handleAuthorization(char *field) {
        char authName[10], authInfo[256];
        sscanf(field, "%s %s", authName, authInfo);
        authorize = string(authInfo);
    }
    
    void CHttpRequest::handleRange(char *field) {
        if (strstr(field, "bytes=")==field) {
            char *start = strtok(field+strlen("bytes="), "-");
            fileStart = start==NULL?

    0:atol(start); char *end = strtok(NULL, "-"); fileEnd = end==NULL?0:atol(end); } } void CHttpRequest::handleIfModSince(char *field) { modifiedTime = string(field); }


    为了保证http解析的效率,本文採用了与nginx中类似的做法,将字段名与解析函数放到了map中(nginx中使用的是hash表,在这里简化为map)。

    在解析完毕之后,调用CHttpResponse构造响应。CHttpResponse代码例如以下:

    #include "CHttpResponse.h"
    #include "CHttpRequest.h"
    #include <sys/socket.h>
    #include "define.h"
    #include <string.h>
    
    #define HTTP_RESPONSE_404 "<html><head><title>404 Not Found</title></head><body><h1>Not Found</h1><p>The requested URL was not found on this server.</p></body></html>"
    
    std::string getStringFromTime(time_t time) {
        char timeBuff[64];
        struct tm tm = *gmtime(&time);
        strftime(timeBuff, sizeof timeBuff, "%a, %d %b %Y %H:%M:%S %Z", &tm);
        return std::string(timeBuff);
    }
    
    CHttpResponse::CHttpResponse(CHttpRequest *request) {
        m_request = request;
        if (m_request->method.compare(TS_HTTP_METHOD_GET_S)==0 || m_request->method.compare(TS_HTTP_METHOD_HEAD_S)==0) {
            std::string path = ROOT_PATH;
            if (m_request->path.compare("/")==0) {
                path += ROOT_HTML;
            }else {
                path += m_request->path;
            }
            
            m_statusCode = 0;
            //if file exist
            if (isFileExist(path.c_str())) {
                //if receive modified time
                if (!m_request->modifiedTime.empty()) {
                    time_t time = fileModifiedTime(path.c_str());
                    if (getStringFromTime(time) == m_request->modifiedTime) {
                        m_statusCode = TS_HTTP_STATUS_NOT_MODIFIED;
                        m_statusMsg = TS_HTTP_STATUS_NOT_MODIFIED_S;
                    }
                }
                //if file modified
                if (m_statusCode == 0) {
                    if (m_request->fileStart || m_request->fileEnd) {
                        long long fileSize = getFileSize(path.c_str());
                        //if request range satisfied
                        if (m_request->fileStart<fileSize && m_request->fileEnd<fileSize) {
                            m_statusCode = TS_HTTP_STATUS_PARTIAL_CONTENT;
                            m_statusMsg = TS_HTTP_STATUS_PARTIAL_CONTENT_S;
                            m_sendFilePath = path;
                        }else {
                            m_statusCode = TS_HTTP_STATUS_REQUEST_RANGE_NOT_SATISFIABLE;
                            m_statusMsg = TS_HTTP_STATUS_REQUEST_RANGE_NOT_SATISFIABLE_S;
                        }
                    }else {
                        m_statusCode = TS_HTTP_STATUS_OK;
                        m_statusMsg = TS_HTTP_STATUS_OK_S;
                        m_sendFilePath = path;
                    }
                }
            } else {
                m_statusCode = TS_HTTP_STATUS_NOT_FOUND;
                m_statusMsg = TS_HTTP_STATUS_NOT_FOUND_S;
                m_sendStr = HTTP_RESPONSE_404;
            }
        }
    }
    
    bool CHttpResponse::response(int connFd) {
        bool result = true;
        std::stringstream responseStream;
        
        responseStream<<m_request->version<<" "<<m_statusMsg<<"
    ";
        //time
        responseStream<<"Date: "<<getStringFromTime(time(0))<<"
    ";
        
        //server name
        responseStream<<"Server: "<<SERVER_NAME<<"
    ";
        
        //keep alive
        responseStream<<"Connection: "<<m_request->connection<<"
    ";
        
        //content length
        long long contentLength = 0;
        //if file exist
        if (!m_sendFilePath.empty()) {
            //if define file end
            if (m_request->fileEnd) {
                contentLength = m_request->fileEnd - m_request->fileStart + 1;
            }
            //if define file start
            else if (m_request->fileStart) {
                contentLength = getFileSize(m_sendFilePath.c_str()) - m_request->fileStart + 1;
            }
            //if undefine start or end
            else {
                contentLength = getFileSize(m_sendFilePath.c_str());
            }
        } else if (!m_sendStr.empty()) {
            contentLength = m_sendStr.length();
        }
        if (contentLength) {
            responseStream<<"Content-Length: "<<contentLength<<"
    ";
        }
        
        //last modified
        if (!m_sendFilePath.empty()) {
            responseStream<<"Last-Modified: "<<getStringFromTime(fileModifiedTime(m_sendFilePath.c_str()))<<"
    ";
            
            responseStream<<"Accept-Ranges: "<<"bytes"<<"
    ";
        }
        
        //content type
        if (!m_sendFilePath.empty()) {
            char path[256];
            strcpy(path, m_sendFilePath.c_str());
            char *ext = strtok(path, ".");
            char *lastExt = ext;
            while (ext!=NULL) {
                ext = strtok(NULL, ".");
                if (ext) lastExt = ext;
            }
            for (int i=0; i<38; i++) {
                if (strcmp(mmt[i].ext, lastExt)==0) {
                    responseStream<<"Content-Type: "<<mmt[i].type<<"
    ";
                    break;
                }
            }
        }
        
        //other
        switch (m_statusCode) {
            case TS_HTTP_STATUS_UNAUTHORIZED:
                responseStream<<"WWW-Authenticate: Basic realm="zhaoxy.com"
    ";
                break;
            case TS_HTTP_STATUS_FOUND:
                responseStream<<"Location: /index.html
    ";
                break;
            case TS_HTTP_STATUS_PARTIAL_CONTENT:
                responseStream<<"Content-Range: "<<"bytes "<<m_request->fileStart<<"-"<<(m_request->fileEnd==0?

    contentLength:m_request->fileEnd)<<"/"<<getFileSize(m_sendFilePath.c_str())<<" "; break; default: break; } //seperator responseStream<<" "; //send response header std::string responseStr = responseStream.str(); std::cout<<responseStr<<std::endl; send(connFd, responseStr.c_str(), responseStr.length(), 0); //content //if not head method if (m_request->method.compare(TS_HTTP_METHOD_HEAD_S)!=0) { if (!m_sendFilePath.empty()) { std::ifstream file(m_sendFilePath); file.seekg(m_request->fileStart, std::ifstream::beg); while(file.tellg() != -1) { char *p = new char[1024]; bzero(p, 1024); file.read(p, 1024); int n = (int)send(connFd, p, 1024, 0); if (n < 0) { std::cout<<"ERROR writing to socket"<<std::endl; result = false; break; } delete p; } file.close(); }else { send(connFd, m_sendStr.c_str(), m_sendStr.length(), 0); } } return result; }


    该代码支持断点续传、last modified和authorization字段。具体的逻辑不作具体说明,有疑问的能够留言。

    该Httpserver的代码已经上传到GitHub上,大家能够直接下载


    假设大家认为对自己有帮助的话,还希望能帮顶一下,谢谢:)
    转载请注明出处。谢谢!


    版权声明:本文博主原创文章,博客,未经同意不得转载。

  • 相关阅读:
    【LeetCode】006 ZigZag Conversion
    【LeetCode】009 Palindrome Number
    【LeetCode】008 String to Integer (atoi)
    【LeetCode】012 013 Roman Integer
    react-native 入门资源合集
    Thread和ExecutorService(一)
    DrawerLayout、CoordinatorLayout、CollapsingToolbarLayout的使用--AndroidSupportDesign练手
    Valid Number
    When Is Cheryl's Birthday
    【笔试】——常用运算符
  • 原文地址:https://www.cnblogs.com/blfshiye/p/4825364.html
Copyright © 2020-2023  润新知