ParserImpl类用于解析/打包HTTP请求/响应:
template <typename Message>
class ParserImpl;
template <>
class ParserImpl<Http::Request> : public ParserBase {...};
template <>
class ParserImpl<Http::Response> : public ParserBase {...};
ParserBase类
ParserImpl是模板类,并继承自父类ParserBase,主要的工作都在父类中完成:
class ParserBase
{
public:
static constexpr size_t StepsCount = 3;
explicit ParserBase(size_t maxDataSize);
ParserBase(const ParserBase&) = delete;
ParserBase& operator=(const ParserBase&) = delete;
ParserBase(ParserBase&&) = default;
ParserBase& operator=(ParserBase&&) = default;
virtual ~ParserBase() = default;
bool feed(const char* data, size_t len);
virtual void reset();
State parse();
Step* step();
protected:
std::array<std::unique_ptr<Step>, StepsCount> allSteps;
size_t currentStep = 0;
private:
ArrayStreamBuf<char> buffer;
StreamCursor cursor;
};
成员变量
public
-
static constexpr size_t StepsCount = 3;
阶段个数,默认为3个阶段,表示HTTP请求的解析分三个阶段进行,即解析请求行、解析请求头和解析请求体。
protected
-
std::array<std::unique_ptr<Step>, StepsCount> allSteps;
为每一个阶段定义一个Step类,将在下文展开介绍Step类 -
size_t currentStep = 0;
当前所处的解析阶段
private
-
ArrayStreamBuf<char> buffer;
用于存放从TCP流中读取的数据 -
StreamCursor cursor;
流游标,表示当前处理到流的那个位置
构造函数
public:
ParserBase::ParserBase(size_t maxDataSize)
: buffer(maxDataSize)
, cursor(&buffer)
{ }
设置buffer的最大值(buffer的底层是用vetcor<char>实现的),并初始化游标。
这里的最大值就是HTTP请求或响应的最大值,在Endpoint.Options.maxRequestSize_
和Endpoint.Options.maxResponseSize_
中配置。
成员函数
-
bool feed(const char* data, size_t len);
向buffer
中填充数据,注意是在buffer现有数据的基础之上填充数据,如果填充的数据超过了设定的最大值,就会返回false,否则返回true。
这与TCP流的特点有关,因为无论是接受还是发送数据,都是分段发送的(凭借我仅存的网络知识,隐约记得有IP分段发送,然后再组装的概念),除此之外,我们也可能分批次的读取网络数据(epoll的ET触发模式),因此这就需要多次的调用feed。每次调用feed后马上执行解析操作,但是问题在于,因为我们是分段解析的,这就有可能导致某些片段不完整,对于不完整的部分需要在下一次片段到达后进行重新的解析,因此需要保留之前的数据,所以每次feed是追加型的,而非覆盖型。 virtual void reset();
void ParserBase::reset()
{
buffer.reset();
cursor.reset();
currentStep = 0;
}
当解析过程发生错误或者解析完成,都要调用reset函数,以重置buffer
、cursor
和currentStep
。
Step* step();
Step* ParserBase::step()
{
return allSteps[currentStep].get();
}
返回当前的Step,详见Step类。
-
State ParserBase::parse()
真正执行解析操作的函数
State ParserBase::parse()
{
State state;
do
{
Step* step = allSteps[currentStep].get();
state = step->apply(cursor);
if (state == State::Next)
{
++currentStep;
}
} while (state == State::Next);
// Should be either Again or Done
return state;
}
返回值表示解析的状态:
enum class State { Again,
Next,
Done };
- Again 就是上面介绍feed的时候提到的,这意味着在解析某一个字段时数据不完整,需要在重新feed获取更多数据之后,重新解析
- Next 表示当前阶段已经完全解析结束
- Done 表示所有阶段都解析完成
step->apply(cursor);
是每个阶段自己的解析方法,分阶段解析显然是个不错的方案。
ParserImpl<Http::Request> 类
template <>
class ParserImpl<Http::Request> : public ParserBase
{
public:
explicit ParserImpl(size_t maxDataSize);
void reset() override;
std::chrono::steady_clock::time_point time() const
{
return time_;
}
Request request;
private:
std::chrono::steady_clock::time_point time_;
};
ParserImpl<Http::Request> 主要添加了两个字段Request request
和std::chrono::steady_clock::time_point time_
。
time_
用于记录对象创建时或执行reset时的时间点,服务端将定期检查peer是否有请求在传输,如果超过一定的时间(由Endpoint.Options.headerTimeout_
和Endpoint.Options.bodyTimeout_
指定)没有数据传输,将会断开连接。
request
则是定义的Request对象,用于存放解析的数据,以传递给用户处理程序使用。
构造函数
Private::ParserImpl<Http::Request>::ParserImpl(size_t maxDataSize)
: ParserBase(maxDataSize)
, request()
, time_(std::chrono::steady_clock::now())
{
allSteps[0] = std::make_unique<RequestLineStep>(&request);
allSteps[1] = std::make_unique<HeadersStep>(&request);
allSteps[2] = std::make_unique<BodyStep>(&request);
}
我们用request
初始化了父类中的Step数组allSteps
。Step类将实现分阶段的解析TCP数据流,并将结果存放到request中。
Step类 RequestLineStep类
struct Step
{
explicit Step(Message* request);
virtual ~Step() = default;
virtual StepId id() const = 0;
virtual State apply(StreamCursor& cursor) = 0;
static void raise(const char* msg, Code code = Code::Bad_Request);
protected:
Message* message;
};
class RequestLineStep : public Step
{
public:
static constexpr StepId Id = Meta::Hash::fnv1a("RequestLine");
explicit RequestLineStep(Request* request)
: Step(request)
{ }
StepId id() const override { return Id; }
State apply(StreamCursor& cursor) override;
};
我们以的RequestLineStep为例,探讨一下解析的过程,因此我们主要关注State apply(StreamCursor& cursor) override;
函数
在Request类的分析中我们知道,RequestLine主要包括三部分:
- 请求方法,如 GET
- 请求的URI,如 /ping?k=v
- 协议版本,一般是http1.1
这些字段分别定义在Request.method_
,Request.resource_
&&Request.query_
,Message.version_
中,详见Request类
代码注释:
State RequestLineStep::apply(StreamCursor& cursor)
{
// 保存当前cursor的状态,如果最终的解析状态是Next,即当前step解析完成,则忽略
// 如果当前最终的解析状态是Again,即表示数据不完整,那么将直接返回,在析构函数中恢复cursor
// 这也就意味着,如果解析状态是Again,那么整个step将重新解析,这是比较简单的实现方式,但是效率略低
StreamCursor::Revert revert(cursor);
auto* request = static_cast<Request*>(message);
// Token用于记录一个字段,一个字段就是一个字符串
StreamCursor::Token methodToken(cursor);
// cursor向前走,直到遇到一个空格,因为在HTTP请求行里面是用空格来分隔的
// 如果未遇到空格,说明数据不完整,将会返回Again状态,此时会调用Revert的析构函数来恢复cursor
if (!match_until(' ', cursor))
return State::Again;
// methodToken.text() 返回获取到的字符串,并与httpMethods进行匹配
// 如果匹配成功,则给request->method_赋值,如果匹配失败,那么将抛出异常
// 异常的处理函数会直接返回400,然后调用ParserImpl.reset进行重置
auto it = httpMethods.find(methodToken.text());
if (it != httpMethods.end())
{
request->method_ = it->second;
}
else
{
raise("Unknown HTTP request method");
}
// 空格后应该是URI,不能再是空格
int n;
if (cursor.eof())
return State::Again;
else if ((n = cursor.current()) != ' ')
raise("Malformed HTTP request after Method, expected SP");
// 若没有待解析数据,则数据不完整,不足以完整解析此step,返回Again
if (!cursor.advance(1))
return State::Again;
// 同样的这里是在解析resource_,因为URI分两部分,?前的路径,和?后的查询参数,当然查询参数可有可无
StreamCursor::Token resToken(cursor);
while ((n = cursor.current()) != '?' && n != ' ')
if (!cursor.advance(1))
return State::Again;
// 给request->resource_赋值
request->resource_ = resToken.text();
// 如果存在查询参数,那么就开始解析然后给request->query_赋值,就不展开注释了
// Query parameters of the Uri
if (n == '?')
{
...
}
// SP
if (!cursor.advance(1))
return State::Again;
// 下面是解析协议版本,方法和前面一样
// HTTP-Version
StreamCursor::Token versionToken(cursor);
while (!cursor.eol())
if (!cursor.advance(1))
return State::Again;
const char* ver = versionToken.rawText();
const size_t size = versionToken.size();
if (strncmp(ver, "HTTP/1.0", size) == 0)
{
request->version_ = Version::Http10;
}
else if (strncmp(ver, "HTTP/1.1", size) == 0)
{
request->version_ = Version::Http11;
}
else
{
raise("Encountered invalid HTTP version");
}
// 最后的最后,是\n和\r,标志着此阶段完成解析
if (!cursor.advance(2))
return State::Again;
// 到此,RequestLine的解析完成,调用revert.ignore,避免在析构函数中执行对游标的恢复
revert.ignore();
// 最后返回Next状态
return State::Next;
}
结合``State ParserBase::parse()```,对解析的过程能有不错的理解深度。
网友评论