muduo_net程式碼剖析之Http相關類介紹
[1] 請求類HttpRequest
注:該類僅僅是對HttpRequest中的成員屬性(即Http請求包內容)賦值,客戶端並沒有真正的向伺服器傳送請求動作。
提供了設定、獲取以下屬性變數的介面:method_(請求方法)、version_(協議版本1.0/1.1)、 string path_(請求路徑)、string query_、Timestamp receiveTime_(請求時間)、std::map<string, string> headers_(首部欄位)
//請求類
class HttpRequest : public muduo::copyable
{
public:
enum Method //請求方法
{
kInvalid, kGet, kPost, kHead, kPut, kDelete
};
enum Version //HTTP版本
{
kUnknown, kHttp10, kHttp11
};
HttpRequest()
: method_(kInvalid),
version_(kUnknown)
{
}
void setVersion(Version v){ version_ = v; }
Version getVersion() const { return version_; }
Method method() const { return method_; }
bool setMethod(const char* start, const char* end)
{
assert(method_ == kInvalid);
string m(start, end);
if (m == "GET")
{
method_ = kGet;
}
else if (m == "POST")
{
method_ = kPost;
}
else if (m == "HEAD")
{
method_ = kHead;
}
else if (m == "PUT")
{
method_ = kPut;
}
else if (m == "DELETE")
{
method_ = kDelete;
}
else
{
method_ = kInvalid;
}
return method_ != kInvalid;
}
const char* methodString() const
{
const char* result = "UNKNOWN";
switch(method_)
{
case kGet:
result = "GET";
break;
case kPost:
result = "POST";
break;
case kHead:
result = "HEAD";
break;
case kPut:
result = "PUT";
break;
case kDelete:
result = "DELETE";
break;
default:
break;
}
return result;
}
void setPath(const char* start, const char* end) //訪問資源的路徑
{
path_.assign(start, end);
}
const string& path() const
{ return path_; }
void setQuery(const char* start, const char* end)
{
query_.assign(start, end);
}
const string& query() const
{ return query_; }
void setReceiveTime(Timestamp t)
{ receiveTime_ = t; }
Timestamp receiveTime() const
{ return receiveTime_; }
void addHeader(const char* start, const char* colon, const char* end)
{
string field(start, colon); //header域
++colon;
while (colon < end && isspace(*colon)) //去除左空格
{
++colon;
}
string value(colon, end); //header值
while (!value.empty() && isspace(value[value.size()-1])) //去除右空格
{
value.resize(value.size()-1);
}
headers_[field] = value; //元素<field,value>,新增到map中
}
string 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>& headers() const
{ return headers_; }
void swap(HttpRequest& that)
{
std::swap(method_, that.method_);
std::swap(version_, that.version_);
path_.swap(that.path_);
query_.swap(that.query_);
receiveTime_.swap(that.receiveTime_);
headers_.swap(that.headers_);
}
private:
Method method_; //請求方法
Version version_; //協議版本1.0/1.1
string path_; //請求路徑
string query_;
Timestamp receiveTime_; //請求時間
std::map<string, string> headers_; //header列表
};
[2] 響應類HttpResponse
注:該類僅僅是對HttpResponse 中的成員屬性(即Http響應包內容)賦值,伺服器並沒有對客戶端的請求執行迴應動作。
- 提供了設定、獲取以下屬性變數的介面:statusCode_(狀態響應碼)、statusMessage_(狀態響應碼對應的文字資訊)、bool closeConnection_(是否關閉連線)、body_(實體)、std::map<string, string> headers_(新增首部欄位)
- appendToBuffer介面:將HttpResponse物件,儲存成Buffer格式,以便於響應給客戶端
class Buffer;
//響應類
class HttpResponse : public muduo::copyable
{
private:
std::map<string, string> headers_; //header列表
HttpStatusCode statusCode_; //狀態響應碼
string statusMessage_; //狀態響應碼對應的文字資訊
bool closeConnection_;//是否關閉連線
string body_; //實體
public:
enum HttpStatusCode //狀態碼
{
kUnknown,
k200Ok = 200, //成功
k301MovedPermanently = 301,//301重定向,請求的頁面永久性一直另一個地址
k400BadRequest = 400, //錯誤的請求,語法格式有錯,伺服器無法處理此請求
k404NotFound = 404, //請求的網頁不存在
};
explicit HttpResponse(bool close)
: statusCode_(kUnknown),
closeConnection_(close)
{
}
void setStatusCode(HttpStatusCode code)
{ statusCode_ = code; }
void setStatusMessage(const string& message)
{ statusMessage_ = message; }
void setCloseConnection(bool on)
{ closeConnection_ = on; }
bool closeConnection() const
{ return closeConnection_; }
//設定文件媒體型別,即Content-Type欄位
void setContentType(const string& contentType)
{ addHeader("Content-Type", contentType); }
//新增首部欄位
void addHeader(const string& key, const string& value)
{ headers_[key] = value; }
void setBody(const string& body)
{ body_ = body; }
//將HttpResponse打包成字串,儲存到Buffer中,以便傳送給客戶端
void appendToBuffer(Buffer* output) const
{
//HTTP /1.1 200 OK
char buf[32];
snprintf(buf, sizeof buf, "HTTP/1.1 %d ", statusCode_); //200
output->append(buf);
output->append(statusMessage_); //狀態資訊OK
output->append("\r\n");
//長短連線的主要區別:是否新增body的長度
if (closeConnection_)
{
//如果是短連線,不需要告訴瀏覽器Content-Length,瀏覽器也能正確處理
//短連結是建立一次,傳送一條資料,因此不存在粘包問題
output->append("Connection: close\r\n");
}
else
{
snprintf(buf, sizeof buf, "Content-Length: %zd\r\n", body_.size());//實體長度
output->append(buf);
output->append("Connection: Keep-Alive\r\n");
}
//遍歷首部欄位列表,將<key,value>新增到buf中
for (const auto& header : headers_)
{
output->append(header.first);
output->append(": ");
output->append(header.second);
output->append("\r\n");
}
output->append("\r\n"); //header與body之間的空行
output->append(body_);
}
};
[3] HttpContext解析請求包
僅有一個重要的核心函式parseRequest:解析請求包,如果解析成功,則會將解析到的請求行、首部欄位新增到成員變數HttpRequest request_中
class HttpContext : public muduo::copyable
{
private:
//解析請求行,並判斷請求行語法是否正確:如果正確,返回false
// 如果錯誤,返回true
bool processRequestLine(const char* begin, const char* end);
HttpRequestParseState state_;//請求解析狀態,狀態機控制解析過程
HttpRequest request_; //http請求類,用於儲存解析後的結果
public:
//列舉型別,控制狀態機應該執行哪個狀態的操作
enum HttpRequestParseState
{
kExpectRequestLine, //解析請求行
kExpectHeaders, //解析首部欄位
kExpectBody, //解析Body
kGotAll, //已經全部解析完成
};
HttpContext(): state_(kExpectRequestLine){}
//[*] 解析請求包,解析順序:請求行、首部欄位
bool parseRequest(Buffer* buf, Timestamp receiveTime);
//判斷是否全部解析完成
bool gotAll() const { return state_ == kGotAll; }
void reset() //解析完成後,清空HttpRequest物件request_
{
state_ = kExpectRequestLine;
HttpRequest dummy;
request_.swap(dummy); //將當前的request_置空
}
const HttpRequest& request() const { return request_; }
HttpRequest& request() { return request_; }
};
/*
*解析請求包:請求行、首部欄位
*返回值:成功,true;失敗,false
*/
bool HttpContext::parseRequest(Buffer* buf, Timestamp receiveTime)
{
bool ok = true;
bool hasMore = true;
while (hasMore)
{
if (state_ == kExpectRequestLine) //處於解析請求行的狀態
{
const char* crlf = buf->findCRLF();//找到\r\n的位置
if (crlf)
{
//解析請求行,並判斷請求行語法是否正確
ok = processRequestLine(buf->peek(), crlf);
if (ok)
{
request_.setReceiveTime(receiveTime); //設定請求時間
buf->retrieveUntil(crlf + 2);//將請求行和\r\n從buf中移除
state_ = kExpectHeaders;//HttpContext將狀態改為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_中
request_.addHeader(buf->peek(), colon, crlf);
}
else
{
// empty line, end of header
// FIXME:
state_ = kGotAll;
hasMore = false;
}
buf->retrieveUntil(crlf + 2); //將( 當前解析的header+\r\n )從buf中移除
}
else
{
hasMore = false;
}
}
else if (state_ == kExpectBody) //當前還不支援請求中帶body
{
// FIXME:
}
}
return ok;
}
---------------------------------------------------------------
華麗的分割線
---------------------------------------------------------------
上面介紹了3個簡單的類,是為了下面介紹HttpServer類,該類是對客戶端發來的請求包做真正的迴應,它本質上就是一個TcpServer,只不過它是回覆了Http請求包而已。
HttpServer類
HttpServer類使用了TcpServer類的所有功能,它僅僅比TcpServer多一個函式介面setHttpCallback()和一個成員變數httpCallback_。該回調函式是用來處理客戶端發來的請求包的。整個執行過程:客戶端發來請求包後,將回調onMessage,在onMessage中又回調了onRequest,在onRequest中又回調了註冊的httpCallback_。詳細看下面程式碼:
//當client傳送來請求包給伺服器後,onMessage被回撥,此時客戶端發來的請求包被存放到buf中
//之後使用解析類物件context對buf進行解析
void HttpServer::onMessage(const TcpConnectionPtr& conn,
Buffer* buf, //客戶端發來的請求包被存放到buf中
Timestamp receiveTime)
{
HttpContext* context = boost::any_cast<HttpContext>(conn->getMutableContext());
//請求訊息解析失敗,即請求失敗
if (!context->parseRequest(buf, receiveTime))
{
conn->send("HTTP/1.1 400 Bad Request\r\n\r\n");
conn->shutdown();
}
//請求訊息解析完畢
if (context->gotAll())
{
onRequest(conn, context->request()); //回撥onRequest()
context->reset();//本次請求處理完畢,重置HttpContext,適用於長連線
}
}
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); //回調了使用者註冊的回撥函式httpCallback_
Buffer buf;
response.appendToBuffer(&buf);
conn->send(&buf);
if (response.closeConnection())
{
conn->shutdown();
}
}
其中,預設的回撥函式httpCallback_
//req:傳入引數,包含了請求包的所有資訊
//resp:傳出引數,根據請求包req的資訊對resp進行賦值
void defaultHttpCallback(const HttpRequest& req, HttpResponse* resp)
{
resp->setStatusCode(HttpResponse::k404NotFound);
resp->setStatusMessage("Not Found");
resp->setCloseConnection(true);
}
自定義的httpCallback_回撥函式的程式碼應該怎麼寫?
- req:傳入引數,包含了請求包的所有資訊
- resp:傳出引數,根據請求包req的資訊對resp進行賦值
程式碼書寫很簡單:根據req,給resp賦值即可
示例程式碼
HTTP客戶端程式碼:使用瀏覽器輸入請求的網址進行訪問
http://192.168.88.9:8000/hello
http://192.168.88.9:8000/favicon.ico
http://192.168.88.9:8000/image/png
HTTP伺服器程式碼:HttpServer_test.c
#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_cb(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 (const auto& header : headers)
{
std::cout << header.first << ": " << header