其他分享
首页 > 其他分享> > muduo学习笔记:net部分之Http--HttpServer

muduo学习笔记:net部分之Http--HttpServer

作者:互联网

前面【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<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_;
};

2、HttpServer的实现

在HttpServer构造函数中,注册TcpServer对外暴露的两个回调函数:新的连接、新消息到来。

  server_.setConnectionCallback(std::bind(&HttpServer::onConnection, this, _1));
  server_.setMessageCallback(std::bind(&HttpServer::onMessage, this, _1, _2, _3));

当客户端如浏览器连接上来,根据以前的分析可知,调用HttpServer::onConnection(), 绑定一个HttpContextTcpConnection 中的成员变量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,然后发送给客户端。

3、HttpServer测试

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(&loop, InetAddress(8000), "dummy");
  server.setHttpCallback(onRequest);
  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',
  '\x0', '\x0', '\x0', '\x10', '\x0', '\x0', '\x0', '\x10','\x8', '\x6', '\x0', '\x0', '\x0', '\x1F', '\xF3', '\xFF',
  'a', '\x0', '\x0', '\x0', '\x19', 't', 'E', 'X','t', 'S', 'o', 'f', 't', 'w', 'a', 'r',
  'e', '\x0', 'A', 'd', 'o', 'b', 'e', '\x20','I', 'm', 'a', 'g', 'e', 'R', 'e', 'a',
  'd', 'y', 'q', '\xC9', 'e', '\x3C', '\x0', '\x0',  '\x1', '\xCD', 'I', 'D', 'A', 'T', 'x', '\xDA',
  '\x94', '\x93', '9', 'H', '\x3', 'A', '\x14', '\x86',  '\xFF', '\x5D', 'b', '\xA7', '\x4', 'R', '\xC4', 'm',
  '\x22', '\x1E', '\xA0', 'F', '\x24', '\x8', '\x16', '\x16',  'v', '\xA', '6', '\xBA', 'J', '\x9A', '\x80', '\x8',
  'A', '\xB4', 'q', '\x85', 'X', '\x89', 'G', '\xB0',  'I', '\xA9', 'Q', '\x24', '\xCD', '\xA6', '\x8', '\xA4',
  'H', 'c', '\x91', 'B', '\xB', '\xAF', 'V', '\xC1',  'F', '\xB4', '\x15', '\xCF', '\x22', 'X', '\x98', '\xB',
  'T', 'H', '\x8A', 'd', '\x93', '\x8D', '\xFB', 'F',  'g', '\xC9', '\x1A', '\x14', '\x7D', '\xF0', 'f', 'v',
  'f', '\xDF', '\x7C', '\xEF', '\xE7', 'g', 'F', '\xA8',  '\xD5', 'j', 'H', '\x24', '\x12', '\x2A', '\x0', '\x5',
  '\xBF', 'G', '\xD4', '\xEF', '\xF7', '\x2F', '6', '\xEC',  '\x12', '\x20', '\x1E', '\x8F', '\xD7', '\xAA', '\xD5', '\xEA',
  '\xAF', 'I', '5', 'F', '\xAA', 'T', '\x5F', '\x9F',  '\x22', 'A', '\x2A', '\x95', '\xA', '\x83', '\xE5', 'r',
  '9', 'd', '\xB3', 'Y', '\x96', '\x99', 'L', '\x6',  '\xE9', 't', '\x9A', '\x25', '\x85', '\x2C', '\xCB', 'T',
  '\xA7', '\xC4', 'b', '1', '\xB5', '\x5E', '\x0', '\x3',  'h', '\x9A', '\xC6', '\x16', '\x82', '\x20', 'X', 'R',
  '\x14', 'E', '6', 'S', '\x94', '\xCB', 'e', 'x',  '\xBD', '\x5E', '\xAA', 'U', 'T', '\x23', 'L', '\xC0',
  '\xE0', '\xE2', '\xC1', '\x8F', '\x0', '\x9E', '\xBC', '\x9',  'A', '\x7C', '\x3E', '\x1F', '\x83', 'D', '\x22', '\x11',
  '\xD5', 'T', '\x40', '\x3F', '8', '\x80', 'w', '\xE5',  '3', '\x7', '\xB8', '\x5C', '\x2E', 'H', '\x92', '\x4',
  '\x87', '\xC3', '\x81', '\x40', '\x20', '\x40', 'g', '\x98',  '\xE9', '6', '\x1A', '\xA6', 'g', '\x15', '\x4', '\xE3',
  '\xD7', '\xC8', '\xBD', '\x15', '\xE1', 'i', '\xB7', 'C',  '\xAB', '\xEA', 'x', '\x2F', 'j', 'X', '\x92', '\xBB',
  '\x18', '\x20', '\x9F', '\xCF', '3', '\xC3', '\xB8', '\xE9',  'N', '\xA7', '\xD3', 'l', 'J', '\x0', 'i', '6',
  '\x7C', '\x8E', '\xE1', '\xFE', 'V', '\x84', '\xE7', '\x3C',  '\x9F', 'r', '\x2B', '\x3A', 'B', '\x7B', '7', 'f',
  'w', '\xAE', '\x8E', '\xE', '\xF3', '\xBD', 'R', '\xA9',  'd', '\x2', 'B', '\xAF', '\x85', '2', 'f', 'F',
  '\xBA', '\xC', '\xD9', '\x9F', '\x1D', '\x9A', 'l', '\x22',  '\xE6', '\xC7', '\x3A', '\x2C', '\x80', '\xEF', '\xC1', '\x15',
  '\x90', '\x7', '\x93', '\xA2', '\x28', '\xA0', 'S', 'j',  '\xB1', '\xB8', '\xDF', '\x29', '5', 'C', '\xE', '\x3F',
  'X', '\xFC', '\x98', '\xDA', 'y', 'j', 'P', '\x40',  '\x0', '\x87', '\xAE', '\x1B', '\x17', 'B', '\xB4', '\x3A',
  '\x3F', '\xBE', 'y', '\xC7', '\xA', '\x26', '\xB6', '\xEE',  '\xD9', '\x9A', '\x60', '\x14', '\x93', '\xDB', '\x8F', '\xD',
  '\xA', '\x2E', '\xE9', '\x23', '\x95', '\x29', 'X', '\x0',  '\x27', '\xEB', 'n', 'V', 'p', '\xBC', '\xD6', '\xCB',
  '\xD6', 'G', '\xAB', '\x3D', 'l', '\x7D', '\xB8', '\xD2',  '\xDD', '\xA0', '\x60', '\x83', '\xBA', '\xEF', '\x5F', '\xA4',
  '\xEA', '\xCC', '\x2', 'N', '\xAE', '\x5E', 'p', '\x1A',  '\xEC', '\xB3', '\x40', '9', '\xAC', '\xFE', '\xF2', '\x91',
  '\x89', 'g', '\x91', '\x85', '\x21', '\xA8', '\x87', '\xB7',  'X', '\x7E', '\x7E', '\x85', '\xBB', '\xCD', 'N', 'N',
  'b', 't', '\x40', '\xFA', '\x93', '\x89', '\xEC', '\x1E',  '\xEC', '\x86', '\x2', 'H', '\x26', '\x93', '\xD0', 'u',
  '\x1D', '\x7F', '\x9', '2', '\x95', '\xBF', '\x1F', '\xDB',  '\xD7', 'c', '\x8A', '\x1A', '\xF7', '\x5C', '\xC1', '\xFF',
  '\x22', 'J', '\xC3', '\x87', '\x0', '\x3', '\x0', 'K',  '\xBB', '\xF8', '\xD6', '\x2A', 'v', '\x98', 'I', '\x0',
  '\x0', '\x0', '\x0', 'I', 'E', 'N', 'D', '\xAE',  'B', '\x60', '\x82',
};

在网页中使用调试模式,打开连接 “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

标签:muduo,Http,--,0.0,resp,TcpServer,HttpServer,const,x0
来源: https://blog.csdn.net/wanggao_1990/article/details/119653235