muduo源码分析之HttpServer

相关文件

muduo/net/http/*
HttpRequst:http请求类
HttpResponse: http响应类
HttpContext: http协议解析类
HttpServer: http服务器类

作用

解析http请求,用户只需根据http请求类,设置好响应类,http服务器会把响应报文发给客户端(浏览器)。

先备知识

http请求

一个典型的http请求:

    GET /hello HTTP/1.1\r\n
    Host: 192.168.91.129:8000\r\n
    Connection: keep-alive\r\n
    Upgrade-Insecure-Requests: 1\r\n
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.212 Safari/537.36 Edg/90.0.818.62\r\n
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\n
    Accept-Encoding: gzip, deflate\r\n
    Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6\r\n
    \r\n

请求报文有请求行line+请求头header+请求实体body。
上面第一行即请求行,下面的是请求头,没有body。
header和body之间有一空行。
请求行
请求方法有Get, Post, Head, Put, Delete等
协议版本:1.0、1.1
请求头
Accept:浏览器可接受的媒体(MIME)类型;
Accept-Language:浏览器所希望的语言种类
Accept-Encoding:浏览器能够解码的编码方法,如gzip,deflate等
User-Agent:告诉HTTP服务器, 客户端使用的操作系统和浏览器的名称和版本
Connection:表示是否需要持久连接,Keep-Alive表示长连接,close表示短连接

http应答

由状态行status line+响应头header+实体body组成
一个典型的http应答:

HTTP/1.1 200 OK
Content-Length: 112
Connection: Keep-Alive
Content-Type: text/html
Server: Muduo

<html><head><title>This is title</title></head><body><h1>Hello</h1>Now is 20130611 02:14:31.518462</body></html>

当然每行结尾都是有/r/n的,header和body之间有一空行。
第一行为状态行
1XX 提示信息 - 表示请求已被成功接收,继续处理
2XX 成功 - 表示请求已被成功接收,理解,接受
3XX 重定向 - 要完成请求必须进行更进一步的处理
4XX 客户端错误 - 请求有语法错误或请求无法实现
5XX 服务器端错误 - 服务器执行一个有效请求失败

使用

在muduo/net/http/tests中提供了使用示例
HttpServer_test.cc

#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(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();
	//设置应答实体body
    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);
  server.setThreadNum(numThreads);
  server.start();
  loop.loop();
}

char favicon[555] = {
  '\x89', 'P', 'N', 'G', '\xD', '\xA', '\x1A', '\xA',
  //太长,略,是一张图标的数据
};

编译运行该示例,即开启了一个http服务器
image
在浏览器地址栏输出ip地址+端口号+请求地址可访问
image
这里访问的是/地址,返回 "hello" +时间戳。

可使用wireshark抓包查看报文
image

HttpServer源码分析

HttpServer类

HttpServer基于TcpServer类,实现TcpServer的onConnection()和onMessage()回调函数。
对外给用户提供解析完http请求后的回调函数接口,即上文中的OnRequest(),在HttpServer中为httpCallback_()。
私有成员:

void onConnection(const TcpConnectionPtr& conn);
void onMessage(const TcpConnectionPtr& conn,
               Buffer* buf,
               Timestamp receiveTime);
void onRequest(const TcpConnectionPtr&, const HttpRequest&);

TcpServer server_;
HttpCallback httpCallback_;//在处理http请求的过程(onrequest)中回调此函数,对请求具体处理

1、当客户端连接时,回调OnConnection(),HttpServer给Tcpconnection设置协议解析类HttpContext。
2、当客户端发送请求,回调OnMessage(),使用HttpContext的方法解析缓冲区buffer中的请求,HttpContext将解析后的请求封装在HttpRequest类中,主要容器是map。
3、在OnMessage()中继续调用Onrequest(),将如何对请求应答交给用户回调函数httpCallback_()。
4、httpCallback_()需要传出一个HttpResponse类,Onrequest()将HttpResponse中的数据发送给客户端。

//HttpServer.cc
void HttpServer::onConnection(const TcpConnectionPtr& conn)
{
  if (conn->connected())
  {
    conn->setContext(HttpContext());//给conn绑定上下文对象,使用了boost::any,可以是任意对象
  }
}

void HttpServer::onMessage(const TcpConnectionPtr& conn,
                           Buffer* 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());//解析后的请求放在HttpRequest中,由context->request()返回
    context->reset();
  }
}

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);//response为输入输出参数
  Buffer buf;
  response.appendToBuffer(&buf);//将应答报文放进缓冲区
  conn->send(&buf);//发送给客户端
  if (response.closeConnection())
  {
    conn->shutdown();
  }
}

HttpRequest请求类

HttpContext协议解析类将解析后的数据封装在HttpRequest请求类
再回忆一遍请求报文包含什么?至少有请求行和请求头。
请求行中包含请求方法、请求地址、协议版本,如GET /hello HTTP/1.1\r\n。
请求头的数据则放进一个map<string, string>中
私有成员:

  enum Method
  {
    kInvalid, kGet, kPost, kHead, kPut, kDelete
  };
  enum Version
  {
    kUnknown, kHttp10, kHttp11
  };

Method method_; //请求方法
Version version_;/协议版本
string path_; //请求路径
string query_; //?后的数据,如GET方法带参数时的URL: /test/demo_form.php?name1=value1&name2=value2
Timestamp receiveTime_;//时间戳
std::map<string, string> headers_;//请求头

主要函数成员不过是一些get/set函数
如header的处理

//HttpRequest.h
void addHeader(const char* start, const char* colon, const char* end)
  {
    string field(start, colon);//colon为冒号:所在位置
    ++colon;
	//去除左空格
    while (colon < end && isspace(*colon))
    {
      ++colon;
    }
    string value(colon, end);
	//去除右空格
    while (!value.empty() && isspace(value[value.size()-1]))
    {
      value.resize(value.size()-1);
    }
    headers_[field] = value; //存入map
  }

  string getHeader(const string& field) const
  {
    string result;
	//查找map并返回
    std::map<string, string>::const_iterator it = headers_.find(field);
    if (it != headers_.end())
    {
      result = it->second;
    }
    return result;
  }

HttpContext协议解析类

HttpContext读取缓冲区中的字符串进行解析,基于状态机实现,依次解析请求行、请求头header、请求体body。
HttpContext数据成员包含解析状态和HttpRequest类

enum HttpRequestParseState
{
  kExpectRequestLine,//解析请求行
  kExpectHeaders,//解析headers
  kExpectBody,//解析body
  kGotAll, //解析完成
};

HttpRequestParseState state_;//请求解析状态
HttpRequest request_;//http请求

两个主要解析方法
processRequestLine()解析请求行,取出请求方法、请求路径、协议版本等信息。
parseRequest按照状态机依次解析请求报文的全部数据。

//HttpContext.cc
bool HttpContext::processRequestLine(const char* begin, const char* end)
{
  bool succeed = false;
  const char* start = begin;
  const char* space = std::find(start, end, ' ');
  if (space != end && request_.setMethod(start, space))
  {
    start = space+1;
    space = std::find(start, end, ' ');
    if (space != end)
    {
      const char* question = std::find(start, space, '?');
      if (question != space)
      {
        request_.setPath(start, question);
        request_.setQuery(question, space);//GET参数
      }
      else
      {
        request_.setPath(start, space);//路径
      }
      start = space+1;
      succeed = end-start == 8 && std::equal(start, end-1, "HTTP/1.");
      if (succeed)
      {
        if (*(end-1) == '1')
        {
          request_.setVersion(HttpRequest::kHttp11);//协议版本1.1
        }
        else if (*(end-1) == '0')
        {
          request_.setVersion(HttpRequest::kHttp10);//协议版本1.0
        }
        else
        {
          succeed = false;
        }
      }
    }
  }
  return succeed;
}

// return false if any error
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);
          state_ = kExpectHeaders; //下一步,解析headers
        }
        else
        {
          hasMore = false;
        }
      }
      else
      {
        hasMore = false;
      }
    }
	//解析headers
    else if (state_ == kExpectHeaders)
    {
      const char* crlf = buf->findCRLF();
      if (crlf)
      {
	  	//找到冒号:位置
        const char* colon = std::find(buf->peek(), crlf, ':');
        if (colon != crlf)
        {
          request_.addHeader(buf->peek(), colon, crlf);//HttpRequest类方法,添加一个header
        }
        else
        {
          // empty line, end of header
          // FIXME:
          state_ = kGotAll;//完成
          hasMore = false;//跳出while循环
        }
        buf->retrieveUntil(crlf + 2);
      }
      else
      {
        hasMore = false;
      }
    }
    else if (state_ == kExpectBody)//目前不解析body
    {
      // FIXME:
    }
  }
  return ok;
}

HttpReasponse响应类

应答类,由用户代码设置数据成员,为HttpServer提供用于发送的buffer数据。
数据成员:

std::map<string, string> headers_; //header列表
HttpStatusCode statusCode_; //状态响应码
// FIXME: add http version
string statusMessage_;//状态响应码对应的文本信息
bool closeConnection_;
string body_;

主要函数appendToBuffer(),将响应部分添加到buffer

//HttpRsponse.cc
void HttpResponse::appendToBuffer(Buffer* output) const
{
  char buf[32];
  //添加响应头
  snprintf(buf, sizeof buf, "HTTP/1.1 %d ", statusCode_);
  output->append(buf);
  output->append(statusMessage_);
  output->append("\r\n");

  if (closeConnection_)
  { 
    //短连接
    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");
  }

  //header列表
  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_);
}
posted @ 2021-06-06 00:24  零十  阅读(504)  评论(0编辑  收藏  举报