应用层协议实现系列(二)——HTTP服务器之http协议解析

来源:互联网 发布:windows 性能监视器 编辑:程序博客网 时间:2024/05/17 04:52

上一篇文章《仿nginx Http服务器的设计与实现(一)——多进程和多路IO的实现》中实现了一个仿照nginx的支持高并发的服务器,但只是实现了端口监听和数据接收,并没有实现对http协议的解析,下面就对如何解析http协议进行说明。

我们可以通过浏览器访问之前所搭建的http服务器,可以看到终端输出如下:

GET / HTTP/1.1Host: 127.0.0.1:8080Connection: keep-aliveCache-Control: max-age=0Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8User-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.36Accept-Encoding: gzip,deflate,sdchAccept-Language: zh-CN,zh;q=0.8

参考一些网上的资料可以知道,http协议主要有三部分组成,即请求行、若干请求字段、请求体。请求行主要包括所使用的http方法,访问的路径以及http的版本。请求字段主要包括若干个详细说明本次http请求的字段,每个字段由字段名+冒号+空格+字段值组成。请求体主要包括发送到客户端的数据。其中请求行和请求字段之间是连续的,而请求字段与请求体之间会有两个空白行(\r\n)分隔。

在明确了这些内容之后,我们就可以开始对接收到的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] = '\0';    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 \r            line[strlen(line)-1] = '\0';            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<<"\r\n";    //time    responseStream<<"Date: "<<getStringFromTime(time(0))<<"\r\n";        //server name    responseStream<<"Server: "<<SERVER_NAME<<"\r\n";        //keep alive    responseStream<<"Connection: "<<m_request->connection<<"\r\n";        //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<<"\r\n";    }        //last modified    if (!m_sendFilePath.empty()) {        responseStream<<"Last-Modified: "<<getStringFromTime(fileModifiedTime(m_sendFilePath.c_str()))<<"\r\n";                responseStream<<"Accept-Ranges: "<<"bytes"<<"\r\n";    }        //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<<"\r\n";                break;            }        }    }        //other    switch (m_statusCode) {        case TS_HTTP_STATUS_UNAUTHORIZED:            responseStream<<"WWW-Authenticate: Basic realm=\"zhaoxy.com\"\r\n";            break;        case TS_HTTP_STATUS_FOUND:            responseStream<<"Location: /index.html\r\n";            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())<<"\r\n";            break;        default:            break;    }        //seperator    responseStream<<"\r\n";        //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字段。具体的逻辑不作详细说明,有疑问的可以留言。

该Http服务器的代码已经上传到GitHub上,大家可以直接下载。


如果大家觉得对自己有帮助的话,还希望能帮顶一下,谢谢:)
个人博客:http://blog.csdn.net/zhaoxy2850
本文地址:http://blog.csdn.net/zhaoxy_thu/article/details/24716221
转载请注明出处,谢谢!

6 0
原创粉丝点击