[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.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_cb);
server.setThreadNum(numThreads);
server.start();
loop.loop();
}
char favicon[555] = {
'\x89', 'P', 'N', 'G', '\xD', '\xA', '\x1A', '\xA',
'\x0', '\x0', '\x0', '\xD', 'I', 'H', 'D', 'R',
};