Pistache源码分析 —— ParserImpl类

2021-06-27  本文已影响0人  蟹蟹宁

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

protected

private

构造函数

public:
ParserBase::ParserBase(size_t maxDataSize)
    : buffer(maxDataSize)
    , cursor(&buffer)
{ }

设置buffer的最大值(buffer的底层是用vetcor<char>实现的),并初始化游标。
这里的最大值就是HTTP请求或响应的最大值,在Endpoint.Options.maxRequestSize_Endpoint.Options.maxResponseSize_中配置。

成员函数

void ParserBase::reset()
{
    buffer.reset();
    cursor.reset();
    currentStep = 0;
}

当解析过程发生错误或者解析完成,都要调用reset函数,以重置buffercursorcurrentStep

Step* ParserBase::step()
{
    return allSteps[currentStep].get();
}

返回当前的Step,详见Step类。

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 };

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 requeststd::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主要包括三部分:

这些字段分别定义在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()```,对解析的过程能有不错的理解深度。

上一篇下一篇

猜你喜欢

热点阅读