仿nginx 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<<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

转载请注明出处,谢谢!

时间: 2024-08-26 11:12:39

仿nginx Http服务器的设计与实现(二)——http协议解析的相关文章

构建高效安全的Nginx Web服务器

一 一.为什么选择Nginx搭建Web服务器 Apache和Nginx是目前使用最火的两种Web服务器,Apache出现比Nginx早. Apache HTTP Server(简称Apache)是世界使用排名第一的Web服务器软件, 音译为阿帕奇,是Apache软件基金会的一个开放源码Web服务器, 可以运行几乎所有的计算机平台,其次开放的API接口, 任何组织和个人都可以在它上面扩展和增加各种需要功能,达到为自己量身定制的功能. Nginx("engine x")是一个高性能的HTT

应用层协议实现系列(一)——HTTPserver之仿nginx多进程和多路IO的实现

近期在尝试自己写一个Httpserver,在粗略研究了nginx的代码之后,决定仿照nginx中的部分设计自己实现一个高并发的HTTPserver,在这里分享给大家. 眼下使用的较多的Httpserver就是apache和nginx,apache的主要特点就是稳定,而nginx的主要特点是承载的并发量高.在这里从实现原理上做一个分析: apache採用的是多进程server模型,即server每监听到一个连接时,会创建一个新的进程去处理连接,进程与进程之间是独立的,因此就算进程在处理连接的过程中

Nginx+Tomcat服务器负载均衡实践方案

1.    为何选择Nginx+Tomcat做负载均衡? 1.1. Nginx是什么? Nginx(发音同 engine x)是一款轻量级的Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,并在一个BSD-like 协议下发行.由俄罗斯的程序设计师Igor Sysoev所开发,供俄国大型的入口网站及搜索引擎Rambler(俄文:Рамблер)使用.其特点是占有内存少,并发能力强,事实上nginx的并发能力确实在同类型的网页服务器中表现较好,中国大陆使用nginx网站用户

23、生鲜电商平台-服务器部署设计与架构

补充说明:Java开源生鲜电商平台-服务器部署设计与架构,指的是通过服务器正式上线整个项目,进行正式的运营. 回顾整个章节,我们涉及到以下几个方面: 1. 买家端 2. 卖家端. 3. 销售端 4. 配送端. 5.系统运营端. 6.公司网址 目前根据业务的情况,采购了阿里云服务器,由于是创业,我身上没多少钱,只采购了一台阿里云.(具体配置如下与域名规划如下) 公司网址: http://www.netcai.com 买家端:  http://buyer.netcai.com 卖家端:  http:

高性能后台服务器架构设计

ref:https://www.cnblogs.com/lidabo/p/6627642.html 如何设计高性能的大型网站系统?在移动互联网时代,客户端应用开发本身,并不是体验的决胜之处,真正对团队挑战的地方,还在于后端,无论是承压能力,还是安全性等方面,如果这些地方过不了关,整个应用的基础是不扎实的. 提高服务器性能最简单粗暴的方式,就是增加机器和升级硬件配置.虽然现在的硬件越来越便宜,但是一味地通过增加机器来解决并发量的增长,成本是非常高昂的.结合技术优化方案,才是更有效的解决方法. 一.

(转)Nginx图片服务器

本文转至博客http://wenxin2009.iteye.com/blog/2117079 Nginx搭建图片服务器 Nginx下载地址:http://nginx.org/en/download.html 本例下载的是window版本nginx-1.6.1 以下是我本机操作说明: 下载完后,解压,并把它放到D:\tools\nginx-1.6.1,双击nginx.exe即可运行nginx.可通http://127.0.0.1访问到nginx欢迎界面,如下  也可在cmd中通过命令进行启停启动n

20步打造最安全的Nginx Web服务器

转自:http://www.open-open.com/solution/view/1319455592515 Nginx是一个轻量级的,高性能的Web服务器以及反向代理和邮箱 (IMAP/POP3)代理服务器.它运行在UNIX,GNU /linux,BSD 各种版本,Mac OS X,Solaris和Windows.根据调查统计,6%的网站使用Nginx Web服务器.Nginx是少数能处理C10K问题的服务器之一.跟传统的服务器不同,Nginx不依赖线程来处理请求.相反,它使用了更多的可扩展

实现最安全的Nginx Web服务器

原文地址:http://www.phpthinking.com/archives/414 Nginx是一个轻量级的,高性能的Web服务器以及反向代理和邮箱(IMAP/POP3)代理服务器.它运行在UNIX,GNU/Linux,BSD各种版本,Mac OS X,Solaris和Windows.根据调查统计,6%的网站使用Nginx Web服务器.Nginx是少数能处理C10K问题的服务器之一.跟传统的服务器不同,Nginx不依赖线程来处理请求.相反,它使用了更多的可扩展的事件驱动(异步)架构.Ng

Nginx网站服务器搭建实例

Nginx是一款开源的高性能HTTP服务器和返向代理服务器. 下载.编译.安装模块: [[email protected] nginx-1.4.0]#wget http://nginx.org/download/nginx-1.4.0.tar.gz [[email protected] nginx-1.4.0]#tar -xzf nginx-1.4.0.tar.gz -C /usr/src/ [[email protected] nginx-1.4.0]#yum -y install gcc p