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

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

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

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请求的字段,每个字段由字段名+冒号+空格+字段值组成。请求体主要包括发送到客户端的数据。其中请求行和请求字段之间是连续的,而请求字段与请求体之间会有两个空白行(\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<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<>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 
#include "define.h"
#include 

#define HTTP_RESPONSE_404 "404 Not Found

Not Found

The requested URL was not found on this server.

" 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->fileStartfileEndversion<<" "<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: "<fileStart<<"-"<<(m_request->fileEnd==0?contentLength:m_request->fileEnd)<<"/"<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"<

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

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


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

你可能感兴趣的:(服务器开发)