从零开始学写HTTP服务器(六)使用muduo网络库


muduo源码中内嵌了一个简单的HTTP server程序,源码见muduo/net/http。看了源码之后受益匪浅。
muduo里面的HttpServer目前只支持GET和HEAD方法,获取资源的Content-Type在源代码中写死,本文主要就是添加了一个读取mime.type的功能。希望之后能继续扩展这个简单的HttpServer,支持CGI和POST。

(一)HttpServer

跟简单的echo server套路一样,首先定义一个HttpServer,它需要用EventLoop对象构造,包含一个TcpServer成员等。唯一的区别就是多了一个onRequest回调

//HttpServer.h

#ifndef __HTTPSERVER_H__
#define __HTTPSERVER_H__
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "HttpContext.h"
#include "HttpRequest.h"
#include "HttpResponse.h"
using namespace std;


class HttpServer : boost::noncopyable
{
    public:
    //http回调类型
    typedef boost::function<void (const HttpRequest&,
        HttpResponse*)> HttpCallback;

    //构造函数
    HttpServer(muduo::net::EventLoop* loop,
        const muduo::net::InetAddress& listenAddr);

    ~HttpServer();  // force out-line dtor, for scoped_ptr members.
    muduo::net::EventLoop* getLoop() const { return server_.getLoop(); }
    void setHttpCallback(const HttpCallback& cb)
    {
        httpCallback_ = cb;
    }
    void setThreadNum(int numThreads)
    {
        server_.setThreadNum(numThreads);
    }
    void start();

    private:
    //onConnection
    void onConnection(const muduo::net::TcpConnectionPtr &conn);
    //onMessage
    void onMessage(const muduo::net::TcpConnectionPtr&conn,
        muduo::net::Buffer*buf,
        muduo::Timestamp receiveTime);
    //+++++
    void onRequest(const muduo::net::TcpConnectionPtr&,const HttpRequest&);
    muduo::net::TcpServer server_;
    HttpCallback httpCallback_;
};
#endif
//HttpServer.cpp

#include "HttpServer.h"
#if 1
//默认HTTP回调,返回错误码
void defaultHttpCallback(const HttpRequest&, HttpResponse* resp)
{
#if 1
    resp->setStatusCode(HttpResponse::CODE_400);
    resp->setStatusMessage("Not Found");
    resp->setCloseConnection(true);
#endif
}
#endif


#if 1
//构造函数
HttpServer::HttpServer(muduo::net::EventLoop* loop,
    const muduo::net::InetAddress& listenAddr)
    : server_(loop, listenAddr, "MyHttpd"),httpCallback_(defaultHttpCallback)
{
    server_.setConnectionCallback(
        boost::bind(&HttpServer::onConnection, this, _1));

    server_.setMessageCallback(
        boost::bind(&HttpServer::onMessage, this, _1, _2, _3));
}
//析构函数
HttpServer::~HttpServer()
{
}
void HttpServer::start()
{
    LOG_WARN << "HttpServer[" << server_.name()
    << "] starts listenning on " << server_.ipPort();
    server_.start();
}
#endif

//新连接回调
void HttpServer::onConnection(const muduo::net::TcpConnectionPtr& conn)
{
    if (conn->connected())
    {
        conn->setContext(HttpContext());
    }
}
//消息回调
void HttpServer::onMessage(const muduo::net::TcpConnectionPtr& conn,
    muduo::net::Buffer* buf,
    muduo::Timestamp receiveTime)
{
    HttpContext*context=boost::any_cast(conn->getMutableContext());
    //解析请求
    if(!context->parseRequest(buf,receiveTime))
    {
        conn->send("HTTP/1.1 400 Bad Request\r\n\r\n");
        conn->shutdown();
    }
    if (context->gotAll())//state_==gotALL
    {
    //请求已经解析完毕
        onRequest(conn, context->request());

        context->reset();//context reset
    }

}

#if 1
void HttpServer::onRequest(const muduo::net::TcpConnectionPtr& conn, const HttpRequest& req)
{
#if 1
    const string& connection = req.getHeader("Connection");
    bool close = connection == "close" ||
    (req.getVersion() == HttpRequest::HTTP10 && connection != "Keep-Alive");
    HttpResponse response(close);//构造响应
    httpCallback_(req, &response);//用户回调
    muduo::net::Buffer buf;
    //此时response已经构造好,将向客户发送Response添加到buffer中
    response.appendToBuffer(&buf);
    conn->send(&buf);
    //如果非Keep-Alive则直接关掉
    if (response.closeConnection())
    {
        conn->shutdown();
    }
#endif
}
#endif

按照TcpServer接收连接->处理连接这个顺序来看,首先将调用onConnection,该函数为Tcp连接创建Http上下文变量HttpContext,这个类主要用于接收客户请求(这里为Http请求),并解析请求。


(二)HttpContext

//HttpContext.h

#ifndef __HTTPCONTEXT_H__
#define __HTTPCONTEXT_H__

#include 
#include 
#include "HttpRequest.h"
class Buffer;
class HttpContext:public muduo::copyable
{
    public:
    //解析请求的状态
    enum HttpRequestParseState
    {
        kExpectRequestLine,
        kExpectHeaders,
        kExpectBody,
        kGotAll,
    };

    HttpContext()
        : state_(kExpectRequestLine)
    {
    }

    bool parseRequest(muduo::net::Buffer* buf, muduo::Timestamp receiveTime);

    bool gotAll() const
    { return state_ == kGotAll; }

    void reset()
    {
        state_ = kExpectRequestLine;
        HttpRequest dummy;
        request_.swap(dummy);
    }
    const HttpRequest& request() const
    { return request_; }

//返回Http请求
    HttpRequest& request()
    { return request_; }

    private:
    //解析请求行
    bool processRequestLine(const char* begin, const char* end);

    HttpRequestParseState state_;
    //解析的结果将保存在request_成员中
    HttpRequest request_;
};
#endif
#include "HttpContext.h"
//解析请求头
bool HttpContext::processRequestLine(const char*begin,const char*end)
{
    bool succeed=false;
    const char*start=begin;
    const char*space = std::find(start,end,' ');
    //设置请求方法//method_
    if(space!=end&&request_.setMethod(start,space))
    {
        start=space+1;
        space = std::find(start,end,' ');
        if(space !=end)
        {
            //解析URI
            const char* question = std::find(start, space, '?');
            if (question != space)
            {
                request_.setPath(start, question);
                request_.setQuery(question, space);
            }
            else
            {
                request_.setPath(start, space);
            }
            //解析HTTP版本号
            start = space+1;
            succeed = end-start == 8 && std::equal(start, end-1, "HTTP/1.");
            if (succeed)
            {
                if (*(end-1) == '1')
                {
                    request_.setVersion(HttpRequest::HTTP11);
                }
                else if (*(end-1) == '0')
                {
                    request_.setVersion(HttpRequest::HTTP10);
                }
                else
                {
                    succeed = false;
                }
            }//endif
        }//endif
    }//endif
    return succeed;
}
//解析请求头
bool HttpContext::parseRequest(muduo::net::Buffer*buf,muduo::Timestamp receiveTime)
{
    bool ok= true;
    bool hasMore=true;
    while(hasMore)
    {
        //解析请求行
        if(state_==kExpectRequestLine)
        {
            //
            const char*crlf=buf->findCRLF();
            if(crlf)
            {
                //开始解析请求行
                ok=processRequestLine(buf->peek(),crlf);
                if(ok)
                {
                    //解析成功
                    request_.setReceiveTime(receiveTime);
                    //回收请求行buffer
                    buf->retrieveUntil(crlf+2);
                    state_=kExpectHeaders;
                }
                else
                {
                    hasMore=false;
                }
            }
            else
            {
                hasMore=false;
            }
        }
        //解析请求头
        else if(state_==kExpectHeaders)
        {
            const char* crlf = buf->findCRLF();
            if (crlf)
            {
                //冒号
                const char* colon = std::find(buf->peek(), crlf, ':');
                if (colon != crlf)
                {
                    request_.addHeader(buf->peek(), colon, crlf);
                }
                else
                {
                // empty line, end of header
                // FIXME:
                    state_ = kGotAll;
                    hasMore = false;
                }
                buf->retrieveUntil(crlf + 2);//回收
            }
            else
            {
                hasMore = false;
            }
        }
        else if(state_==kExpectBody)
        {
            // FIXME:
        }
    }//endwhile
    return ok;
}

(三)HttpRequest

HttpContext类对客户端发来的请求进行接收和解析,最终将解析出来的消息保存到了HttpRequest对象中,下面来看看这个类中都包含哪些内容:

//HttpRequest.h

#ifndef __HTTPREQUEST_H__
#define __HTTPREQUEST_H__
#include 
#include 

#include 
#include 
#include 
using namespace std;
class HttpRequest:muduo::copyable
{
    public:
    enum Method
    {
        INVALID,
        GET,
        POST,
        HEAD,
        PUT,
        DELETE
    };
    enum Version
    {
        UNKNOWN,
        HTTP10,
        HTTP11
    };

    HttpRequest();
    //set和get方法声明
    void setVersion(Version v);
    Version getVersion() const;
    bool setMethod(const char *start,const char* end);
    Method method()const;
    const char*methodString()const;
    void setPath(const char*start,const char*end);
    const string& path()const;
    void setQuery(const char* start, const char* end);
    const string& query() const;
    void setReceiveTime(muduo::Timestamp t);
    muduo::Timestamp receiveTime() const;
    void addHeader(const char*start,const char*colon,const char*end);
    string getHeader(const string& field)const;
    const map<string,string>&headers()const;
    void swap(HttpRequest& that);



    private:
    Method method_;
    Version version_;
    string path_;
    string query_;
    muduo::Timestamp receiveTime_;
    std::map<string,string>headers_;
};
#endif 
//HttpRequest.cpp

#include "HttpRequest.h"
HttpRequest::HttpRequest(): method_(INVALID),
    version_(UNKNOWN)
{
}

void HttpRequest::setVersion(Version v)
{
    version_=v;
}
HttpRequest::Version HttpRequest::getVersion()const
{
    return version_;
}
bool HttpRequest::setMethod(const char* start, const char* end)
{
    assert(method_ == INVALID);
    string m(start, end);
    if (m == "GET")
    {
        method_ = GET;
    }
    else if (m == "POST")
    {
        method_ = POST;
    }
    else if (m == "HEAD")
    {
        method_ = HEAD;
    }
    else if (m == "PUT")
    {
        method_ = PUT;
    }
    else if (m == "DELETE")
    {
        method_ = DELETE;
    }
    else
    {
        method_ = INVALID;
    }
    return method_ != INVALID;
}

HttpRequest::Method HttpRequest::method() const
{ 
    return method_;
}

const char* HttpRequest::methodString() const
{
    const char* result = "UNKNOWN";
    switch(method_)
    {
    case GET:
        result = "GET";
        break;
    case POST:
        result = "POST";
        break;
    case HEAD:
        result = "HEAD";
        break;
    case PUT:
        result = "PUT";
        break;
    case DELETE:
        result = "DELETE";
        break;
    default:
        break;
    }
    return result;
}
void HttpRequest::setPath(const char* start, const char* end)
{
    path_.assign(start, end);
}
const string& HttpRequest::path() const
{
    return path_;
}
void HttpRequest::setQuery(const char* start, const char* end)
{
    query_.assign(start, end);
}

const string& HttpRequest::query() const
{ 
    return query_;
}


void HttpRequest::setReceiveTime(muduo::Timestamp t)
{ 
    receiveTime_ = t; 
}

muduo::Timestamp HttpRequest::receiveTime() const
{ 
    return receiveTime_;
}
void HttpRequest::addHeader(const char* start,
        const char* colon,
        const char* end)
{
    string field(start, colon);
    ++colon;
    while (colon < end && isspace(*colon))
    {
        ++colon;
    }
    string value(colon, end);
    //while循环用来去掉后导的空格
    while (!value.empty() && isspace(value[value.size()-1]))
    {
        value.resize(value.size()-1);
    }
    headers_[field] = value;//将解析出来的头信息放入map中
}


string HttpRequest::getHeader(const string& field) const
{
    string result;
    std::map<string, string>::const_iterator it = headers_.find(field);
    if (it != headers_.end())
    {
        result = it->second;
    }
    return result;
}

const std::map<string, string>& HttpRequest::headers() const
{
    return headers_;
}

void HttpRequest::swap(HttpRequest& that)
{
    std::swap(method_, that.method_);
    path_.swap(that.path_);
    query_.swap(that.query_);
    receiveTime_.swap(that.receiveTime_);
    headers_.swap(that.headers_);
}

(四)HttpResponse

解析出来请求头后就需要按照不同的请求方法、类型等构造响应了,muduo封装了HttpResponse类将响应实体保存到Buffer对象中,最后通过TcpConnection发送给客户端。

//HttpResponse.h

#ifndef __HTTPRESPONSE_H__
#define __HTTPRESPONSE_H__
#include 
#include 
#include 
#include 
#include 
using namespace std;
class HttpResponse:public muduo::copyable
{
    public:
    enum HttpStatusCode
    {
        CODE_UNKNOWN,
        CODE_200=200,
        CODE_301=301,
        CODE_400=400,
        CODE_404=404
    };
    explicit HttpResponse(bool close)
        : statusCode_(CODE_UNKNOWN),
        closeConnection_(close)
    {
    }
    void setStatusCode(HttpStatusCode code);
    void setStatusMessage(const string&message);
    void setCloseConnection(bool on);
    bool closeConnection()const;
    void setContentType(const string&contentType);
    void addHeader(const string&key,const string&value);
    void setBody(const string &body);
    void appendToBuffer(muduo::net::Buffer*output)const;
    private:
    //响应头
    std::map<string,string>headers_;
    //响应码
    HttpStatusCode statusCode_;
    //状态信息
    string statusMessage_;
    //是否 keep-alive
    bool closeConnection_;
    //响应报文
    string body_;

};
#endif
//HttpResponse.cpp

#include "HttpResponse.h"
//set和get方法
void HttpResponse::setStatusCode(HttpStatusCode code)
{
    statusCode_=code;
}
void HttpResponse::setStatusMessage(const string& message)
{
    statusMessage_ = message;
}
void HttpResponse::setCloseConnection(bool on)
{
    closeConnection_ = on;
}
bool HttpResponse::closeConnection() const
{
    return closeConnection_;
}
void HttpResponse::setContentType(const string& contentType)
{
    addHeader("Content-Type", contentType);
}
void HttpResponse::addHeader(const string& key, const string& value)
{
    headers_[key] = value;
}
void HttpResponse::setBody(const string& body)
{
    body_ = body;
}

void HttpResponse::appendToBuffer(muduo::net::Buffer* output) const
{
    char buf[32];
    //构造响应行
    snprintf(buf, sizeof buf, "HTTP/1.1 %d ", statusCode_);
    output->append(buf);
    output->append(statusMessage_);
    output->append("\r\n");

    if (closeConnection_)
    {
        //
        output->append("Connection: close\r\n");
    }
    else
    {
        //Keep-Alive需要Content-Length
        snprintf(buf, sizeof buf, "Content-Length: %zd\r\n", body_.size());
        output->append(buf);
        output->append("Connection: Keep-Alive\r\n");
    }

    for (std::map<string, string>::const_iterator it = headers_.begin();
        it != headers_.end();
        ++it)
    {
        //迭代构造响应头
        output->append(it->first);
        output->append(": ");
        output->append(it->second);
        output->append("\r\n");
    }

    output->append("\r\n");
    //响应报文
    output->append(body_);

}

(五)Util

除此之外我写了一个简单的工具类用来解析mime.type

//Util.cpp

#include "Util.h"
namespace Util
{
    //获取请求的绝对路径
    string ConstructPath(const string &path)
    {
        string webHome="/home/zhangxiao/zxtest/myhttpd/httpservermuduo";
        if(path=="/")
        {
            return webHome+"/index.html";
        }
        else
        {
            return webHome+path;
        }
    }

    //获取文件扩展名
    string GetExtent(const string &path)
    {
    int i;
    for(i=path.size()-1;;--i)
    {
        if(path[i]=='.')
        break;
    }
        return string(path.begin()+i+1,path.end());
    }
    //获取content-Type
    void GetContentType(const string &tmpExtension,
        string &contentType)
    {
        ifstream mimeFile("mime.types");
        string line;
        while(NULL!=getline(mimeFile,line))
        {
            if(line[0]!='#')
            {
                stringstream lineStream(line);
                contentType.clear();
                lineStream>>contentType;
                vector<string>extensions;
                string extension;
                while(lineStream>>extension)
                {
                    extensions.push_back(extension);
                }
                for(int i=0;iif(tmpExtension==extensions[i])
                    {
                        mimeFile.close();
                        return ;
                    }
                }
            }//endif
        }//end while
        //如果都不匹配就默认为text/plain
        contentType="text/plain";
        mimeFile.close();
    }
    //获取Content
    string GetContent(const string &fileName)
    {
        std::ifstream fin(fileName, std::ios::in | std::ios::binary);
        if(fin.fail())
        {
            return string("");
        }
        std::ostringstream oss;
        oss << fin.rdbuf();
        return std::string(oss.str());
    }
}

(六)服务端代码

利用上述封装的类,写出服务端测试代码:

#include "HttpServer.h"
#include "HttpRequest.h"
#include "HttpResponse.h"
#include 
#include 
#include "Util.h"

#include 
#include 
#include 
using namespace std;


void onRequest(const HttpRequest& req, HttpResponse* resp)
{
    string path=Util::ConstructPath(req.path());
    string extent=Util::GetExtent(path);
    string contentType="";
    Util::GetContentType(extent,contentType);
    string content=Util::GetContent(path);
    if(content.empty())
    {
        resp->setStatusCode(HttpResponse::CODE_404);
        resp->setStatusMessage("Not Found");
    }
    else
    {
        resp->setBody(content);
        resp->setStatusCode(HttpResponse::CODE_200);
        resp->setStatusMessage("OK");
        resp->setContentType(contentType);
    } 
}

int main(int argc, char* argv[])
{
    int numThreads = 0;
    if (argc > 1)
    {
    //benchmark = true;
        muduo::Logger::setLogLevel(muduo::Logger::WARN);
        numThreads = atoi(argv[1]);
    }
    muduo::net::EventLoop loop;
    HttpServer server(&loop, muduo::net::InetAddress(8000) );
    server.setHttpCallback(onRequest);
    server.setThreadNum(numThreads);
    server.start();
    loop.loop();
    return 0;
}

(七)测试

本地运行,用chrome做客户端测试:
这里写图片描述

从零开始学写HTTP服务器(六)使用muduo网络库_第1张图片
其中图是我自己截图的一个png格式文件,放在工程目录下,浏览器会自动发送请求。


(八)附录

index.html文件:

<html>
<head>
<title>Welcome to nginx!title>
head>
<body bgcolor="white" text="black">
<img src="/baidu.png"  />
<center><h1>zxtest Welcome to nginx!h1>center>
body>
html>

Makefile文件

PROGS=myhttpd

MUDUO_DIRECTORY = ${HOME}/build/release-install
MUDUO_INCLUDE = $(MUDUO_DIRECTORY)/include
MUDUO_LIBRARY = $(MUDUO_DIRECTORY)/lib

all : ${PROGS}
CXXFLAGS+=-g -std=c++11 -I${MUDUO_INCLUDE} 
LDFLAGS+=-L${MUDUO_LIBRARY} -lmuduo_net -lmuduo_base -lpthread -lrt

myhttpd:myhttpd.o HttpServer.o HttpRequest.o HttpResponse.o HttpContext.o Util.o
    @${CXX} ${CXXFLAGS}  -o $@   $^  ${LDFLAGS}


clean:
    @rm -rf ${PROGS} *.o 

.PHONY: all clean

(九)参考

图解http
linux多线程服务端编程使用muduo网络库

你可能感兴趣的:(Http协议)