前面【muduo学习笔记:net部分之Http–HttpRequest、HttpResponse和HttpContext】介绍了TCP数据数据Buffer承载的HTTP报文的解析,
前面【muduo学习笔记:net部分之Http–HttpRequest、HttpResponse 和 HttpContext】介绍了TCP数据数据Buffer承载的HTTP报文的解析,本文结合TcpServer,基于muduo实现一个简单的HttpServer。由于对协议解析不完善,它不适合写web的通用服务。
1、HttpServer的定义
有了TcpServer的基础,加上前面博客关于HttpRequest、HttpResponse 和 HttpContext的使用,我们可以封装一个HttpServer,在收到客户端的消息后,按照HTTP协议解析Buffer,并使用回调函供用户处理Request、返回Response。
class HttpServer : noncopyable
{
public:
typedef std::function HttpCallback;
HttpServer(EventLoop* loop,
const InetAddress& listenAddr,
const string& name,
TcpServer::Option option = TcpServer::kNoReusePort);
EventLoop* getLoop() const { return server_.getLoop(); }
/// Not thread safe, callback be registered before calling start().
// 设置http请求的回调函数
void setHttpCallback(const HttpCallback& cb) { httpCallback_ = cb; }
void setThreadNum(int numThreads) { server_.setThreadNum(numThreads); }
void start();
private:
// TcpServer的新连接、新消息的回调函数
void onConnection(const TcpConnectionPtr& conn);
void onMessage(const TcpConnectionPtr& conn, Buffer* buf,Timestamp receiveTime);
// 在onMessage中调用,并调用用户注册的httpCallback_函数,对请求进行具体的处理
void onRequest(const TcpConnectionPtr&, const HttpRequest&);
TcpServer server_;
HttpCallback httpCallback_;
};
2、HttpServer的实现
在HttpServer构造函数中,注册TcpServer对外暴露的两个回调函数:新的连接、新消息到来。
server_.setConnectionCallback(std::bind(&HttpServer::onConnection, this, _1));
server_.setMessageCallback(std::bind(&HttpServer::onMessage, this, _1, _2, _3));
当客户端如浏览器连接上来,根据以前的分析可知,调用HttpServer::onConnection(), 绑定一个HttpContext
到TcpConnection
中的成员变量std::any context_
,这里绑定一个HttpContext主要是为了长连接中仅分配一次对象,提高效率。
void HttpServer::onConnection(const TcpConnectionPtr& conn)
{
if (conn->connected()){
conn->setContext(HttpContext()); // 绑定一个HttpContext到TcpConnection
}
}
接着客户端发出请求,比如访问服务器的某个路径,那么自动回调HttpServer::onMessage()
,
void HttpServer::onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp receiveTime)
{
HttpContext* cOntext= std::any_cast(conn->getMutableContext());
// 解析请求
if (!context->parseRequest(buf, receiveTime)){
conn->send("HTTP/1.1 400 Bad Request\r\n\r\n"); // 解析失败, 400错误
conn->shutdown(); // 断开本段写
}
// 请求解析成功
if (context->gotAll()){
onRequest(conn, context->request()); // 调用onRequest()私有函数
context->reset(); // 复用HttpContext对象
}
}
其中parseRequest()
会将存放在Buffer
中的请求解析到server_.TcpConnection.context_.request_
中,最后调用HttpServer::onRequest()
,
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); // 调用客户端的http处理函数,填充response
Buffer buf;
response.appendToBuffer(&buf); // 将响应格式化填充到buf中
conn->send(&buf); // 将响应回复发送给客户端
if (response.closeConnection()){
conn->shutdown(); // 如果是短连接,直接关闭。
}
}
即要用客户代码设置的httpCallback_
函数来填充HttpResponse
,然后发送给客户端。
3、HttpServer测试
muduo中自带的sample实现了几个GET请求,POST的请求可以根据实际情况进行解析。
#include
#include
#include
#include
#include
#include
#include
在网页中使用调试模式,打开连接 “localhost:8000/”,显示界面、两次请求如下。标签页的图标正常显示,而且长度在请求中也是555字节。
控制台结果实际为3次请求,控制台打印的解析请求头与web端截图完全一致。
20210812 13:46:00.293944Z 1467 WARN HttpServer[dummy] starts listenning on 0.0.0.0:8000 - HttpServer.cc:53
20210812 13:46:25.036289Z 1467 INFO TcpServer::newConnection [dummy] - new connection [dummy-0.0.0.0:8000#1] from 127.0.0.1:1071 - TcpServer.cc:80
20210812 13:46:25.036585Z 1467 INFO TcpServer::newConnection [dummy] - new connection [dummy-0.0.0.0:8000#2] from 127.0.0.1:1072 - TcpServer.cc:80
Headers GET /
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive
DNT: 1
Host: localhost:8000
Sec-Fetch-Dest: document
Sec-Fetch-Mode: navigate
Sec-Fetch-Site: none
Sec-Fetch-User: ?1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36
20210812 13:46:25.301348Z 1467 INFO TcpServer::newConnection [dummy] - new connection [dummy-0.0.0.0:8000#3] from 127.0.0.1:1073 - TcpServer.cc:80
关闭标签页,控制台无任何反应,关闭浏览器之后,3个连接断开
20210812 13:47:29.260650Z 1467 INFO TcpServer::removeConnectionInLoop [dummy] - connection dummy-0.0.0.0:8000#2 - TcpServer.cc:109
20210812 13:47:29.260789Z 1467 INFO TcpServer::removeConnectionInLoop [dummy] - connection dummy-0.0.0.0:8000#1 - TcpServer.cc:109
20210812 13:47:29.260821Z 1467 INFO TcpServer::removeConnectionInLoop [dummy] - connection dummy-0.0.0.0:8000#3 - TcpServer.cc:109