http server muduo库

  • content {:toc}

0 写在开头

如无必要,勿增实体”———威廉

这就是著名的奥卡姆剃刀原则, 他说的是, 小就是美, 今天在写处理http的网络库时, 深有感触

http协议位于传输层上方, 但是不一定是最高的层次, 他就像操作系统对于硬件和用户的关系一样, 既要支持用户直接用, 也要支持作为协议的一层

从支持用户直接使用的角度来看, 报文的请求行, 请求头, 请求体要一块进行处理, 但是从协议的一层来看, 请求体要向上传递, 交给另一个协议进行处理, 设计时, 要让 body部分不处理, 这就带来了一个问题, 如何区分headersbody, 因为headers可以有很多行, 协议的设计人员使用一个空行来进行分隔, 既要用少的规则来区分不同的属性, 又要尽可能的简单

优点

  • 有着crlf这个明确的分界线来表示一行, 我们能够逐行处理
  • 请求行中有着空格隔开, 可以十分方便的处理每个值
  • 请求头分为很多行, 可以逐行处理每一对header, 使用同一个逻辑 ( while )
  • 请求头和请求体之间有着一个空行, 标志着请求头的结束, 能够方便的区分( 判断 )这两者

用户关心的事情只有

  • 对发送来的 HTTP做出回复
  • 因此, 为用户提供的接口是
1
2
using HttpCallback = function<void(const HttpRequest &, HttpResponse * )>;
void setHttpCallback(const HttpCallback & cb);

1 HttpRequest

1
2
3
https://www.example.com/product/123?category=electronics&sort=price_asc,
- path_的值为/product/123
- query_的值为category=electronics&sort=price_asc
  • 存储了连接的状态, URL以及 headers
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class HttpRequest{
public:
    using HttpMap= map<string, string> ;

    enum Method{
        kInvalid, kGet, kPost, kHead, kPut, kDelete
    };

    enum Version{
        kUnkown, kHttp10 , kHttp11
    };
private:
    Method method_;
    Version version_;    

    std::string path_;
    std::string query_;
    Timestamp receiveTime_;
    std::map<string, string> headers_;

};

2 HttpContext

2.1 主要的功能:

  • 处理 http报文, 将报文解析出来, 传入成员HttpRequest
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class HttpContext{
public:
    enum HttpRequestParseState{
        kExpectRequestLine, // 期望解析请求行
        kExpectHeaders, // 期望解析请求头部
        kExpectBody, // 期望解析请求体
        kGotAll, // 已经解析完整请求
    };
    bool parseRequCest(Buffer* buf, Timestamp receivetime);
private:
    HttpRequestParseState state_;
    HttpRequest request_;

};

2.2 http报文格式

  • 请求行 / 状态行
    • 方法名
    • URL
    • 协议版本
  • 请求头 / 响应头
  • 请求体 / 响应体
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
POST /index.html HTTP/1.1 #(回车换行)
Who: Alex                 #(回车换行)
Content-Type: text/plain 
Host: 127.0.0.1:8888
Content-Length: 28
Cookie: JSESSIONID=24DF2688E37EE4F66D9669D2542AC17B #(回车换行)
#(回车换行)
Let's watch a movie together



# 响应报文
HTTP/1.1 200 OK 
Server: Apache-Coyote/1.1
Content-Type: application/json 
Transfer-Encoding: chunked 
Date: Mon, 12 Sep 2011 12: 41: 24 GMT
6f
{"password":"1234","userName":"tom","birthday":null,"salary":0,
"realName": "tomson","userId": "1000","dept":null}
0

其中, 状态行和首部中的每行都是以回车符 (\r,%0d,CR) 和换行符 (\n,%0a,LF) 结束, headersbody中间有一个空行

因此我们也要分为三部分处理, 首先处理请求行, 再处理 header, 最后再处理 请求体

2.3 核心函数bool parseRequCest(Buffer* buf, Timestamp receivetime);

  • 功能: 从buffer中将报文解析出来
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
bool HttpContext::parseRequCest(Buffer *buf, Timestamp receivetime)
{
    bool ok = true;
    bool has_more = true;
    while(has_more){
        if(state_ ==kExpectRequestLine){
            // 找到第一行结束的位置
            const char *crlf = buf->findCRLF();

            if(crlf){
                // 处理 请求行
                ok = processRequestLine(buf-> Peek(), crlf);

                if(ok){
                    // 设置时间戳
                    request_.setReceiveTime(receivetime);
                    buf-> Retrieve(crlf + 2 - buf-> Peek());
                    // 处理完请求行, 处理 headers
                    state_ = kExpectHeaders;
                }
                else{// !ok
                    // 遇到错误, 处理失败
                    has_more = false;
                }
            }
            else{// ! crlf
                // 没有找到 crlf, 非法报文
                has_more = false;
            }
        }

        // 处理 头部, 本身没有循环, 通过外部循环来**逐行** 处理
        else if (state_ == kExpectHeaders){// state_ != kExpectRequestLine
            const char* crlf = buf->findCRLF();

            if(crlf){
                const char * colon = std::find(buf-> Peek(), crlf, ':');
                if(colon != crlf){
                    ++colon;
                    while(*colon == ' '){
                        colon++;
                    }
                    request_.addHeader(string(buf-> Peek(), colon), string(colon, crlf));
                }
                // 有一个空行, 头部处理完毕
                else{
                    state_ = kGotAll;
                    has_more = false;
                }
                buf-> Retrieve(crlf + 2 - buf-> Peek());
            }
            // 没找到回车换行, 非法报文
            else{
                has_more = false;
            }
        }

        // body 部分依旧留在 buf中, 
        // 空语句, 在这里不处理, 只是为了逻辑上更顺畅
        else if (state_ == kExpectBody){

        }

    }
    return ok;
}

2.4处理请求行

  • 每个值中间都用空格方法(空格)URL(空格)版本CRLF
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
bool HttpContext::processRequestLine(const char *begin, const char *end)
{
    // 传进来的数据就是一行, 不用找 crlf
    // 以空格为准
    bool succeed = false;
    const char * start = begin;
    const char * space = std::find(start, end, ' ');

    // 设置方法名
    if(space != end && request_.setMethod(string(start, space))){
        start = space +1;

        // 继续查找下一个空格
        space = std::find(start, end, ' ');
        if(space != end){
            const char * question = std::find(start, space, '?');
            if(question != space){
                request_.setPath(string(start, question));
                request_.setQuery(string(question, space));
            }
            else{
                request_.setPath(string(start, space));
            }

            start = space+1;
            succeed = end - start == 8 && std::equal(start, end-1, "HTTP/1.");

            if(succeed ){
                if(*(end-1) == '1'){
                    request_.setVersion(HttpRequest::kHttp11);
                }
                if(*(end-1) == '0'){
                    request_.setVersion(HttpRequest::kHttp10);
                }
                else{
                    succeed = false;
                }
            }

        }// space != end
    
        
    }// set method
    return succeed;
}

3 httpResponse

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
class HttpResponse{
public:
      enum HttpStatusCode
    {
        kUnknown,
        k200Ok = 200,
        k301MovedPermanently = 301,
        k400BadRequest = 400,
        k404NotFound = 404,
    };

    explicit HttpResponse(bool close)
        : status_code_(kUnknown),
        close_connection_(close)
    {
    }

        void setStatusCode(HttpStatusCode code)
    { status_code_ = code; }

    void setStatusMessage(const string& message)
    { status_message_ = message; }

    void setCloseConnection(bool on)
    { close_connection_ = on; }

    bool closeConnection() const
    { return close_connection_; }

    void setContentType(const string& contentType)
    { addHeader("Content-Type", contentType); }

    // FIXME: replace string with StringPiece
    void addHeader(const string& key, const string& value)
    { headers_[key] = value; }

    void setBody(const string& body)
    { body_ = body; }

    void appendToBuffer(Buffer* output) const;

private:
    HttpStatusCode status_code_;
    string body_;
    string status_message_;
    bool close_connection_;
    std::map<string, string> headers_;
};

4 HttpServer

用户只需要关心:

  • 对来的 报文 做 制作 response, 因此using HttpCallback = function<void(const HttpRequest &, HttpResponse * )> ;, 第一个参数用来保存用户的请求, 第二个参数我们用来制作报文
  • 为用户提供的接口function<void(const HttpRequest &, HttpResponse * )>

因此在 onMessage 中, 库设计者需要

  • tcp中的消息转换为 HTTP报文
  • 调用用户传入的callback函数,
  • 发送 response
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
class HttpServer{
public:
    using HttpCallback = function<void(const HttpRequest &, HttpResponse * )> ;

    // 实际就是进行 server_的初始化操作
    HttpServer(EventLoop *loop, const InetAddress & linten_addr, 
                const string & name, TcpServer::Option option = TcpServer::kNoReusePort);
    EventLoop * getLoop()const;
    void setHttpCallback(const HttpCallback & cb){
        http_callback_ = cb;
    }

    void setThreadNum(int num_thread){
        server_.setNumThread(num_thread);
    }

    void start();

    void onConnection(const TcpConnectionPtr & conn);
    void onMessage(const TcpConnectionPtr &conn, Buffer *buf, Timestamp receive_time);

    void onRequest(const TcpConnectionPtr& conn, const HttpRequest & req);


private:
    TcpServer server_;
    HttpCallback http_callback_;
};



void defaultHttpCallback(const HttpRequest &, HttpResponse *resp){
    resp-> setStatusCode(HttpResponse::k404NotFound);
    resp-> setStatusMessage("Not Found");
    resp-> setCloseConnection(true);
}



HttpServer::HttpServer(EventLoop *loop, const InetAddress &listen_addr, 
    const string &name, TcpServer::Option option):
    server_(loop, listen_addr, name, option ),
    http_callback_(defaultHttpCallback)
{
    server_.setConnectionCallback(std::bind(&HttpServer::onConnection, this, _1));
    server_.setMessageCallback(std::bind(&HttpServer::onMessage, this, _1, _2, _3));
    
}

EventLoop *HttpServer::getLoop() const
{
    return server_.getLoop();
}

void HttpServer::start()
{
    LOG_DEBUG("httpserver %s start, listen on %s", server_.name(), server_.ipPort());
    server_.start();
}

void HttpServer::onConnection(const TcpConnectionPtr &conn)
{
    if(conn-> connected()){
        conn->setContext();
    }
}

void HttpServer::onMessage(const TcpConnectionPtr &conn, Buffer *buf, Timestamp receive_time)
{
    HttpContext context;
    bool ok =  context.parseRequCest(buf, receive_time);
    if(!ok){
        conn-> send(string("HTTP/1.1 400 Bad Request\r\n\r\n"));
        conn-> shutdown();
    }

    if(context.gotAll()){
        onRequest(conn, context.request());
        context.reset();
    }



}

void HttpServer::onRequest(const TcpConnectionPtr &conn, const HttpRequest &req)
{
    const string &connection = req.getHeader("Connection");
    bool isclose = connection=="close" || 
        (req.getVersion() == HttpRequest::kHttp10 && connection != "Keep-Alive");

    HttpResponse response(isclose);
    http_callback_(req, &response);
    Buffer buf;
    response.appendToBuffer(&buf);
    conn->send(&buf);

    if(response.closeConnection()){
        conn-> shutdown();
    }



}
Licensed under CC BY-NC-SA 4.0
最后更新于 Oct 22, 2024 20:36 +0800
使用 Hugo 构建
主题 StackJimmy 设计