跳转至

IHttpCore路由返回类型 Response类型

在IHttpCore中,我们定义了一系列的Response 类型作为 函数的返回值类型,用户可以使用这些类型来返回数据。

Response 类型概述

这里还有返回 尾缀的概念, 和 $json: 这个东西。

在我们上述简单类型的返回过程中,我们可以返回一系列的类型,上面的类型有一系列的不足之处,比如他不能设置 返回状态,不能设置一系列Header 状态,不能添加cookie 类型,如果我们在数据处理的过程中出现异常了,也没有办法向客户端返回一个异常指示。所以我们引入了 Response 类型的概念。

Response 类型的命名是以 I 开头, 以 Response 结尾。比如 IFileResponse, IHtmlResponse等等。用户也可以定义自己的返回类型。IHttpCore 也提供一个插件 INodyResponse, 这个插件支持用户返回一个Html模版名称 和一组用于渲染的数据,模版和渲染数据结合一起,生成一个完整的 html内容再返回给用户。

同时我们的基础的返回类型在实际的解析过程中,也会被包装成相应的Response 类型。比如当返回类型是 IJson 类型的时候,IHttpCore 在对返回类型的解析时,会将 IJson 的值包装成 IJsonResponse 类型。我们的 IHttpStatus 类型会被包装成 IStatusResponse 类型。QString 一般会被包装成 IPlainTextRespponse。之所以是一般,是因为字符串类型会有一些特殊处理的内容,在这里我们会列举出来。

有一点就是我们的 Response 类型前缀是 I,没有带上 我们的库限制 Http,IHtmlResponse 不是 IHttpHtmlResponse。这样做固然打破的我们的命名限制,但好处就是开发者在写命名的时候可以缩短代码。而且http 模块是在之后使用最频繁的模块,这便值得我们为此这样命名。如果之后有其他的模块需要 response, 则此时我们的Response 就需要添加上前缀了。比如之后有一个模块是 Abc, 那么模块的Response 就应该是 IAbcFileResponse, 而不是 IFileResponse。

IHttpResponseInterface和 IHttpResponseWare

IHttpResponseInterface 是所有的 Response 类型的CRTP基类,如果开发者想写自己的 Response,则可以继承该基类。关于扩展Repsonse的内容,可以参考下面的内容。

IHttpResponseWare 是 IHttpResponseInterface 的一个父类。IResponseWare是具体存储返回内容的基类。在IHttpResponseWare 中,可以设置 mime,status, header, session, content 等一系列返回给客户端的内容。

尾缀类型

在IHttpCore 中,针对Response 类型,我们定义了很多的字符串字面量。比如:

IFileResponse operator"" _file(const char* str, size_t size);
IHtmlResponse operator"" _html(const char* str, size_t size);

这些字面量可以构造对应的 Response 类型。这样在返回的过程中,用户可以直接使用字面量后缀来构造对象。比如:

1
2
3
4
$GetMapping(fileResponse)
IFileResponse fileResponse(){
    return ":/resource/defaultWebConfig.json"_file;
}

前缀类型

函数返回一个 String 类型的时候,IHttpCore 定义了一系列的前缀,这些前缀可以将String的数据转换成其他的类型,如文件类型,html 类型等等一系列的类型。比如:

1
2
3
4
5
$GetMapping(fileResponse)
QString fileResponse()
{
    return "$file::/resource/defaultWebConfig.json";
}

这个函数返回了 QString 的类型的数据"$file::/resource/defaultWebConfig.json"。这个数据在实际解析的时候会拆分成两个部分,$file::/resource/defaultWebConfig.json。前面的 $file: 一个数据类型前缀,这个前缀会在解析的时候判断和匹配。所以这条返回的数据会被解析成一个 IFileResponse 类型,并使用 :/resource/defaultWebConfig.json 来构造这个IFileResponse。这个就是前缀类型。

再举一个例子:

1
2
3
4
5
$GetMapping(htmlResponse)
IString htmlResponse()
{
    return "$html:<h1>hello world</h>";
}

这个请求在实际响应的过程中,会构建一个 IHtmlResponse, 内容是 <h1>hello world</h>,返回给客户端的Content-Type 是 text/html; charset=UTF-8,而不是 text/plain

返回 Invalid

返回 Status

下面说明我们在框架中定义的各类的Response。

IFileResponse

IFileResponse 支持用户将一个文件返回给客户端。

1
2
3
4
$GetMapping(fileResponse)
IFileResponse fileResponse(){
    return ":/resource/defaultWebConfig.json";
}

IFileResponse 会在解析的过程中尝试查找一个对应的mime类型,如果找到了,就使用查找的mime,如果没有找到,则使用application/octet-stream, 表示这个是一个二进制流文件。关于mime 类型,请参考 http/mime 相关的内容。

用户可以返回Qt 的资源文件,也可以Qt 的resource 资源文件,这些文件可以和 代码一起编译到可运行的程序里面, 这些文是以 :/ 开头的文件,如上面的代码所示。用户也可以给定一个相对或者绝对的地址,这个地址指向一个文件。

IFileResponse 的类型定义如下:

class IFileResponse : public IResponseInterface<IFileResponse>
{
    $AsResponse(IFileResponse)
public:
    using IResponseInterface::IResponseInterface;
//    using IResponseInterface::operator [];

public:
    IFileResponse() = default;
    IFileResponse(const char* data);
    IFileResponse(const QString& path);
    IFileResponse(IString&& path);
    IFileResponse(const IString& path);
    IFileResponse(const std::string& path);

public:
    void enableContentDisposition();

public:
    virtual std::string prefixMatcher() final;

private:
    IFileResponseContent* m_fileResponseContent{};
};

IFileResponse operator"" _file(const char* str, size_t size);

我们的构造函数没有添加 explict, 所以用户可以直接返回一个字符串类型,IFileResponse 会自动构造,并解析相关内容。

contentDisposition

注意类中有一个 enableContentDisposition 函数。这个函数可以在返回的时候设置进去。如果调用该函数,发送给客户端的头中将添加 Content-Disposition内容的 Header。该Header 的作用是在客户端如浏览器请求的时候,会直接将文件保存下来,而不会在客户端进行显示。

1
2
3
4
5
6
$GetMapping(fileResponse)
IFileResponse fileResponse(){
    IFileResponse data(":/resource/defaultWebConfig.json"_file);
    data.enableContentDisposition();
    return data;
}

文件失效问题

如果用户提供的地址当中并没有文件,则会返回 IHttpNotFoundInvalid 也就是 500 的状态。这里之所以不返回404 状态,是因为在请求过程中匹配到了相应的URL, 但是处理函数内部指向的文件失效,所以这里是 服务器响应的问题,而不易url 查找的问题。

前缀

IFileResponse 的前缀是 $file:, 也就是 prefixMatcher() 所定义的内容。

1
2
3
4
$GetMapping(fileResponse)
QString fileResponse(){
    return "$file::/resource/defaultWebConfig.json";
}

后缀

在第26行定义一个 _file 的 字面量。这个字面量将 const char* 的内容转换成 IFileResponse。所以上面的例子也可以写成

1
2
3
4
$GetMapping(fileResponse)
IFileResponse fileResponse(){
    return ":/resource/defaultWebConfig.json"_file;
}

IHtmlResponse

IHtmlResponse 支持用户返回 html 类型的数据,数据返回到客户端会被解析成 html 并进行后续的操作,比如渲染等。IHtmlResponse 返回状态是 200 OK,返回的类型是text/html; charset=UTF-8

IHtmlResponse 的声明如下:

class IHtmlResponse : public IHttpResponseInterface<IHtmlResponse>
{
    $AsResponse(IHtmlResponse)
public:
    using IHttpResponseInterface::IHttpResponseInterface;

public:
    IHtmlResponse();
    IHtmlResponse(const QString& data);
    IHtmlResponse(std::string&&);
    IHtmlResponse(const std::string&);
    IHtmlResponse(QByteArray&&);
    IHtmlResponse(const QByteArray&);

public:
    virtual std::string prefixMatcher() final;
};

IHtmlResponse operator"" _html(const char* str, size_t size);

构造

在上述的代码中,我们可以看见 IHtmlResponse 可以被 String 类型进行隐式构造。所以用户可以返回一个String 类型或者字符串即可。

1
2
3
4
$GetMapping(htmlResponse)
IHtmlResponse htmlResponse(){
    return "<h1>hello world</h1>";
}

前缀

IHtmlResponse 的前缀是 $html:

1
2
3
4
$GetMapping(htmlResponsePrefix)
QString htmlResponsePrefix(){
    return "$html:<h1>hello world</h1>";
}

后缀

IHtml 的后缀类型字面量是 _html

1
2
3
4
$GetMapping(htmlResponseSurfix)
IHtmlResponse htmlResponseSurfix(){
    return "<h1>hello world</h1>"_html;
}

IJsonResponse

IJsonResponse 用于返回json数据给用户,返回的状态是 200 OK, 返回的 mime 是application/json; charset=UTF-8

类声明

IJsonResponse 的声明如下:

class IJsonResponse : public IHttpResponseInterface<IJsonResponse>
{
    $AsResponse(IJsonResponse)
public:
    using IHttpResponseInterface::IHttpResponseInterface;
//    using IResponseInterface::operator [];

public:
    IJsonResponse();
    IJsonResponse(const char*);
    IJsonResponse(const QString&);

    IJsonResponse(IJson&&);
    IJsonResponse(const IJson&);

    IJsonResponse(std::string&&);
    IJsonResponse(const std::string&);

    IJsonResponse(IString&&);
    IJsonResponse(const IString&);

    IJsonResponse(QByteArray&&);
    IJsonResponse(const QByteArray&);

    template<typename T>
    IJsonResponse(T value);

public:
    virtual std::string prefixMatcher() final;
};

IJsonResponse operator"" _json(const char* str, size_t size);

构造参数

String 类型构造

在上面的代码中,可以看到可以通过传入 String 类型数据,IJson类型数据来 构造一个IJsonResponse 对象。如下:

1
2
3
4
5
6
7
8
9
$GetMapping(jsonResponseArray)
IJsonResponse jsonResponseArray(){
    return R"(["apple", "banana", "cherry"])";
}

$GetMapping(jsonResponseObject)
IJsonResponse jsonResponseObject(){
    return R"({"name": "John", "age": 30, "city": "New York"})";
}

注意一点,这里如果构造的内容是 String类型 (QString, std::string, const char*, QByteArray)的话,IJsonResponse 是不会检查数据内容是否是正确,会直接将传入的String 数据发送给客户端。也就是说,如果开发者构造 IJsonResponse 的字符串参数是一个非法的json 数据的话,客户端也将接收到一个非法的 json 数据。所以这里开发者需要保证 json 数据的正确性,完整性。

IJson 构造

用户可以传入一个 IJson 对象来构造 IJsonResponse

1
2
3
4
$GetMapping(jsonTypeResponse)
IJsonResponse jsonTypeResponse(){
    return IJson::parse(R"({"name": "John", "age": 30, "city": "New York"})");
}

模版构造

在上述的代码中,定义了一个模版构造函数,用户可以通过这个构造函数传递 Bean/复合类型的数据给 IJsonResponse, IJsonResponse 在构造函数内将对象转换成 json 数据,返回给客户端。

// 返回 {'index': 30, 'name': 'yuekeyuan'}
$GetMapping(jsonBean)
IJsonResponse jsonBean(){
    return MyBean(30, "yuekeyuan");
}

// 返回 [{'index': 30, 'name': 'yuekeyuan'}, {'index': 2, 'name': 'yueqichu'}]
$GetMapping(jsonBeans)
IJsonResponse jsonBeans(){
    return QList<MyBean>{
        MyBean(30, "yuekeyuan"),
        MyBean(2, "yueqichu")
    };
}

// 返回 [1, 2, 4, 5]
$GetMapping(jsonIntList)
IJsonResponse jsonIntList(){
    return QList<int>{
        1, 2, 4, 5
    };
}

具体有哪些类型可以转换,可以参考 IHttpCore 路由返回类型 基础类型 的文档。

前缀

IJsonResponse 的前缀是 $json: 用户可以通过该前缀将一个String 类型转换成 json 类型返回给用户

1
2
3
4
5
$GetMapping(jsonPrefix)
QString jsonPrefix()
{
    return R"($json:{"name": "John", "age": 30, "city": "New York"})";
}

后缀

用户可以通过 _json 字面量后缀将一个字符串转换成 IJsonResponse

1
2
3
4
5
$GetMapping(jsonSuffix)
IJsonResponse jsonSuffix()
{
    return "{\"name\": \"John\", \"age\": 30, \"city\": \"New York\"}"_json;
}

IBytesResponse

IBytesResponse 将一段数据返回给请求端,状态是 200 OK, Content-Type是 application/octet-stream, 表示这个是一段字节流。他的定义如下:

class IBytesResponse : public IHttpResponseInterface<IBytesResponse>
{
    $AsResponse(IBytesResponse)
public:
    using IHttpResponseInterface::IHttpResponseInterface;
//    using IResponseInterface::operator [];

public:
    IBytesResponse();
    IBytesResponse(const char* data);
    IBytesResponse(const QString& data);

    IBytesResponse(QByteArray &&data);
    IBytesResponse(const QByteArray &data);

    IBytesResponse(std::string&&);
    IBytesResponse(const std::string&);

    IBytesResponse(IString&&);
    IBytesResponse(const IString&);

public:
    virtual std::string prefixMatcher() final;
};

IBytesResponse operator"" _bytes(const char* str, size_t size);

构造

如上所示,IBytesResponse 传入构造函数的是一系列的 String 类型。这些数据传入构造函数,然后被发送给请求方。

注意一点,构造函数支持 QString 类型的数据。在实际的处理中,IHttpCore 会将 QString 类型的数据通过 toUtf8() 这个函数转换成 QByteArray 类型。也就是说,这里我们默认的数据类型是 utf-8 类型的数据。如果用户传递的数据不是utf-8类型的,则需要用户自己手动转换。

举例如下:

1
2
3
4
5
6
7
8
9
$GetMapping(bytes)
IBytesResponse bytes(){
    return "hello world";
}

$GetMapping(bytesQString)
IBytesResponse bytesQString(){
    return QString(QStringLiteral("岳克远"));
}

前缀

IBytesResponse 的前缀是 $bytes:

1
2
3
4
$GetMapping(bytesPrefix)
IString bytesPrefix(){
    return "$bytes:hello world";
}

后缀

IBytesResponse 的后缀是 _bytes

1
2
3
4
$GetMapping(bytesSuffix)
IBytesResponse bytesSuffix(){
    return "hello world"_bytes;
}

IPlainTextResponse

IPlainTextResponse 是用于返回一个 text/plain 类型数据的类型,返回状态是 200 OK。他的类型声明如下:

class IPlainTextResponse : public IHttpResponseInterface<IPlainTextResponse>
{
    $AsResponse(IPlainTextResponse)
public:
    using IHttpResponseInterface::IHttpResponseInterface;
//    using IResponseInterface::operator [];

public:
    IPlainTextResponse();
    virtual ~IPlainTextResponse() = default;

    IPlainTextResponse(const char* value);
    IPlainTextResponse(const QString& value);

    IPlainTextResponse(std::string&& value);
    IPlainTextResponse(const std::string& value);

    IPlainTextResponse(QByteArray&& value);
    IPlainTextResponse(const QByteArray& value);

    IPlainTextResponse(IString&& value);
    IPlainTextResponse(const IString& value);

public:
    virtual std::string prefixMatcher() final;
};

IPlainTextResponse operator"" _text(const char* str, size_t size);

构造

IPlainTextResponse 可以使用String 类型进行数据构造,示例代码如下:

1
2
3
4
5
6
7
8
9
$GetMapping(plain)
IPlainTextResponse plain(){
    return "hello world";
}

$GetMapping(plainIString)
IPlainTextResponse plainIString(){
    return IString("hello world");
}

前缀

IPlainTextResponse 的前缀是 $text:, 示例如下:

1
2
3
4
$GetMapping(plainPrefix)
QString plainPrefix(){
    return "$text:hello world";
}

后缀

IPlainTextResponse 的字面量后缀是 _text, 示例如下:

1
2
3
4
$GetMapping(plainSuffix)
IPlainTextResponse plainSuffix(){
    return "hello world"_text;
}

IRedirectResponse

在IHttpCore中支持服务重定向的功能,开发者可以手动写状态码,写 header 中的 location内容,也可以使用 IRedirectResponse 这个Response 类。

IRedirectResponse 的返回状态是 302 FOUND。返回内容不存在,但是会在Header 中添加 Location 字段。IRedirectResponse 的声明如下:

class IRedirectResponse : public IHttpResponseInterface<IRedirectResponse>
{
    $AsResponse(IRedirectResponse)
public:
    using IHttpResponseInterface::IHttpResponseInterface;
//    using IResponseInterface::operator [];

public:
    IRedirectResponse();
    IRedirectResponse(const char* data);
    IRedirectResponse(const QString &path);
    IRedirectResponse(const std::string& path);
    IRedirectResponse(const QByteArray&& path);
    IRedirectResponse(const IString&& path);

public:
    virtual std::string prefixMatcher() final;
    void updateLocationPath();

private:
    QString m_redirectPath;
};

构造

IRedirectResponse 可以使用 IString构造。 IString 的内容必须是合法的路径格式。IRedirectResponse 不会检查路径的内容是否合法,所以开发者需要主动保证路径的合法性。示例如下:

1
2
3
4
$GetMapping(redirect)
IRedirectResponse redirect(){
    return "http://www.baidu.com";
}

在上述的示例中,客户端请求 /redirect 路径的时候,服务器端发送了 redirect 的要求给客户端,重定向的地址是 http://www.baidu.com。客户端会随之请求该路径的内容。

前缀

IRedirectResponse 的前缀是 $redirect: 。示例如下:

1
2
3
4
$GetMapping(redirectPrefix)
QString redirectPrefix(){
    return "$redirect:http://www.baidu.com";
}

后缀

IRedirectResponse 的后缀字面量 是 _redirect。示例如下:

1
2
3
4
$GetMapping(redirect)
IRedirectResponse redirectSuffix(){
    return "http://www.baidu.com"_redirect;
}

嵌套使用

IRedirectResponse 有一个非常方便的地方是该类可以返回给任意一个Response 类型。当一个函数在响应的过程中,发现该处理函数不满足条件的时候,就可以直接返回 IRedirectResponse 类型的数据给 Response。这样也可以返回数据,示例如下:

1
2
3
4
5
6
7
8
9
$GetMapping(redirectFromText)
IPlainTextResponse redirectFromText(){
    return IRedirectResponse("http://www.baidu.com");
}

$GetMapping(redirectFromJson)
IJsonResponse redirectFromJson(){
    return "http://www.baidu.com"_redirect;
}

上述不论 IPlainTextResponse 或者 IJsonResponse 或者其他类型的reponse, 在请求执行的时候都会重定位到 http://www.baidu.com 这个路径。

IStatusResponse

IStatusResponse 返回一个状态码给请求者。如下是他的声明:

class IStatusResponse : public IHttpResponseInterface<IStatusResponse>
{
    $AsResponse(IStatusResponse)
public:
    using IHttpResponseInterface::IHttpResponseInterface;
//    using IResponseInterface::operator [];

public:
    IStatusResponse() = default;
    IStatusResponse(const QString&);
    IStatusResponse(const std::string&);
    IStatusResponse(int code, const QString& errorMsg="");
    IStatusResponse(IHttpStatus status, const QString& errorMsg="");

public:
    virtual std::string prefixMatcher() final;
};

IStatusResponse operator"" _status(unsigned long long int);

构造

构造一个 IStatusResponse, 第一个参数是状态码,第二个参数是可选的,是附带的信息。

注意这里的 status 不仅仅可以是 RFC 协议规定的 状态码,也可以是用户自定义的状态码。但是对于用户自定义的状态码而言,他的状态的解释是 UNKNOWN。例如,如果用户返回给的一个code 是 600, 这个状态码并没有被 RFC协议所规定和支持。那么他的响应首行可以是 HTTP/1.1 600 UNKNOWN\r\n。这种机制给用户提供一定的便捷性,用户自定义状态码。

当用户的第二个参数有内容的时候,也就是说用户想给当前的status 一个内容,这个内容的mime 是 text/plain

如果传入的参数只有一个 String 类型的话,则这个String 类型所存储的内容必须是数字字符串。这样就能转换成 IHttpStatus。

示例如下:

1
2
3
4
$GetMapping(status)
IStatusResponse status(){
    return {500, "hello world"};
}

前缀

IStatusResponse 的前缀是 $staus: 示例如下:

1
2
3
4
$GetMapping(statusPrefix)
QString statusPrefix(){
    return "$status:400";
}

后缀

IStatusResponse 的后缀是 _status 。示例如下:

1
2
3
4
$GetMapping(statusSuffix)
IStatusResponse statusSuffix(){
    return 400_status;
}

嵌套使用

IStatusResponse 可以作为其他的数据的返回值。比如,函数原本的返回类型是 IJsonResponse。 但在处理过程中发现有 错误,我想返回500 的错误,这个时候可以使用 IStatusResponse 来指定错误。下面是示例

1
2
3
4
5
6
7
8
9
$GetMapping(statusFromJson)
IJsonResponse statusFromJson(){
    return IStatusResponse(404);
}

$GetMapping(statusFromBytes)
IBytesResponse statusFromBytes(){
    return 500_status;
}

Invalid 类型

在系统错误处理组件中,我们引入了 Invalid 类型, 具体内容可以参考 http/invalid。invalid 的目的是解决http协议请求响应的过程中的各种错误问题。开发者可以直接返回预定义的 Invalid 对象来处理函数处理过程中的invaild 对象。Invalid 在实际的应用中比 IStatusResponse 更加好用,方便。

如下是示例代码:

1
2
3
4
5
6
7
8
9
$GetMapping(notFoundInvalid)
IFileResponse notFoundInvalid(){
    return IHttpNotFoundInvalid("file not found");
}

$GetMapping(badRequest)
IJsonResponse badRequest(){
    return IHttpBadRequestInvalid("json value error");
}

直接返回 Invalid 可以在处理http 请求中对错误处理有更灵活的响应,降低编程的难度。

用户如何扩展自定义的类型

如果上述的类型不能够满足开发者的需求,开发者也可以自定义数据类型用于返回数据。