当前位置 博文首页 > wanggao的专栏:muduo学习笔记:net部分之Http--HttpServer

    wanggao的专栏:muduo学习笔记:net部分之Http--HttpServer

    作者:[db:作者] 时间:2021-09-09 09:40

    前面【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(