当前位置 博文首页 > wanggao的专栏:muduo学习笔记:net部分之Http--HttpServer
前面【muduo学习笔记:net部分之Http–HttpRequest、HttpResponse 和 HttpContext】介绍了TCP数据数据Buffer承载的HTTP报文的解析,本文结合TcpServer,基于muduo实现一个简单的HttpServer。由于对协议解析不完善,它不适合写web的通用服务。
有了TcpServer的基础,加上前面博客关于HttpRequest、HttpResponse 和 HttpContext的使用,我们可以封装一个HttpServer,在收到客户端的消息后,按照HTTP协议解析Buffer,并使用回调函供用户处理Request、返回Response。
class HttpServer : noncopyable
{
public:
typedef std::function<void (const HttpRequest&, HttpResponse*)> 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_;
};
在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<HttpContext>(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
,然后发送给客户端。
muduo中自带的sample实现了几个GET请求,POST的请求可以根据实际情况进行解析。
#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]; // favicon图标数据
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 (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") // /hello,返回文本
{
resp->setStatusCode(HttpResponse::k200Ok);
resp->setStatusMessage("OK");
resp->setContentType("text/plain");
resp->addHeader("Server", "Muduo");
resp->setBody("hello, world!\n");
}
else // 不存在的URL,404 错误
{
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(