特征

下面是任何基于EVE的API都可以公开的主要功能列表。

对 REST 的重视

EVE项目旨在提供尽可能最好的符合REST的API实现。基本 REST 像这样的原则 关注点分离无状态分层系统缓存能力均匀界面 在设计核心API时一直在考虑。

全方位的积垢操作

API可以支持 CRUD 操作。在同一个API中,一个端点可以访问只读资源,另一个端点可以完全编辑资源。下表显示了EVE通过REST实现CRUD的过程:

行动

HTTP动词

语境

创造

POST

Collection

创造

PUT

文件

替换

PUT

文件

快点,头

收集/文件

更新

PATCH

文件

删除

DELETE

收集/文件

重写HTTP方法

作为不支持这些方法的奇数客户端的回退,API将很高兴地 X-HTTP-Method-Override 请求。例如,不支持 PATCH 方法可以发送 POST 请求 X-HTTP-Method-Override: PATCH 标题。然后,API将执行 PATCH ,重写原始请求方法。

可自定义资源终结点

默认情况下,EVE将使已知的数据库集合可用作资源端点(REST语言中的持久标识符)。所以一个数据库 people 我们将在 example.com/people API终结点。但是,您可以自定义URI,这样API端点就可以变成,比如, example.com/customers/overseas .考虑以下请求:

$ curl -i http://myapi.com/people
HTTP/1.1 200 OK

响应有效负载将如下所示:

{
    "_items": [
        {
            "firstname": "Mark",
            "lastname": "Green",
            "born": "Sat, 23 Feb 1985 12:00:00 GMT",
            "role": ["copy", "author"],
            "location": {"city": "New York", "address": "4925 Lacross Road"},
            "_id": "50bf198338345b1c604faf31",
            "_updated": "Wed, 05 Dec 2012 09:53:07 GMT",
            "_created": "Wed, 05 Dec 2012 09:53:07 GMT",
            "_etag": "ec5e8200b8fa0596afe9ca71a87f23e71ca30e2d",
            "_links": {
                "self": {"href": "people/50bf198338345b1c604faf31", "title": "person"},
            },
        },
        ...
    ],
    "_meta": {
        "max_results": 25,
        "total": 70,
        "page": 1
    },
    "_links": {
        "self": {"href": "people", "title": "people"},
        "parent": {"href": "/", "title": "home"}
    }
}

这个 _items 列表包含请求的数据。除了它自己的字段之外,每个项目还提供一些重要的附加字段:

描述

_created

项目创建日期。

_updated

项目上次更新时间。

_etag

ETag,用于并发控制和条件请求。

_id

唯一项键,也需要访问单个项端点。

这些附加字段由API自动处理(客户端在添加/编辑资源时不需要提供这些字段)。

这个 _meta 字段提供分页数据,只有在以下情况下才会出现 分页 已启用(默认情况下)。这个 _links 列表提供 HATEOAS 指令。

子资源

端点支持子资源,因此您可以拥有如下内容: people/<contact_id>/invoices .当设置 url 规则对于此类终结点,您将使用regex并为其分配字段名:

invoices = {
    'url': 'people/<regex("[a-f0-9]{24}"):contact_id>/invoices'
    ...

然后,转到以下端点:

people/51f63e0838345b6dcd7eabff/invoices

将导致按如下方式查询基础数据库:

{'contact_id': '51f63e0838345b6dcd7eabff'}

而这个:

people/51f63e0838345b6dcd7eabff/invoices?where={"number": 10}

查询方式如下:

{'contact_id': '51f63e0838345b6dcd7eabff', "number": 10}

请注意,在设计您的API时,大多数时候您可以不借助子资源就离开。在上面的示例中,通过简单地公开 invoices 客户端可以这样查询的终结点:

invoices?where={"contact_id": 51f63e0838345b6dcd7eabff}

invoices?where={"contact_id": 51f63e0838345b6dcd7eabff, "number": 10}

这主要是一种设计选择,但请记住,在启用单个文档端点时,可能会导致性能下降。此合法的GET请求:

people/<contact_id>/invoices/<invoice_id>

将导致在数据库上查找两个字段。这不是理想的,也不是真正需要的,因为 <invoice_id> 是一个唯一的字段。相反,如果您有一个简单的资源端点,文档查找将在单个字段上进行:

invoices/<invoice_id>

支持子资源的端点在 DELETE 操作。A DELETE 到以下端点:

people/51f63e0838345b6dcd7eabff/invoices

将导致删除所有符合以下查询的文档:

{'contact_id': '51f63e0838345b6dcd7eabff'}

因此,对于子资源端点,只删除满足端点语义的文档。这与标准行为不同,但是集合端点上的删除操作将导致删除集合中的所有文档。

另一个例子。A DELETE 到以下项终结点:

people/51f63e0838345b6dcd7eabff/invoices/1

将导致删除以下查询匹配的所有文档:

{'contact_id': '51f63e0838345b6dcd7eabff', "<invoice_id>": 1}

这种行为支持典型的树结构,其中仅资源的ID本身不一定是主键。

可自定义的多项目终结点

资源可以或不能公开单个项终结点。API用户可以访问 peoplepeople/<ObjectId>people/Doe ,但仅限于 /works .当您授予对项目端点的访问权限时,您可以定义最多两个查找,这两个查找都是用regex定义的。第一个将是主端点,并与数据库主键结构(即 ObjectId 在MongoDB数据库中)。

$ curl -i http://myapi.com/people/521d6840c437dc0002d1203c
HTTP/1.1 200 OK
Etag: 28995829ee85d69c4c18d597a0f68ae606a266cc
Last-Modified: Wed, 21 Nov 2012 16:04:56 GMT
...

第二个字段是可选的和只读的,将与具有唯一值的字段匹配,因为EVE无论如何只检索第一个匹配项。

$ curl -i http://myapi.com/people/Doe
HTTP/1.1 200 OK
Etag: 28995829ee85d69c4c18d597a0f68ae606a266cc
Last-Modified: Wed, 21 Nov 2012 16:04:56 GMT
...

由于我们访问的是同一个项目,因此在这两种情况下,响应负载看起来都是这样的:

{
    "firstname": "John",
    "lastname": "Doe",
    "born": "Thu, 27 Aug 1970 14:37:13 GMT",
    "role": ["author"],
    "location": {"city": "Auburn", "address": "422 South Gay Street"},
    "_id": "50acfba938345b0978fccad7"
    "_updated": "Wed, 21 Nov 2012 16:04:56 GMT",
    "_created": "Wed, 21 Nov 2012 16:04:56 GMT",
    "_etag": "28995829ee85d69c4c18d597a0f68ae606a266cc",
    "_links": {
        "self": {"href": "people/50acfba938345b0978fccad7", "title": "person"},
        "parent": {"href": "/", "title": "home"},
        "collection": {"href": "people", "title": "people"}
    }
}

如您所见,项目端点提供了自己的 HATEOAS 指令。

请注意

根据REST原则,资源项应该只有一个唯一标识符。EVE坚持为每个项目提供一个默认端点。添加第二个端点是一个应该仔细考虑的决定。

考虑我们上面的例子。即使没有 people/<lastname> 端点,客户机始终可以通过按姓氏查询资源端点来检索人员: people/?where={{"lastname": "Doe"}} .事实上,整个例子就是fubar,因为可能有多个人共享同一个姓氏,但是你明白了。

过滤

资源终结点允许使用者检索多个文档。支持查询字符串,允许过滤和排序。同时支持本机mongo查询和python条件表达式。

这是我们要的所有文件在哪里 lastname 价值是 Doe

http://myapi.com/people?where={"lastname": "Doe"}

curl 您可以这样做:

$ curl -i -g http://myapi.com/people?where={%22lastname%22:%20%22Doe%22}
HTTP/1.1 200 OK

可以对嵌入的文档字段进行筛选:

http://myapi.com/people?where={"location.city": "San Francisco"}

日期字段也很容易查询:

http://myapi.com/people?where={"born": {"$gte":"Wed, 25 Feb 1987 17:00:00 GMT"}}

日期值应符合RFC1123。如果需要不同的格式,可以更改 DATE_FORMAT 设置。

一般来说,你会发现 MongoDB queries “只是工作”。如果你需要它, MONGO_QUERY_BLACKLIST 允许您黑名单不需要的操作员。

原生python语法的工作方式如下:

$ curl -i http://myapi.com/people?where=lastname=="Doe"
HTTP/1.1 200 OK

这两种语法都允许使用条件运算符和逻辑运算符和/或运算符,不管它们是嵌套的还是组合的。

默认情况下,在所有文档字段上启用筛选器。但是,API维护人员可以选择全部禁用它们和/或白名单允许的它们(请参见 ALLOWED_FILTERS 在里面 全局配置 )。如果通过查询非索引字段来抓取或担心DBDOS攻击是一个问题,那么白名单允许的过滤器就是解决之道。

您还可以选择根据资源的架构验证传入的筛选器,并在任何筛选器无效时拒绝应用该筛选,方法是使用 VALIDATE_FILTERING 系统设置(参见 全局配置

漂亮的印刷

通过指定一个名为 pretty

$ curl -i http://myapi.com/people?pretty
HTTP/1.1 200 OK

{
    "_items": [
        {
            "_updated": "Tue, 19 Apr 2016 08:19:00 GMT",
            "firstname": "John",
            "lastname": "Doe",
            "born": "Thu, 27 Aug 1970 14:37:13 GMT",
            "role": [
                "author"
            ],
            "location": {
                "city": "Auburn",
                "address": "422 South Gay Street"
            },
            "_links": {
                "self": {
                    "href": "people/5715e9f438345b3510d27eb8",
                    "title": "person"
                }
            },
            "_created": "Tue, 19 Apr 2016 08:19:00 GMT",
            "_id": "5715e9f438345b3510d27eb8",
            "_etag": "86dc6b45fe7e2f41f1ca53a0e8fda81224229799"
        },
        ...
    ]
}

分选

也支持排序:

$ curl -i http://myapi.com/people?sort=city,-lastname
HTTP/1.1 200 OK

将返回按城市排序然后按姓氏(降序)排序的文档。如您所见,如果需要颠倒字段的排序顺序,只需在字段名称前面加上一个减号。

MongoDB数据层还支持本机MongoDB语法:

http://myapi.com/people?sort=[("lastname", -1)]

翻译成以下内容 curl 请求:

$ curl -i http://myapi.com/people?sort=[(%22lastname%22,%20-1)]
HTTP/1.1 200 OK

将返回按姓氏降序排序的文档。

默认情况下启用排序,可以在全局和/或资源级别禁用排序(请参见 SORTING 在里面 全局配置sorting 在里面 域配置 )。也可以在每个API端点设置默认排序(请参见 default_sort 在里面 域配置

请注意

始终使用双引号括起字段名和值。使用单引号将导致 400 Bad Request 响应。

分页

默认情况下,启用资源分页以提高性能并保留带宽。当使用者请求资源时,将提供与查询匹配的前n个项目,并提供到后续/以前页面的链接和响应。默认和最大页面大小是可自定义的,用户可以通过查询字符串请求特定的页面:

$ curl -i http://myapi.com/people?max_results=20&page=2
HTTP/1.1 200 OK

当然,您可以混合所有可用的查询参数:

$ curl -i http://myapi.com/people?where={"lastname": "Doe"}&sort=[("firstname", 1)]&page=5
HTTP/1.1 200 OK

可以禁用分页。请注意,为了清楚起见,上述示例没有正确转义。如果使用 curl ,请参阅中提供的示例 过滤 .

HATEOAS

作为应用程序状态引擎的超媒体 (HATEOAS) 默认情况下启用。每个GET响应包括 _links 第节。链接提供了他们的详细信息 relation 相对于被访问的资源,以及 title .然后,客户机可以使用关系和标题动态更新其UI,或者在不事先知道API结构的情况下导航该API。一个例子:

{
    "_links": {
        "self": {
            "href": "people",
            "title": "people"
        },
        "parent": {
            "href": "/",
            "title": "home"
        },
        "next": {
            "href": "people?page=2",
            "title": "next page"
        },
        "last": {
            "href": "people?page=10",
            "title": "last page"
        }
    }
}

到API主页(API入口点)的GET请求将提供指向可访问资源的链接列表。从那里,任何客户机都可以通过跟踪每个响应提供的链接来导航API。

hateoas链接始终与API入口点相关,因此如果您的API主页位于 examples.com/api/v1 , the self 上面例子中的链接意味着 终结点位于 examples.com/api/v1/people .

请注意 nextpreviouslastrelated 只有在适当的时候才包括项目。

禁用hateoas

可以在API和/或资源级别禁用hateoas。你为什么要关闭hateoas?好吧,如果您知道您的客户机应用程序不会使用这个特性,那么您可能希望节省带宽和性能。

致使

EVE响应根据请求自动呈现为JSON(默认)或XML Accept 标题。入站文档(用于插入和编辑)采用JSON格式。

$ curl -H "Accept: application/xml" -i http://myapi.com
HTTP/1.1 200 OK
Content-Type: application/xml; charset=utf-8
...
<resource>
    <link rel="child" href="people" title="people" />
    <link rel="child" href="works" title="works" />
</resource>

可以通过编辑更改默认渲染器 RENDERERS 设置文件中的值。

RENDERERS = [
    'eve.render.JSONRenderer',
    'eve.render.XMLRenderer'
]

您可以通过子类化创建自己的渲染器。 eve.render.Renderer .每个渲染器都应设置为有效 mime ATTR和HAW .render() 方法已实现。请注意,必须始终启用至少一个渲染器。

有条件请求

每个资源表示都提供上次更新时的信息 (Last-Modified ,以及根据表示本身计算的哈希值 (ETag )。这些头允许客户端通过使用 If-Modified-Since 标题:

$ curl -H "If-Modified-Since: Wed, 05 Dec 2012 09:53:07 GMT" -i http://myapi.com/people/521d6840c437dc0002d1203c
HTTP/1.1 200 OK

If-None-Match 标题:

$ curl -H "If-None-Match: 1234567890123456789012345678901234567890" -i http://myapi.com/people/521d6840c437dc0002d1203c
HTTP/1.1 200 OK

数据完整性和并发控制

API响应包括 ETag 头,它还允许适当的并发控制。一个 ETag 是表示服务器上资源当前状态的哈希值。不允许用户编辑 (PATCHPUT )或删除 (DELETE )资源,除非它们提供最新的 ETag 对于他们试图编辑的资源。这可以防止用过时版本覆盖项目。

考虑以下工作流:

$ curl -H "Content-Type: application/json" -X PATCH -i http://myapi.com/people/521d6840c437dc0002d1203c -d '{"firstname": "ronald"}'
HTTP/1.1 428 PRECONDITION REQUIRED

我们试图编辑 (PATCH 但是我们没有提供 ETag 所以我们有一个 428 PRECONDITION REQUIRED 后退。让我们再试一次:

$ curl -H "If-Match: 1234567890123456789012345678901234567890" -H "Content-Type: application/json" -X PATCH -i http://myapi.com/people/521d6840c437dc0002d1203c -d '{"firstname": "ronald"}'
HTTP/1.1 412 PRECONDITION FAILED

这次出了什么问题?我们提供了强制 If-Match 头,但其值与 ETag 根据当前存储在服务器上的项的表示进行计算,因此我们得到 412 PRECONDITION FAILED .再一次!

$ curl -H "If-Match: 80b81f314712932a4d4ea75ab0b76a4eea613012" -H "Content-Type: application/json" -X PATCH -i http://myapi.com/people/50adfa4038345b1049c88a37 -d '{"firstname": "ronald"}'
HTTP/1.1 200 OK

最后!响应有效载荷如下所示:

{
    "_status": "OK",
    "_updated": "Fri, 23 Nov 2012 08:11:19 GMT",
    "_id": "50adfa4038345b1049c88a37",
    "_etag": "372fbbebf54dfe61742556f17a8461ca9a6f5a11"
    "_links": {"self": "..."}
}

这一次我们得到了补丁,服务器返回了新的 ETag .我们也有新的 _updated 价值,最终将允许我们执行 conditional requests .

并发控制适用于所有版本方法: PATCH (编辑) PUT (更换) DELETE (删除)。

禁用并发控制

如果您的用例需要,您可以选择完全禁用并发控制。通过设置 IF_MATCH 配置变量到 False (见 全局配置 )。当禁用并发控制时,不会向etag提供响应。您应该小心禁用此功能,因为这样会有效地打开API,从而冒着替换文档的旧版本的风险。或者,如果 ENFORCE_IF_MATCH 已禁用。当禁用并发检查强制时,使用 If-Match 头将作为条件请求进行处理,并且不使用 If-Match 标题将不作为条件处理。

大容量插入

客户可提交一份文件供插入:

$ curl -d '{"firstname": "barack", "lastname": "obama"}' -H 'Content-Type: application/json' http://myapi.com/people
HTTP/1.1 201 OK

在这种情况下,响应负载将只包含相关的文档元数据:

{
    "_status": "OK",
    "_updated": "Thu, 22 Nov 2012 15:22:27 GMT",
    "_id": "50ae43339fa12500024def5b",
    "_etag": "749093d334ebd05cf7f2b7dbfb7868605578db2c"
    "_links": {"self": {"href": "people/50ae43339fa12500024def5b", "title": "person"}}
}

当A 201 Created 在post请求之后返回, Location 响应中还包含标题。它的值是新文档的URI。

为了减少环回的数量,客户机还可以通过一个请求提交多个文档。它需要做的只是将文档包含在JSON列表中:

$ curl -d '[{"firstname": "barack", "lastname": "obama"}, {"firstname": "mitt", "lastname": "romney"}]' -H 'Content-Type: application/json' http://myapi.com/people
HTTP/1.1 201 OK

响应将是一个列表本身,每个文档的状态为:

{
    "_status": "OK",
    "_items": [
        {
            "_status": "OK",
            "_updated": "Thu, 22 Nov 2012 15:22:27 GMT",
            "_id": "50ae43339fa12500024def5b",
            "_etag": "749093d334ebd05cf7f2b7dbfb7868605578db2c"
            "_links": {"self": {"href": "people/50ae43339fa12500024def5b", "title": "person"}}
        },
        {
            "_status": "OK",
            "_updated": "Thu, 22 Nov 2012 15:22:27 GMT",
            "_id": "50ae43339fa12500024def5c",
            "_etag": "62d356f623c7d9dc864ffa5facc47dced4ba6907"
            "_links": {"self": {"href": "people/50ae43339fa12500024def5c", "title": "person"}}
        }
    ]
}

当提交多个文档时,API利用MongoDB 大容量插入 功能,这意味着不仅有一个请求从客户端传输到远程API,而且在API服务器和数据库之间执行单个环回。

如果插入成功,请记住 Location header只返回第一个创建的文档的URI。

数据验证

数据验证是开箱即用的。您的配置包括API管理的每个资源的模式定义。发送到要插入/更新的API的数据将根据架构进行验证,并且只有验证通过时才会更新资源。

$ curl -d '[{"firstname": "bill", "lastname": "clinton"}, {"firstname": "mitt", "lastname": "romney"}]' -H 'Content-Type: application/json' http://myapi.com/people
HTTP/1.1 201 OK

响应将包含请求中提供的每个项目的成功/错误状态:

{
    "_status": "ERR",
    "_error": "Some documents contains errors",
    "_items": [
        {
            "_status": "ERR",
            "_issues": {"lastname": "value 'clinton' not unique"}
        },
        {
            "_status": "OK",
        }
    ]
]

在上面的示例中,第一个文档没有验证,因此整个请求被拒绝。

当所有文档通过验证并正确插入时,响应状态为 201 Created .如果任何文档未通过验证,则响应状态为 422 Unprocessable Entity 或由定义的任何其他错误代码 VALIDATION_ERROR_STATUS 配置。

有关详细信息,请参阅 数据验证 .

可扩展数据验证

数据验证基于 Cerberus 验证系统,因此它是可扩展的,所以您可以使它适应您的特定用例。假设您的API只能接受某个字段值的奇数;您可以扩展验证类来验证它。或者说你想确保一个增值税字段与你自己的国家增值税算法相匹配,你也可以这样做。事实上,EVE的MongoDB数据层本身通过实现 unique 架构字段约束。有关详细信息,请参阅 数据验证 .

编辑文档(修补程序)

客户端可以使用 PATCH 方法,而 PUT 将替换它。 PATCH 无法删除字段,但只能更新其值。

考虑以下模式:

'entity': {
    'name': {
        'type': 'string',
        'required': True
    },
    'contact': {
        'type': 'dict',
        'required': True,
        'schema': {
            'phone': {
                'type': 'string',
                'required': False,
                'default': '1234567890'
            },
            'email': {
                'type': 'string',
                'required': False,
                'default': 'abc@efg.com'
            },
        }
    }
}

两个符号: {{contact: {{email: 'an email'}}}}{{contact.email: 'an email'}} 可用于更新 email 中的字段 contact 子文档。

记住 PATCH 无法删除字段,但只能更新现有值。此外,默认情况下 PATCH 将规范化架构中定义了默认值的缺少正文字段。考虑上面的模式。如果你 PATCH 有这样一个身体:

{'contact.email': 'xyz@gmail.com'}

并以此文档为目标:

{
  'name': 'test account',
  'contact': {'email': '123@yahoo.com', 'phone': '9876543210'}
}

然后更新的文档将如下所示:

{
  'name': 'test account',
  'contact': {
    'email': 'xyz@gmail.com',
    'phone': '1234567890'
  }
}

也就是说, contact.phone 已重置为其默认值。这可能不是我们想要的行为。要更改它,可以设置 normalize_on_patch (或) NORMALIZE_ON_PATCH 全球)至 False .现在,更新后的文档将如下所示:

{
  'name': 'test account',
  'contact': {
    'email': '123@yahoo.com',
    'phone': '9876543210'
  }
}

资源级缓存控制

可以为每个资源设置全局和单个缓存控制指令。

$ curl -i http://myapi
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 131
Cache-Control: max-age=20
Expires: Tue, 22 Jan 2013 09:34:34 GMT
Server: Eve/0.0.3 Werkzeug/0.8.3 Python/2.7.3
Date: Tue, 22 Jan 2013 09:34:14 GMT

上面的回答包括 Cache-ControlExpires 标题。这些将最小化服务器上的负载,因为启用缓存的使用者仅在真正需要时执行资源密集型请求。

API版本控制

我不太喜欢API版本控制。我认为客户机应该足够智能,能够透明地处理API更新,特别是在EVE支持的API之后。 HATEOAS. 当版本控制是必需的时,不同的API版本应该是独立的实例,因为版本之间的许多东西可能是不同的:缓存、URI、模式、验证等等。支持URI版本控制(http://api.example.com/v1/…)。

文档版本控制

EVE支持文档的自动版本控制。默认情况下,此设置处于关闭状态,但可以全局打开或为每个资源单独配置。启用后,EVE开始自动跟踪对文档的更改并添加字段 _version_latest_version 检索文档时。

在幕后,EVE将文档版本存储在与EVE定义的每个主资源集合并行的阴影集合中。在正常的发布、放置和修补操作期间,新文档版本将自动添加到此集合中。获取提供对文档版本访问的项时,可以使用特殊的新查询参数。使用访问特定版本 ?version=VERSION ,访问所有版本 ?version=all 和访问所有版本的差异 ?version=diffs .集合查询功能(如投影、分页和排序)使用 alldiff 除了不起作用的排序 diff .

需要注意的是,在打开版本控制时,有一些非标准的场景可能会产生意外的结果。尤其是,在EVE生成的API之外修改集合时,不会保存文档历史记录。此外,如果在任何时候 VERSION 从主文档中删除字段(打开版本控制时无法通过API执行此操作),随后的写入操作将重新初始化 VERSION 数字 VERSION =1.此时将有多个版本的文档具有相同的版本号。在正常实践中, VERSIONING 可以对以前未启用版本控制的任何新集合甚至现有集合启用而不必担心。

此外,还缓存文档版本独有的角案例。特定文档版本包括 _latest_version 字段,其值在创建新文档版本时将更改。为了说明这一点,Eve决定了时间 _latest_version 已更改(主文档上次更新的时间戳),并使用该值填充 Last-Modified 标题并检查 If-Modified-Since 特定文档版本查询的条件缓存验证器。请注意,这将不同于版本的最后更新字段中的时间戳。文档版本的etag在以下情况下不会更改: _latest_version 然而,变化。这会导致两种情况。首先,因为Eve无法确定客户是否 _latest_version 仅来自etag的最新信息,查询仅使用 If-None-Match 对于缓存,旧文档版本的验证将始终使其缓存无效。第二,在创建多个新版本的同一秒内获取和缓存的版本可能接收不正确的 Not Modified 随后的反应 GET 查询原因 Last-Modified 分辨率为1秒的值,静态ETag值不提供变化指示。这两种情况都是极不可能发生的,但是期望每秒进行多次编辑的应用程序应该考虑到可能会出现过时的穿孔情况。 _latest_version 数据。

有关更多信息,请参阅和 全局配置域配置 .

认证

支持自定义的基本身份验证(RFC-2617)、基于令牌的身份验证和基于HMAC的身份验证。OAuth2可以轻松集成。您可以锁定整个API,或者只锁定一些端点。您还可以限制CRUD命令,例如允许打开只读访问,同时限制对授权用户的编辑、插入和删除。还支持基于角色的访问控制。有关详细信息,请参阅 认证和授权 .

CORS跨源资源共享

EVE支持的API可以通过网页中包含的javascript访问。默认禁用, CORS 允许网页使用RESTAPI,这通常受到大多数浏览器“同一域”安全策略的限制。这个 X_DOMAINS 设置允许指定允许哪些域执行CORS请求。正则表达式的列表可以在 X_DOMAINS_RE 这对于具有动态子域范围的网站很有用。例如,确保正确地锚定和转义正则表达式 X_DOMAINS_RE = ['^http://sub-\d{{3}}\.example\.com$'] .

JSONP支持

一般来说,当您可以启用CORS时,您不希望添加JSONP:

对JSONP提出了一些批评。跨源资源共享(cors)是一种从不同域中的服务器获取数据的较新方法,它解决了其中的一些批评。所有的现代浏览器现在都支持CORS,使其成为一种可行的跨浏览器替代方案。 (source. )

但是,在某些情况下,当您确实需要jsonp时,比如当您必须支持遗留软件(ie6 anyone?)。

在您刚刚设置的EVE中启用JSONP JSONP_ARGUMENT .那么,任何有效的请求 JSONP_ARGUMENT 将返回用所述参数值包装的响应。例如,如果设置 JSON_ARGUMENT = 'callback'

$ curl -i http://localhost:5000/?callback=hello
hello(<JSON here>)

没有的请求 callback 参数将不使用JSONP。

默认为只读

如果您只需要一个只读API,那么您可以在几分钟内启动并运行它。

默认值和可空值

字段可以具有默认值和可以为空的类型。当为post(create)请求提供服务时,将为缺少的字段分配配置的默认值。参见 defaultnullable 中的关键字 架构定义 更多信息。

预定义的数据库筛选器

资源终结点将只公开(和更新)与预定义筛选器匹配的文档。这允许多个资源端点无缝地指向同一数据库集合。典型的用例是假设的 people 数据库上的集合正由 /admins/users API端点。

预测

此功能允许您创建集合和文档的动态视图,或者更精确地说,使用“投影”来决定应返回或不应返回哪些字段。换句话说,投影是有条件的查询,客户机指定API应该返回哪些字段。

$ curl -i -G http://myapi.com/people --data-urlencode 'projection={"lastname": 1, "born": 1}'
HTTP/1.1 200 OK

上面的查询将只返回 姓氏born 在“人员”资源中的所有可用字段中。也可以排除字段:

$ curl -i -G http://myapi.com/people --data-urlencode 'projection={"born": 0}'
HTTP/1.1 200 OK

上面将返回所有字段,但是 born .请注意,诸如id_字段、创建日期、更新日期等关键字段仍将包含在有效负载中。还要记住,包括Mongo在内的一些数据库引擎不允许混合使用包含和独占的选择。

嵌入式资源序列化

如果某个文档域正在引用另一个资源中的文档,则客户端可以请求将引用的文档嵌入到请求的文档中。

客户机有权通过查询参数根据每个请求激活文档嵌入。假设你有一个 emails 资源配置如下:

 DOMAIN = {
     'emails': {
         'schema': {
             'author': {
                 'type': 'objectid',
                 'data_relation': {
                     'resource': 'users',
                     'field': '_id',
                     'embeddable': True
                 },
             },
             'subject': {'type': 'string'},
             'body': {'type': 'string'},
         }
     }

A像这样: /emails?embedded={{"author":1}} 将返回完全嵌入的用户文档,而不返回 embedded 参数只返回用户 ObjectId .嵌入式资源序列化在资源和项上都可用 (/emails/<id>/?embedded={{"author":1}} )终结点。

可以在全局级别启用或禁用嵌入(通过设置 EMBEDDING 要么 TrueFalse )在资源级别(通过切换 embedding 值)。此外,只有带有 embeddable 值显式设置为 True 将允许嵌入引用的文档。

嵌入还可以处理与特定文档版本的数据关系,但是模式看起来有点不同。要启用与特定版本的数据关系,请添加 'version': True 到数据关系块。您还需要更改 typedict 并添加 schema 定义如下。

 DOMAIN = {
     'emails': {
         'schema': {
             'author': {
                 'type': 'dict',
                 'schema': {
                     '_id': {'type': 'objectid'},
                     '_version': {'type': 'integer'}
                 },
                 'data_relation': {
                     'resource': 'users',
                     'field': '_id',
                     'embeddable': True,
                     'version': True,
                 },
             },
             'subject': {'type': 'string'},
             'body': {'type': 'string'},
         }
     }

正如你所看到的, 'version': True 将数据关系字段的预期值更改为具有字段名称的字典 data_relation['field']VERSION . 用 'field': '_id' 在上述数据关系定义中 VERSION = '_version' 在eve配置中,此方案中数据关系的值将是一个带有字段的字典。 _id_version .

预定义的资源序列化

还可以为预定义的资源序列化选择一些字段。如果列出的字段是可嵌入的,并且它们实际上引用了其他资源中的文档(并且为该资源启用了嵌入),则默认情况下将嵌入引用的文档。客户端仍然可以从默认嵌入的字段中选择退出:

$ curl -i http://example.com/people/?embedded={"author": 0}
HTTP/1.1 200 OK

局限性

目前,我们支持通过位于任何子文档(嵌套的dict和list)中的引用嵌入文档。例如,查询 /invoices/?embedded={{"user.friends":1}} 将返回文档 user 所有他的 friends 嵌入,但仅当 user 是子文档,并且 friends 是引用列表(可以是dict、嵌套dict等的列表)。此功能是关于获取请求的序列化。不支持发布、放置或修补嵌入式文档。

默认情况下启用文档嵌入。

请注意

说到MongoDB,嵌入式资源序列化处理的是 文件参考 (链接文档),与 嵌入的文档 ,也由EVE支持(参见 MongoDB Data Model Design )。嵌入式资源序列化是一个很好的功能,它可以真正帮助您为客户机规范化数据模型。但是,在决定是否启用它时,特别是在默认情况下,请记住,要查找的每个嵌入资源都需要数据库查找,这很容易导致性能问题。

软删除

EVE提供了一种可选的“软删除”模式,在这种模式下,已删除的文档将继续存储在数据库中,并且能够还原,但仍然作为响应API请求的已删除项。默认情况下禁用软删除,但可以使用 SOFT_DELETE 配置设置,或使用域配置在资源级别单独配置 soft_delete 设置。见 全局配置域配置 有关启用和配置软删除的详细信息。

启用软删除时,回调附加到 on_delete_resource_originalson_delete_resource_originals_<resource_name> 事件将通过 originals 参数(参见 事件钩子

行为

启用软删除后,对单个项目和资源的删除请求的响应与对传统“硬”删除的响应相同。但是,在后台,eve不会从数据库中删除已删除的项目,而是使用 _deleted 元字段设置为 true .(的名称 _deleted 字段是可配置的。参见 全局配置 .)启用软删除时所做的所有请求根据或以其他方式说明 _deleted 字段。

这个 _deleted 字段自动添加并初始化为 false 对于启用软删除时创建的所有文档。启用软删除之前创建的文档,因此不能定义 _deleted 数据库中的字段仍将包括 _deleted: false 在API响应数据中,由EVE在响应构造期间添加。这些文档的放置或修补程序将添加 _deleted 存储文档的字段,设置为 false .

对于获取软删除文档请求的响应与对丢失或“硬”删除文档的响应略有不同。获取软删除文档的请求仍将使用 404 Not Found 状态代码,但响应正文将包含软删除文档 _deleted: true .无论默认设置或请求的内容如何,嵌入在已删除文档中的文档都不会在响应中展开。 embedded 查询参数。这是为了确保软删除文档包含在 404 响应反映文档被删除时的状态,如果嵌入的文档被更新,则不更改。

默认情况下,资源级GET请求的响应中将不包括软删除项。此行为与“硬”删除后的请求行为匹配。如果需要在响应中包含已删除的项,则 show_deleted 查询参数可以添加到请求中。(以下简称 show_deleted 参数名称是可配置的。参见 全局配置 )EVE将响应所有文档,不管是否已删除,并且由客户机来解析返回的文档' _deleted 字段。这个 _deleted 也可以在请求中显式筛选字段,只允许使用 ?where={{"_deleted": true}} 查询。

软删除是在数据层中强制执行的,这意味着应用程序代码使用 app.data.find_oneapp.data.find 方法将自动筛选出软删除项。传递请求对象 req.show_deleted == True 或者一个查找字典,它显式筛选 _deleted 字段将覆盖默认筛选。

恢复软删除项

对软删除文档的放置或修补请求将恢复该文档,并自动设置 _deletedfalse 在数据库中。修改 _deleted 直接字段不是必需的(或允许的)。例如,使用补丁请求时,只指定要在还原版本中更改的字段,或者发出一个空的请求来还原文档。必须在适当授权的情况下对软删除文档进行请求,否则将被拒绝。

请注意,如果恢复以前软删除的文档,最终的唯一字段可能会被复制到两个不同的文档中:还原的一个文档和另一个文档,它们可能在原始文档(现在还原)时以相同的字段值存储。处于“已删除”状态。这是因为在验证 unique 新文档或更新文档的规则。

版本

软删除版本化文档将创建该文档的新版本 _deleted 设置为 true .对已删除版本的get请求将收到 404 Not Found 响应如上所述,而以前的版本将继续响应 200 OK .对 ?version=diff?version=all 将包括删除的版本,就好像它是其他版本一样。

数据关系

前夜 data_relation 验证程序将不允许引用已软删除的文档。试图创建或更新引用软删除文档的文档将失败,就像该文档已被硬删除一样。与软删除文档的现有数据关系仍保留在数据库中,但要求对这些关系进行嵌入式文档序列化的请求将解析为空值。同样,这与硬删除文档的关系行为相匹配。

与已删除文档版本的版本数据关系也将无法验证,但允许与删除前或还原文档后的版本的关系,并将继续成功解决。

考虑事项

在应用程序中使用后禁用软删除需要数据库维护,以确保API保持一致。禁用软删除后,请求将不再筛选或处理 _deleted 字段和软删除的文档现在将在您的API上重新激活。因此,在禁用软删除时,必须执行数据迁移以删除具有 _deleted == True ,并建议删除 _deleted 文档中的字段,其中 _deleted == False .在现有应用程序中启用软删除是安全的,并且将维护从该点删除的文档。

事件钩子

预请求事件挂钩

当接收到GET/HEAD、POST、PATCH、PUT、DELETE请求时,这两个请求都是 on_pre_<method> 和A on_pre_<method>_<resource> 引发了事件。您可以使用多个回调函数订阅这些事件。

>>> def pre_get_callback(resource, request, lookup):
...  print('A GET request on the "%s" endpoint has just been received!' % resource)

>>> def pre_contacts_get_callback(request, lookup):
...  print('A GET request on the contacts endpoint has just been received!')

>>> app = Eve()

>>> app.on_pre_GET += pre_get_callback
>>> app.on_pre_GET_contacts += pre_contacts_get_callback

>>> app.run()

回调将接收请求的资源,原始资源 flask.request 对象和当前查找字典作为参数(唯一例外是 on_pre_POST 不提供 lookup 参数)。

动态查找筛选器

自从 lookup 字典将被数据层用来检索资源文档,开发人员可以选择修改它,以便向查找查询添加自定义逻辑。

def pre_GET(resource, request, lookup):
    # only return documents that have a 'username' field.
    lookup["username"] = {'$exists': True}

app = Eve()

app.on_pre_GET += pre_GET
app.run()

在运行时更改查找字典与应用 预定义的数据库筛选器 通过配置。但是,只能通过配置设置静态过滤器,而通过连接到 on_pre_<METHOD> 事件允许您改为设置动态筛选器,这允许额外的灵活性。

后请求事件挂钩

当执行了get、post、patch、put和delete方法时,这两个方法都是 on_post_<method>on_post_<method>_<resource> 引发了事件。您可以使用多个回调函数订阅这些事件。回调将接收访问的资源,原始 flask.request 对象和响应负载。

>>> def post_get_callback(resource, request, payload):
...  print('A GET on the "%s" endpoint was just performed!' % resource)

>>> def post_contacts_get_callback(request, payload):
...  print('A get on "contacts" was just performed!')

>>> app = Eve()

>>> app.on_post_GET += post_get_callback
>>> app.on_post_GET_contacts += post_contacts_get_callback

>>> app.run()

数据库事件挂钩

数据库事件挂钩的工作方式与请求事件挂钩类似。这些事件在数据库操作之前和之后激发。以下是如何配置事件的示例:

>>> def add_signature(resource, response):
...     response['SIGNATURE'] = "A %s from eve" % resource

>>> app = Eve()
>>> app.on_fetched_item += add_signature

你可以用烧瓶 abort() 要中断数据库操作:

>>> from flask import abort

>>> def check_update_access(resource, updates, original):
...     abort(403)

>>> app = Eve()
>>> app.on_insert_item += check_update_access

如果两个操作都可用,则会为资源和项激发事件。对于每个操作,将触发两个事件:

  • 通用: on_<action_name>

  • 资源名称: on_<action_name>_<resource_name>

让我们看看哪些活动可用:

行动

什么

什么时候?

事件名称/方法签名

资源

on_fetched_resource
def event(resource_name, response)
on_fetched_resource_<resource_name>
def event(response)

项目

on_fetched_item
def event(resource_name, response)
on_fetched_item_<resource_name>
def event(response)

差异

on_fetched_diffs
def event(resource_name, response)
on_fetched_diffs_<resource_name>
def event(response)

插入

项目

之前

on_insert
def event(resource_name, items)
on_insert_<resource_name>
def event(items)

on_inserted
def event(resource_name, items)
on_inserted_<resource_name>
def event(items)

替换

项目

之前

on_replace
def event(resource_name, item, original)
on_replace_<resource_name>
def event(item, original)

on_replaced
def event(resource_name, item, original)
on_replaced_<resource_name>
def event(item, original)

更新

项目

之前

on_update
def event(resource_name, updates, original)
on_update_<resource_name>
def event(updates, original)

on_updated
def event(resource_name, updates, original)
on_updated_<resource_name>
def event(updates, original)

删除

项目

之前

on_delete_item
def event(resource_name, item)
on_delete_item_<resource_name>
def event(item)

on_deleted_item
def event(resource_name, item)
on_deleted_item_<resource_name>
def event(item)

资源

之前

on_delete_resource
def event(resource_name)
on_delete_resource_<resource_name>
def event()
on_delete_resource_originals
def event(resource_name, originals, lookup)
on_delete_resource_originals_<resource_name>
def event(originals, lookup)

on_deleted_resource
def event(resource_name, item)
on_deleted_resource_<resource_name>
def event(item)

获取事件

这些是带有方法签名的获取事件:

  • on_fetched_resource(resource_name, response)

  • on_fetched_resource_<resource_name>(response)

  • on_fetched_item(resource_name, response)

  • on_fetched_item_<resource_name>(response)

  • on_fetched_diffs(resource_name, response)

  • on_fetched_diffs_<resource_name>(response)

当项目刚从数据库中读取并即将发送到客户端时,将引发这些项目。注册的回调函数可以在返回到客户机之前根据需要操作这些项。

>>> def before_returning_items(resource_name, response):
...  print('About to return items from "%s" ' % resource_name)

>>> def before_returning_contacts(response):
...  print('About to return contacts')

>>> def before_returning_item(resource_name, response):
...  print('About to return an item from "%s" ' % resource_name)

>>> def before_returning_contact(response):
...  print('About to return a contact')

>>> app = Eve()
>>> app.on_fetched_resource += before_returning_items
>>> app.on_fetched_resource_contacts += before_returning_contacts
>>> app.on_fetched_item += before_returning_item
>>> app.on_fetched_item_contacts += before_returning_contact

需要注意的是,项目获取事件将与 Document Versioning 对于特定文档版本,如 ?version=5 以及所有文档版本 ?version=all .使用访问所有版本的差异 ?version=diffs 将只处理diff fetch事件。请注意,diffs返回应在回调中处理的部分文档。

插入事件

这些是具有方法签名的插入事件:

  • on_insert(resource_name, items)

  • on_insert_<resource_name>(items)

  • on_inserted(resource_name, items)

  • on_inserted_<resource_name>(items)

当一个POST请求命中API并且新项目即将存储在数据库中时,将激发这些事件:

  • on_insert 对于每个资源端点。

  • on_insert_<resource_name> 对于特定的 <resource_name> 资源终结点。

回调函数可以钩住这些事件来任意添加新字段或编辑现有字段。

插入项目后,将激发以下两个事件:

  • on_inserted 对于每个资源端点。

  • on_inserted_<resource_name> 对于特定的 <resource_name> 资源终结点。

验证错误

作为参数传递给这些事件的项出现在列表中。并且只发送那些通过验证的项目。

例子:

>>> def before_insert(resource_name, items):
...  print('About to store items to "%s" ' % resource_name)

>>> def after_insert_contacts(items):
...  print('About to store contacts')

>>> app = Eve()
>>> app.on_insert += before_insert
>>> app.on_inserted_contacts += after_insert_contacts

替换事件

以下是替换事件及其方法签名:

  • on_replace(resource_name, item, original)

  • on_replace_<resource_name>(item, original)

  • on_replaced(resource_name, item, original)

  • on_replaced_<resource_name>(item, original)

当Put请求命中API并且在通过验证后将要替换某个项时,将激发以下事件:

  • on_replace 用于任何资源项终结点。

  • on_replace_<resource_name> 用于特定资源终结点。

item 将要存储的新项。 original 是正在被替换的数据库中的项。回调函数可以钩住这些事件来任意添加或更新 item 字段,或执行其他辅助操作。

替换该项后,将激发其他两个事件:

  • on_replaced 用于任何资源项终结点。

  • on_replaced_<resource_name> 用于特定资源终结点。

更新事件

这些是更新事件及其方法签名:

  • on_update(resource_name, updates, original)

  • on_update_<resource_name>(updates, original)

  • on_updated(resource_name, updates, original)

  • on_updated_<resource_name>(updates, original)

当一个补丁请求命中API并且一个项目将在通过验证后更新时,这些事件将被激发。 before 项目更新:

  • on_update 对于任何资源终结点。

  • on_update_<resource_name> 只有当 <resource_name> 终结点已命中。

在这里 updates 代表应用于该项目的更新,以及 original 数据库中即将更新的项。回调函数可以钩住这些事件来任意添加或更新 updates 或执行其他辅助动作。

After 项目已更新:

  • on_updated 为任何资源终结点激发。

  • on_updated_<resource_name> 只有当 <resource_name> 终结点已命中。

请注意

请注意 last_modifiedetag 头将始终与数据库中项目的状态保持一致(它们不会被更新以反映回调函数最终应用的更改)。

删除事件

这些是具有方法签名的删除事件:

  • on_delete_item(resource_name, item)

  • on_delete_item_<resource_name>(item)

  • on_deleted_item(resource_name, item)

  • on_deleted_item_<resource_name>(item)

  • on_delete_resource(resource_name)

  • on_delete_resource_<resource_name>()

  • on_delete_resource_originals(originals, lookup)

  • on_delete_resource_originals_<resource_name>(originals, lookup)

  • on_deleted_resource(resource_name)

  • on_deleted_resource_<resource_name>()

项目

当一个删除请求到达一个项目端点并且 before 该项将被删除,这些事件将被激发:

  • on_delete_item 对于请求命中的任何资源。

  • on_delete_item_<resource_name> 对于特定的 <resource_name> 被删除命中的项终结点。

After 该项目已被删除 on_deleted_item(resource_name, item)on_deleted_item_<resource_name>(item) 被提升。

item 正在删除的项目。回调函数可以钩住这些事件来执行附件操作。不,此时您不能随意中止删除操作(您可能应该查看 数据验证 或者最终完全禁用删除命令)。

资源

如果您有足够的勇气在资源终结点上启用删除命令(允许一次性清除整个集合),则可以通过将回调函数挂接到 on_delete_resource(resource_name)on_delete_resource_<resource_name>() 钩子。

  • on_delete_resource_originals 对于在检索原始文档后被请求击中的任何资源。

  • on_delete_resource_originals_<resource_name> 对于特定的 <resource_name> 在检索原始文档后被删除操作击中的资源终结点。

注意:这两个事件对于在给定查找和原始列表的实际删除操作之前执行一些业务逻辑很有用。

聚合事件挂钩

还可以将一个或多个回调附加到聚合端点。这个 before_aggregation 在即将执行聚合时激发事件。任何附加的回调函数都将接收端点名称和聚合管道作为参数。如果需要,可以更改管道。

>>> def on_aggregate(endpoint, pipeline):
...   pipeline.append({"$unwind": "$tags"})

>>> app = Eve()
>>> app.before_aggregation += on_aggregate

这个 after_aggregation 在执行聚合时激发事件。附加的回调函数可以利用此事件在文档返回到客户机之前修改文档。

>>> def alter_documents(endpoint, documents):
...   for document in documents:
...     document['hello'] = 'well, hello!'

>>> app = Eve()
>>> app.after_aggregation += alter_documents

有关聚合支持的详细信息,请参阅 MongoDB聚合框架

请注意

为了提供无缝的事件处理功能,EVE依赖于 Events 包裹。

速率限制

每个用户/方法都支持API速率限制。您可以为每个HTTP方法设置请求数和时间窗口。如果在时间窗口内达到请求限制,则API将使用 429 Request limit exceeded 直到定时器复位。用户由身份验证头标识,或者(如果缺少)由客户机IP标识。当启用速率限制时,适当 X-RateLimit- 每个API响应都提供头。假设将速率限制设置为每15分钟300个请求,这是用户通过一个请求访问端点后得到的结果:

X-RateLimit-Remaining: 299
X-RateLimit-Limit: 300
X-RateLimit-Reset: 1370940300

您可以为每个支持的方法(get、post、patch、delete)设置不同的限制。

请注意

速率限制在默认情况下是禁用的,启用后需要运行Redis服务器。关于速率限制的教程即将推出。

自定义ID字段

EVE允许扩展其标准数据类型支持。在 处理自定义ID字段 教程我们将了解如何使用UUID值而不是MongoDB默认对象ID作为唯一的文档标识符。

文件存储

媒体文件(图像、PDF等)可以上载为 media 文档字段。上传通过 POSTPUTPATCH 和往常一样,但是使用 multipart/form-data 内容类型。

我们假设 accounts 终结点的架构如下:

accounts = {
    'name': {'type': 'string'},
    'pic': {'type': 'media'},
    ...
}

我们会用卷发 POST 这样地:

$ curl -F "name=john" -F "pic=@profile.jpg" http://example.com/accounts

为了优化性能,文件存储在 GridFS 默认情况下。自定义 MediaStorage 类可以实现并传递给应用程序,以支持其他存储系统。A FileSystemMediaStorage 课程正在进行中,很快将包含在EVE包中。

由于还没有合适的开发人员指南,您可以查看 MediaStorage 如果您对开发自定义存储类感兴趣,请访问source。

将媒体文件作为base64字符串提供服务

当请求文档时,媒体文件将作为base64字符串返回,

 {
     '_items': [
         {
             '_updated':'Sat, 05 Apr 2014 15:52:53 GMT',
             'pic':'iVBORw0KGgoAAAANSUhEUgAAA4AAAAOACA...',
         }
     ]
     ...
}

但是,如果 EXTENDED_MEDIA_INFO 列表已填充(默认情况下不是这样),有效负载格式将不同。此标志允许从其他元字段的驱动程序进行传递。例如,使用mongodb驱动程序, content_typenamelength 可以添加到此列表,并将从基础驱动程序传递。

什么时候? EXTENDED_MEDIA_INFO 字段将是一个字典,而文件本身存储在 file 键和其他键是元字段。假设标志设置如下:

EXTENDED_MEDIA_INFO = ['content_type', 'name', 'length']

然后输出将类似于

{
    '_items': [
        {
            '_updated':'Sat, 05 Apr 2014 15:52:53 GMT',
            'pic': {
                'file': 'iVBORw0KGgoAAAANSUhEUgAAA4AAAAOACA...',
                'content_type': 'text/plain',
                'name': 'test.txt',
                'length': 8129
            }
        }
    ]
    ...
}

对于MongoDB,可以在 driver documentation .

如果您有其他方法来检索媒体文件(例如自定义flask端点),那么可以通过将设置为从负载中排除媒体文件。 False 这个 RETURN_MEDIA_AS_BASE64_STRING 标志。这考虑到如果 EXTENDED_MEDIA_INFO 使用。

在专用终结点服务媒体文件

虽然返回作为base64字段嵌入的文件是默认行为,但您可以选择在专用媒体端点为它们提供服务。你通过设置 RETURN_MEDIA_AS_URLTrue .启用此功能时,文档字段包含指向相应文件的URL,这些文件在媒体终结点提供服务。

可以更改默认媒体终结点 (media )通过更新 MEDIA_BASE_URLMEDIA_ENDPOINT 设置。假设您通过一个自定义的 MediaStorage 子类。您可能会这样设置媒体端点:

# disable default behaviour
RETURN_MEDIA_AS_BASE64_STRING = False

# return media as URL instead
RETURN_MEDIA_AS_URL = True

# set up the desired media endpoint
MEDIA_BASE_URL = 'https://s3-us-west-2.amazonaws.com'
MEDIA_ENDPOINT = 'media'

设置 MEDIA_BASE_URL 是可选的。如果未设置任何值,则在为生成URL时将使用API基地址 MEDIA_ENDPOINT .

部分媒体下载

当在专用端点上提供文件时,客户机可以请求部分下载。这允许他们提供优化的暂停/恢复等功能(无需重新启动下载)。要执行部分下载,请确保 Range 头被添加到客户机请求中。

$ curl http://localhost/media/yourfile -i -H "Range: bytes=0-10"
HTTP/1.1 206 PARTIAL CONTENT
Date: Sun, 20 Aug 2017 14:26:42 GMT
Content-Type: audio/mp4
Content-Length: 11
Connection: keep-alive
Content-Range: bytes 0-10/23671
Last-Modified: Sat, 19 Aug 2017 03:25:36 GMT
Accept-Ranges: bytes

abcdefghilm

在上面的代码片段中,我们看到curl请求文件的第一块。

利用投影优化媒体文件的处理

客户机和API维护人员可以利用 预测 从响应有效负载中包含/排除媒体字段的功能。

假设客户机存储了一个带有图像的文档。图像字段称为 形象 它属于 media 类型。稍后,客户机希望检索相同的文档,但为了优化速度,并且由于图像已经缓存,因此不希望随文档一起下载图像。它可以通过请求从响应有效载荷中修剪字段来实现这一点:

$ curl -i http://example.com/people/<id>?projection={"image": 0}
HTTP/1.1 200 OK

将返回文档及其所有字段,除了 形象 字段。

此外,当设置 datasource 属性对于任何给定的资源终结点,可以显式排除字段(属于 media 类型,但也属于任何其他类型)来自默认响应:

people = {
    'datasource': {
        'projection': {'image': 0}
    },
    ...
}

现在,客户机必须通过发送这样的请求,明确地请求将图像字段包含在响应有效负载中:

$ curl -i http://example.com/people/<id>?projection={"image": 1}
HTTP/1.1 200 OK

另见

有关 datasource 设置。

媒体文件注释为 multipart/form-data

如果您将媒体文件上载为 multipart/form-data 除文件字段之外的所有其他字段都将被视为 strings 用于所有现场验证目的。如果已经将某些资源域定义为不同类型(布尔值、数字、列表等),则这些域的验证规则将失败,从而阻止您成功提交资源。

如果在这种情况下仍希望能够执行字段验证,则必须打开 MULTIPART_FORM_FIELDS_AS_JSON 在设置文件中,为了将传入字段视为JSON编码的字符串,并且仍然能够验证字段。

请注意,如果您确实打开了 MULTIPART_FORM_FIELDS_AS_JSON 您必须以正确编码的JSON字符串的形式提交所有资源字段。

例如a number 应提交为 1234 (正如您通常所期望的那样)。A boolean 必须作为 true (注意小写 t )。A list 字符串的 ["abc", "xyz"] .最后是一个 string ,这是最有可能旅行的事情,您必须提交 "'abc'" (请注意,它用双引号括起来)。如果怀疑您提交的是有效的JSON字符串,您可以尝试从http://jsonlint.com/的JSON验证器传递它,以确保它是正确的。

使用媒体列表

使用媒体列表时,无法在默认配置中提交这些列表。启用 AUTO_COLLAPSE_MULTI_KEYSAUTO_CREATE_LISTS 使这成为可能。这允许为一个键发送多个值 multipart/form-data 请求并以这种方式上载文件列表。

GeoJSON

MongoDB数据层支持在 GeoJSON 格式。支持的所有geojson对象 MongoDB 可供选择:

  • Point

  • Multipoint

  • LineString

  • MultiLineString

  • Polygon

  • MultiPolygon

  • GeometryCollection

所有这些对象都实现为本机EVE数据类型(请参见 架构定义 )因此,它们需要经过适当的验证。

在下面的示例中,我们将扩展 people 通过添加 location 类型字段 Point.

people = {
    ...
    'location': {
        'type': 'point'
    },
    ...
}

存储联系人及其位置非常简单:

$ curl -d '[{"firstname": "barack", "lastname": "obama", "location": {"type":"Point","coordinates":[100.0,10.0]}}]' -H 'Content-Type: application/json'  http://127.0.0.1:5000/people
HTTP/1.1 201 OK

EVE还支持geojson FeatureFeatureCollection 对象,在 MongoDB 文档。geojson规范允许对象包含任意数量的成员(名称/值对)。EVE验证的实施更加严格,只允许两个成员。可以通过设置禁用此限制 ALLOW_CUSTOM_FIELDS_IN_GEOJSONTrue .

查询geojson数据

一般来说,所有MongoDB geospatial query operators 并支持其关联的几何说明符。在这个例子中,我们使用 $near 操作员查询距离某一点1000米范围内的所有联系人:

?where={"location": {"$near": {"$geometry": {"type":"Point", "coordinates": [10.0, 20.0]}, "$maxDistance": 1000}}}

有关地理查询的详细信息,请参阅MongoDB文档。

内部资源

默认情况下,对主端点的GET请求的响应将包括所有资源。这个 internal_resource 但是,设置关键字允许您将端点设置为内部,仅对内部数据操作可用:不能对其进行HTTP调用,并且将从 HATEOAS 链接。

一个使用示例是记录系统中发生的所有插入的机制,这些插入可以用于审计或通知系统。首先我们定义一个 internal_transaction 端点,标记为 internal_resource

 internal_transactions = {
     'schema': {
         'entities': {
             'type': 'list',
         },
         'original_resource': {
             'type': 'string',
         },
     },
     'internal_resource': True
 }

现在,如果我们访问主端点 HATEOAS 如果启用,我们将无法获取 internal-transactions 列出(并通过HTTP点击端点将返回 404 )我们可以使用数据层访问我们的秘密端点。像这样:

 from eve import Eve

 def on_generic_inserted(self, resource, documents):
     if resource != 'internal_transactions':
         dt = datetime.now()
         transaction = {
             'entities':  [document['_id'] for document in documents],
             'original_resource': resource,
             config.LAST_UPDATED: dt,
             config.DATE_CREATED: dt,
         }
         app.data.insert('internal_transactions', [transaction])

 app = Eve()
 app.on_inserted += self.on_generic_inserted

 app.run()

我承认这个例子是最基本的,但希望它能说明问题。

增强的日志记录

通过默认的应用程序日志记录程序可以记录许多事件。标准 LogRecord attributes 使用一些请求属性进行扩展:

clientip

执行请求的客户端的IP地址。

url

完整的请求URL,包括最终的查询参数。

method

请求方法 (POSTGET 等)

您可以在登录到文件或任何其他目标时使用这些字段。

回调函数还可以利用内置记录器。下面的示例将应用程序事件记录到一个文件中,并在每次调用自定义函数时记录自定义消息。

import logging

from eve import Eve

def log_every_get(resource, request, payload):
    # custom INFO-level message is sent to the log file
    app.logger.info('We just answered to a GET request!')

app = Eve()
app.on_post_GET += log_every_get

if __name__ == '__main__':

    # enable logging to 'app.log' file
    handler = logging.FileHandler('app.log')

    # set a custom log format, and add request
    # metadata to each log line
    handler.setFormatter(logging.Formatter(
        '%(asctime)s %(levelname)s: %(message)s '
        '[in %(filename)s:%(lineno)d] -- ip: %(clientip)s, '
        'url: %(url)s, method:%(method)s'))

    # the default log level is set to WARNING, so
    # we have to explicitly set the logging level
    # to INFO to get our custom message logged.
    app.logger.setLevel(logging.INFO)

    # append the handler to the default application logger
    app.logger.addHandler(handler)

    # let's go
    app.run()

目前只有MongoDB层引发的异常和 POSTPATCHPUT 方法被记录。我们的想法是增加一些 INFO 而且可能 DEBUG 将来的事件级别。

作战日志

oplog是一个API范围内所有编辑操作的日志。每 POSTPATCH PUTDELETE 操作可以记录到oplog。在其核心,oplog只是一个服务器日志。有点不同的是,它可以作为只读端点公开,从而允许客户机像对待任何其他API端点那样查询它。

每个oplog条目都包含有关文档和操作的信息:

  • 执行的操作

  • 文档的唯一ID

  • 更新日期

  • 创建日期

  • 资源终结点URL

  • 用户令牌,如果 用户限制的资源访问 已为终结点启用

  • 可选自定义数据

与任何其他API维护的文档一样,oplog条目也公开了:

  • 条目ID

  • ETag

  • 如果启用了,则讨厌OA字段。

如果 OPLOG_AUDIT 是否启用项也公开:

  • 客户端IP

  • 用户名或令牌(如果可用)

  • 应用于文档的更改(用于 DELETE 包括整个文件)。

典型的oplog条目如下:

{
    "o": "DELETE",
    "r": "people",
    "i": "542d118938345b614ea75b3c",
    "c": {...},
    "ip": "127.0.0.1",
    "u": "admin",
    "_updated": "Fri, 03 Oct 2014 08:16:52 GMT",
    "_created": "Fri, 03 Oct 2014 08:16:52 GMT",
    "_etag": "e17218fbca41cb0ee6a5a5933fb9ee4f4ca7e5d6"
    "_id": "542e5b7438345b6dadf95ba5",
    "_links": {...},
}

为了节省一点空间(至少在MongoDB上),字段名被缩短了:

  • o 表示执行的操作

  • r 表示资源端点

  • i 表示文档ID

  • ip 是客户端IP

  • u 代表用户(或令牌)

  • c 代表发生的变化

  • extra 是可用于存储自定义数据的可选字段

_created_updated 与目标文档相关,后者在各种场景中都很方便(例如,当客户机可以使用oplog时,稍后将详细介绍)。

请注意,默认情况下 c (更改)字段不包括 POST 操作。您可以添加 POSTOPLOG_CHANGE_METHODS 设置(参见 全局配置 )如果您希望在每次插入时都包含整个文档。

如何操作oplog?

有七种设置专用于oplog:

  • OPLOG 打开和关闭oplog功能。默认为 False .

  • OPLOG_NAME 是数据库上的oplog集合的名称。默认为 oplog .

  • OPLOG_METHODS 是要记录的HTTP方法的列表。默认为全部。

  • OPLOG_ENDPOINT 是终结点名称。默认为 None .

  • OPLOG_AUDIT 如果启用,也会记录IP地址和更改。默认为 True .

  • OPLOG_CHANGE_METHODS 确定将记录更改的方法。默认为 [“修补”、“放置”、“删除”] .

  • OPLOG_RETURN_EXTRA_FIELD 确定是否 extra 字段应由返回 OPLOG_ENDPOINT .默认为 False .

如您所见,oplog功能在默认情况下是关闭的。还有,因为 OPLOG_ENDPOINT 默认为 None ,即使在没有公共oplog端点可用的情况下切换该功能。您必须显式设置端点名称,以便向公众公开您的oplog。

oplog端点

由于oplog端点只是一个标准的api端点,所以您可以自定义它。这允许设置自定义身份验证(您可能希望此资源仅可用于管理目的)或任何其他有用的设置。

请注意,虽然您可以更改其大部分设置,但端点始终是只读的,因此设置 resource_methodsitem_methods 去别的地方 ['GET'] 不会有任何作用。此外,除非您需要自定义它,否则向域中添加一个oplog条目并不是真正必要的,因为它将自动为您添加。

将oplog作为一个端点公开在您有多个客户机(如电话、平板电脑、Web和桌面应用程序)需要保持彼此和服务器同步的情况下可能很有用。他们可以访问oplog来了解自上次访问以来发生的所有事情,而不是访问每个端点。这是一个单一的请求,而不是几个。这并不总是客户可以采用的最佳方法。有时,最好只查询某个端点上的更改。这也是可能的,只需查询oplog以了解在该端点上发生的更改。

扩展oplog项

每次要更新oplog时, on_oplog_push 事件被激发。您可以将一个或多个回调函数挂接到此事件。回拨接收 resourceentries 作为论据。前者是资源名,后者是将要写入磁盘的oplog条目列表。

您的回调可以添加一个可选的 extra 字段到规范的oplog条目。字段可以是任何类型。在本例中,我们将向每个条目添加自定义dict:

def oplog_extras(resource, entries):
    for entry in entries:
        entry['extra'] = {'myfield': 'myvalue'}

app = Eve()

app.on_oplog_push += oplog_extras
app.run()

请注意,除非您明确设置 OPLOG_RETURN_EXTRA_FIELDTrue , the extra 字段将 notOPLOG_ENDPOINT .

备注

你在MongoDB上吗?考虑让oplog成为 capped collection .另外,如果你想知道是的,那么 Eve oplog 的灵感来源于 Replica Set Oplog .

架构终结点

通过启用EVE的架构端点,可以将资源架构公开给API客户端。为此,请设置 SCHEMA_ENDPOINT 要从中提供架构数据的API端点名称的配置选项。启用后,EVE会将端点视为包含JSON编码的CERBERUS模式定义的只读资源,并按资源名称进行索引。资源可见性和授权设置是受尊重的,因此请求没有读身份验证的内部资源或资源在架构端点将不可访问。默认情况下, SCHEMA_ENDPOINT 设置为 None .

MongoDB聚合框架

支持 MongoDB Aggregation Framework 是内置的。在下面的示例(取自pymongo)中,我们将执行一个简单的聚合,以计算整个集合中标记数组中每个标记的出现次数。为了实现这一点,我们需要将三个操作传递给管道。首先,我们需要展开标签数组,然后按标签分组并汇总,最后按计数排序。

因为python字典不维护应该使用的顺序 SON 或集合 OrderedDict 如果需要明确的排序,例如 $sort

posts = {
    'datasource': {
        'aggregation': {
            'pipeline': [
                {"$unwind": "$tags"},
                {"$group": {"_id": "$tags", "count": {"$sum": 1}}},
                {"$sort": SON([("count", -1), ("_id", -1)])}
            ]
        }
    }
}

上面的管道是静态的。您可以选择允许使用动态管道,而客户机将直接影响聚合结果。让我们稍微更新一下管道:

posts = {
    'datasource': {
        'aggregation': {
            'pipeline': [
                {"$unwind": "$tags"},
                {"$group": {"_id": "$tags", "count": {"$sum": "$value"}}},
                {"$sort": SON([("count", -1), ("_id", -1)])}
            ]
        }
    }
}

你可以看到 count 字段现在要求和 $value ,在执行请求时由客户端设置:

$ curl -i http://example.com/posts?aggregate={"$value": 2}

上面的请求将导致在服务器上使用 count 字段配置为静态 {{"$sum": 2}} .客户只需添加 aggregate 查询参数,然后传递带有字段/值对的字典。像所有其他关键字一样,您可以更改 aggregate 到你喜欢的关键字,只需设置 QUERY_AGGREGATION 在您的设置中。

您还可以设置pymongo本机支持的所有选项。有关聚合的详细信息,请参阅 高级数据源模式 .

你可以通过 {{}} 到要忽略的字段。考虑到以下管道:

posts = {
    'datasource': {
        'aggregation': {
            'pipeline': [
                {"$match": { "name": "$name", "time": "$time"}}
                {"$unwind": "$tags"},
                {"$group": {"_id": "$tags", "count": {"$sum": 1}}},
            ]
        }
    }
}

如果执行以下请求:

$ curl -i http://example.com/posts?aggregate={"$name": {"$regex": "Apple"}, "$time": {}}

舞台 {{"$match": {{ "name": "$name", "time": "$time"}}}} 在管道中执行为 {{"$match": {{ "name": {{"$regex": "Apple"}}}}}} .对于以下请求:

$ curl -i http://example.com/posts?aggregate={"$name": {}, "$time": {}}

舞台 {{"$match": {{ "name": "$name", "time": "$time"}}}} 将完全跳过管道中的。

以上请求将忽略 "count": {{"$sum": "$value"}}}} .自定义回调函数可以附加到 before_aggregationafter_aggregation 事件挂钩。有关详细信息,请参阅 聚合事件挂钩 .

局限性

客户端分页 (?page=2 )默认情况下启用。目前通过注入 $facet 两个子管道连接阶段,总计 ($count )和分页结果 ($limit 首先,然后 $skip )到聚合管道的最后 before_aggregation 钩子。您可以通过设置来关闭分页 paginationFalse 对于终结点。请记住,禁用分页时,所有聚合结果都包含在每个响应中。只有在预期的响应有效负载不是很大的情况下,禁用分页才是合适的(实际上也是可取的)。

客户端排序 (?sort=field1 )在聚合终结点不支持。当然可以添加一个或多个 $sort 到管道的阶段,正如我们在上面的示例中所做的那样。如果您添加了 $sort 准备管道,考虑将其添加到管道末端。根据MongoDB的 $limit 文档 (link) :

当A $sort 紧跟在 $limit 在管道中,排序操作只维护顶部 n 结果随着进展,其中 n 是指定的限制,MongoDB只需要存储 n 内存中的项。

正如我们前面看到的,分页增加了 $limit 阶段到管道末端。因此,如果启用分页,并且 $sort 是管道的最后一个阶段,那么应该优化得到的组合管道。

单个端点不能同时服务于常规和聚合结果。但是,由于可以设置多个端点,所有端点都来自同一个数据源(请参见 多个API端点,一个数据源 )可以轻松实现类似的功能。

MongoDB和SQL支持

对单个或多个MongoDB数据库/服务器的支持是现成的。SQLAlchemy扩展提供对SQL后端的支持。可以相对轻松地开发其他数据层。访问 extensions page 获取社区开发的数据层和扩展的列表。

由烧瓶驱动

Eve是基于 Flask Micro Web框架。实际上,eve本身是一个flask子类,这意味着eve公开了flask的所有功能和细节,就像内置的开发服务器和 debugger, 综合支持 unittesting 和一个 extensive documentation .