muduo_net代码剖析之Http相关类介绍

[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响应包内容)赋值,服务器并没有对客户端的请求执行回应动作。

  1. 提供了设置、获取以下属性变量的接口:statusCode_(状态响应码)、statusMessage_(状态响应码对应的文本信息)、bool closeConnection_(是否关闭连接)、body_(实体)、std::map<string, string> headers_(添加首部字段)
  2. 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_回调函数的代码应该怎么写?

  1. req:传入参数,包含了请求包的所有信息
  2. 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',
};

相关文章:

  • 2021-05-07
  • 2021-12-23
  • 2021-10-07
  • 2021-06-17
  • 2021-10-18
  • 2021-11-28
  • 2021-06-29
猜你喜欢
  • 2021-07-18
  • 2021-11-23
  • 2021-10-04
  • 2021-06-11
  • 2021-05-23
  • 2021-08-23
相关资源
相似解决方案