muduo網路庫學習之muduo_http 庫涉及到的類

s1mba發表於2013-11-13
1、http request:

request line + header + body (header分為普通報頭,請求報頭與實體報頭)

header與body之間有一空行(CRLF)

請求方法有:
Get, Post, Head, Put, Delete等

協議版本1.0、1.1

常用請求頭
Accept:瀏覽器可接受的媒體(MIME)型別;
Accept-Language:瀏覽器所希望的語言種類
Accept-Encoding:瀏覽器能夠解碼的編碼方法,如gzip,deflate等
User-Agent:告訴HTTP伺服器, 客戶端使用的作業系統和瀏覽器的名稱和版本
Connection:表示是否需要持久連線,Keep-Alive表示長連線,close表示短連線

一個典型的http 請求:

GET / HTTP/1.1
Accept: image/jpeg, application/x-ms-application, image/gif, application/xaml+xml, image/pjpeg, application/x-ms-xbap, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*
Accept-Language: zh-CN
User-Agent: Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; Tablet PC 2.0)
Accept-Encoding: gzip, deflate
Host: 192.168.159.188:8000
Connection: Keep-Alive


2、http response:

status line + header + body (header分為普通報頭,響應報頭與實體報頭)

header與body之間有一空行(CRLF)

狀態響應碼
1XX  提示資訊 - 表示請求已被成功接收,繼續處理
2XX  成功 - 表示請求已被成功接收,理解,接受
3XX  重定向 - 要完成請求必須進行更進一步的處理
4XX  客戶端錯誤 -  請求有語法錯誤或請求無法實現
5XX  伺服器端錯誤 -   伺服器執行一個有效請求失敗

一個典型的http 響應:

HTTP/1.1 200 OK
Content-Length: 112
Connection: Keep-Alive
Content-Type: text/html
Server: Muduo

<html><head><title>This is title</title></head><body><h1>Hello</h1>Now is 20130611 02:14:31.518462</body></html>


3、muduo_http 庫

HttpRequest:http請求類封裝,主要有以下幾個成員:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
 
class HttpRequest : public muduo::copyable
{
public:
    void addHeader(const char *start, const char *colon, const char *end);
private:
    Method method_;     // 請求方法
    Version version_;       // 協議版本1.0/1.1
    string path_;           // 請求路徑
    Timestamp receiveTime_; // 請求時間
    std::map<string, string> headers_;  // header列表
}

HttpResponse:http響應類封裝,主要有以下幾個成員:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
class HttpResponse : public muduo::copyable
{
public:

    void appendToBuffer(Buffer *output) const;  // 將HttpResponse新增到Buffer

private:
    std::map<string, string> headers_;  // header列表
    HttpStatusCode statusCode_;         // 狀態響應碼
    // FIXME: add http version
    string statusMessage_;              // 狀態響應碼對應的文字資訊
    bool closeConnection_;              // 是否關閉連線
    string body_;                           // 實體
};

HttpContext:http協議解析類,主要有以下幾個成員:

 C++ Code 
1
2
3
4
5
6
7
 

class HttpContext : public muduo::copyable
{
private:
    HttpRequestParseState state_;       // 請求解析狀態
    HttpRequest request_;               // http請求
};

HttpServer:http伺服器類封裝,主要有幾個成員:

 C++ Code 
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
 
/// A simple embeddable HTTP server designed for report status of a program.
/// It is not a fully HTTP 1.1 compliant server, but provides minimum features
/// that can communicate with HttpClient and Web browser.
/// It is synchronous, just like Java Servlet.
class HttpServer : boost::noncopyable
{
public:
    typedef boost::function < void (const HttpRequest &,
                                    HttpResponse *) > HttpCallback;

    /// Not thread safe, callback be registered before calling start().
    void setHttpCallback(const HttpCallback &cb)
    {
        httpCallback_ = cb;
    }

    void setThreadNum(int numThreads)
    {
        server_.setThreadNum(numThreads);
    }

    void start() { server_.start(); }

private:
    void onConnection(const TcpConnectionPtr &conn);
    void onMessage(const TcpConnectionPtr &conn, Buffer *buf, Timestamp receiveTime);
    void onRequest(const TcpConnectionPtr &, const HttpRequest &);

    TcpServer server_;
    HttpCallback httpCallback_; // 在處理http請求(即呼叫onRequest)的過程中回撥此函式,對請求進行具體的處理
};

在HttpServer 建構函式中:

 C++ Code 
1
2
3
4
 
server_.setConnectionCallback(
    boost::bind(&HttpServer::onConnection, this, _1));
server_.setMessageCallback(
    boost::bind(&HttpServer::onMessage, this, _1, _2, _3));

即通過設定server_ ,最終設定到TcpConnection 的回撥函式, 當客戶端如瀏覽器連線上來,根據以前的分析可知,呼叫HttpServer::onConnection(), 繫結HttpContext到TcpConnection 中的 boost::any context_;

 C++ Code 
1
2
3
4
 
if (conn->connected())
{
    conn->setContext(HttpContext());    // TcpConnection與一個HttpContext繫結
}

接著客戶端發出請求,比如訪問伺服器的某個路徑,呼叫HttpServer::onMessage(),

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 
void HttpServer::onMessage(const TcpConnectionPtr &conn,
                           Buffer *buf,
                           Timestamp receiveTime)
{
    HttpContext *context = boost::any_cast<HttpContext>(conn->getMutableContext());

    if (!detail::parseRequest(buf, context, receiveTime))
    {
        conn->send("HTTP/1.1 400 Bad Request\r\n\r\n");
        conn->shutdown();
    }

    // 請求訊息解析完畢
    if (context->gotAll())
    {
        onRequest(conn, context->request());
        context->reset();       // 本次請求處理完畢,重置HttpContext,適用於長連線(一個連線多次請求)
    }
}

其中parseRequest() 會將存放在Buffer 中的請求解析到server_.TcpConnection.context_.request_ 中,最後呼叫HttpServer::onRequest(),

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
void HttpServer::onRequest(const TcpConnectionPtr &conn, const HttpRequest &req)
{
    const string &connection = req.getHeader("Connection");
    bool close = connection == "close" ||
                 (req.getVersion() == HttpRequest::kHttp10 && connection != "Keep-Alive");
    HttpResponse response(close);
    httpCallback_(req, &response);  // 客戶程式碼設定的回撥函式,填充response
    Buffer buf;
    response.appendToBuffer(&buf);  // 將響應填充到buf
    conn->send(&buf);       // 將buf 中的響應傳送給客戶端
    if (response.closeConnection())
    {
        conn->shutdown();   //短連線直接關閉
    }
}

即要用客戶程式碼設定的httpCallback_ 函式來填充httpResponse,然後傳送給客戶端。

測試程式碼:
HttpServer_test.cc:

 C++ Code 
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
 
#include <muduo/net/http/HttpServer.h>
#include <muduo/net/http/HttpRequest.h>
#include <muduo/net/http/HttpResponse.h>
#include <muduo/net/EventLoop.h>
#include <muduo/base/Logging.h>

#include <iostream>
#include <map>

using namespace muduo;
using namespace muduo::net;

extern char favicon[555];
bool benchmark = false;

// 實際的請求處理
void onRequest(const HttpRequest &req, HttpResponse *resp)
{
    std::cout << "Headers " << req.methodString() << " " << req.path() << std::endl;
    if (!benchmark)
    {
        const std::map<string, string> &headers = req.headers();
        for (std::map<string, string>::const_iterator it = headers.begin();
                it != headers.end();
                ++it)
        {
            std::cout << it->first << ": " << it->second << std::endl;
        }
    }

    if (req.path() == "/")
    {
        resp->setStatusCode(HttpResponse::k200Ok);
        resp->setStatusMessage("OK");
        resp->setContentType("text/html");
        resp->addHeader("Server""Muduo");
        string now = Timestamp::now().toFormattedString();
        resp->setBody("<html><head><title>This is title</title></head>"
                      "<body><h1>Hello</h1>Now is " + now +
                      "</body></html>");
    }
    else if (req.path() == "/favicon.ico")
    {
        resp->setStatusCode(HttpResponse::k200Ok);
        resp->setStatusMessage("OK");
        resp->setContentType("image/png");
        resp->setBody(string(favicon, sizeof favicon));
    }
    else if (req.path() == "/hello")
    {
        resp->setStatusCode(HttpResponse::k200Ok);
        resp->setStatusMessage("OK");
        resp->setContentType("text/plain");
        resp->addHeader("Server""Muduo");
        resp->setBody("hello, world!\n");
    }
    else
    {
        resp->setStatusCode(HttpResponse::k404NotFound);
        resp->setStatusMessage("Not Found");
        resp->setCloseConnection(true);
    }
}

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

// 這是一個圖片資料
char favicon[555] =
{
    .....
};

執行程式,使用瀏覽器訪問目錄,如下:


伺服器端輸出如下:
simba@ubuntu:~/Documents/build/debug/bin$ ./httpserver_test 
20131113 08:15:30.110059Z  3637 TRACE IgnoreSigPipe Ignore SIGPIPE - EventLoop.cc:51
20131113 08:15:30.112625Z  3637 TRACE updateChannel fd = 4 events = 3 - EPollPoller.cc:104
20131113 08:15:30.113241Z  3637 TRACE EventLoop EventLoop created 0xBFBC8EF4 in thread 3637 - EventLoop.cc:76
20131113 08:15:30.113677Z  3637 TRACE updateChannel fd = 5 events = 3 - EPollPoller.cc:104
20131113 08:15:30.114767Z  3637 WARN  HttpServer[dummy] starts listenning on 0.0.0.0:8000 - HttpServer.cc:155
20131113 08:15:30.115687Z  3637 TRACE updateChannel fd = 6 events = 3 - EPollPoller.cc:104
20131113 08:15:30.116272Z  3637 TRACE loop EventLoop 0xBFBC8EF4 start looping - EventLoop.cc:108
20131113 08:15:32.870784Z  3637 TRACE poll 1 events happended - EPollPoller.cc:65
20131113 08:15:32.871784Z  3637 TRACE printActiveChannels {6: IN }  - EventLoop.cc:271
20131113 08:15:32.872287Z  3637 INFO  TcpServer::newConnection [dummy] - new connection [dummy:0.0.0.0:8000#1] from 192.168.56.1:2794 - TcpServer.cc:93
20131113 08:15:32.872898Z  3637 DEBUG TcpConnection TcpConnection::ctor[dummy:0.0.0.0:8000#1] at 0x9BD77E0 fd=8 - TcpConnection.cc:65
20131113 08:15:32.873280Z  3637 TRACE newConnection [1] usecount=1 - TcpServer.cc:111
20131113 08:15:32.873640Z  3637 TRACE newConnection [2] usecount=2 - TcpServer.cc:113
20131113 08:15:32.874050Z  3637 TRACE updateChannel fd = 8 events = 3 - EPollPoller.cc:104
20131113 08:15:32.874459Z  3637 TRACE newConnection [5] usecount=2 - TcpServer.cc:123
20131113 08:15:32.890691Z  3637 TRACE poll 1 events happended - EPollPoller.cc:65
20131113 08:15:32.890825Z  3637 TRACE printActiveChannels {8: IN }  - EventLoop.cc:271
Headers GET /hello
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8
Connection: keep-alive
Cookie: Hm_lvt_3d143f0a07b6487f65609d8411e5464f=1380329613
Host: 192.168.56.188:8000
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36
20131113 08:15:42.902273Z  3637 TRACE poll  nothing happended - EPollPoller.cc:74
20131113 08:15:52.912887Z  3637 TRACE poll  nothing happended - EPollPoller.cc:74
......
20131113 08:17:11.421249Z  3637 TRACE poll 1 events happended - EPollPoller.cc:65
20131113 08:17:11.421328Z  3637 TRACE printActiveChannels {8: IN }  - EventLoop.cc:271
Headers GET /favicon.ico
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8
Connection: keep-alive
Cookie: Hm_lvt_3d143f0a07b6487f65609d8411e5464f=1380329613
Host: 192.168.56.188:8000
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.66 Safari/537.36
20131113 08:17:21.432017Z  3637 TRACE poll  nothing happended - EPollPoller.cc:74
20131113 08:17:31.439508Z  3637 TRACE poll  nothing happended - EPollPoller.cc:74

從輸出中看到,fd=6是監聽套接字,fd=8是與伺服器連線起來的已連線套接字(fd=7 是idlefd_)。瀏覽器訪問某個目錄,fd=8可讀事件發生,伺服器端最終執行到httpCallback_(onRequest() 函式),首先從HttpRequest中讀取解析到的請求頭部等資訊並列印出來,即Headers GET /hello 開始的幾行輸出。這是一個長連線,所以第二次切換目錄訪問時,也是fd=8可讀事件發生,只不過填充到HttpResponse 的資料不同,故瀏覽器端看到的輸出就不一樣了。

注意:muduo只實現了最簡單的http應答功能,並不能處理帶有body的http 請求,比如POST。實現http庫主要是為了讓muduo inspect 庫可以通過HTTP方式為伺服器提供監控介面。


參考:
《UNP》
muduo manual.pdf
《linux 多執行緒伺服器程式設計:使用muduo c++網路庫》

相關文章