RaptorDB REST






4.92/5 (11投票s)
RaptorDB 文档数据库的 REST Web 界面。
- 引言
- 什么是 REST?
- RaptorDB REST 做什么?
- 如何使用 RaptorDB REST
- 内部工作原理
- RestServer 如何工作
- 附录 v1.0.1 - 网页的奇妙世界
- 附录 v1.1.0 - 排序
- 之前版本
- 历史
前言
本文档和相关代码本应是RaptorDB
主发行版的一部分,但由于它是一个概念验证,我希望获得用户的反馈,因此我决定创建一篇单独的文章。如果一切顺利,我将将其集成到主要版本中,所以请告诉我您的想法。 简介
编程世界正在发生变化,越来越多地倾向于像 JavaScript 和 Web 应用程序这样的语言(这让像我这样的老程序员感到沮丧),如果你想生存,你必须适应,所以本文档和相关代码是 RaptorDB
的 REST Web 接口实现。
将 RaptorDB
与 Web 应用程序结合使用有几个原因:
- 您已经拥有使用
RaptorDB
的 .net 应用程序,并且希望利用现有的基础设施。
- 即使您不使用 .net,使用像
RaptorDB
这样高性能的 NoSQL 数据库也很有吸引力。
本文使用以下参考资料:
什么是 REST?
从根本上说,REST 接口只是在 URL 路径上实现 HTTP 的 POST GET PUT DELETE 指令。这意味着您可以提供路径和参数并查询该路径的信息。数据通常以 XML 或 JSON 格式返回给调用者。
RaptorDB REST 做什么?
RaptorDB REST
将启动一个 HTTP 侦听器并处理来自您提供的路由表的传入请求。处理过程包括保存 JSON 文档以及检索这些文档创建的相同或来自视图的数据。
局限性 & 要求
使用此 REST 接口的主要限制是您会丢失多态性,因为 JSON 到对象的反序列化器无法确定嵌套类型,因为这些信息不包含在 JSON 输入字符串中 (fastJSON
的 $type
扩展)。
要 POST JSON 文档,您必须提供一个字符串 GUID
值,这是唯一标识该文档所必需的。如果您正在处理多用户应用程序,那么这应该不是问题,因为生成客户端 ID 比往返服务器更可取。
如何使用 RaptorDB REST
您可以通过运行输出文件夹中的 testing.exe
应用程序轻松测试 REST 接口。这个控制台应用程序将在端口 8080 上启动 REST 服务器,并生成 100,000 个 Salesinvoice
实体并将它们插入 RaptorDB
数据库。
执行此操作的代码很简单:
var rest = new RestServer(8080, @"..\RaptorDBdata");
但在开始之前,您应该了解以下概念。
概念
在使用RaptorDB REST
时,您应该了解以下关键概念:
- REST 特定:URL、路由表、实体容器
- RaptorDB 特定:实体对象、视图和映射函数(请参阅此处的文章 https://codeproject.org.cn/Articles/375413/RaptorDB-the-Document-Store)
此外,已定义实体类的存在是一个决定性因素,因为路由和视图都依赖于实体或数据容器。
您可以通过以下两种方式使用 RaptorDB REST
:
- 在您的已编译代码中,通过代码初始化
- 使用路由脚本文件
脚本文件方法非常强大,并且是执行此操作的首选方式,因为您可以在运行时更改实现。
URL
所有 REST 服务都从 URL 路径开始,因此您应该创建有意义且具有实际名称的路径。URL 的端点可以是以下之一:- 用于插入/更新数据的
POST
端点 - 指向
RaptorDB
视图的GET
端点 - 指向
RaptorDB
视图上聚合函数的GET
端点
路由表
所有精彩之处都在于路由表。这类似于 Node.js 的 Express 框架,您在该框架中创建 URL 路径并提供一个闭包函数来执行工作。此路由表由“Datafolder\Routing
”文件夹中的 *.route
文件填充。这使您可以将应用程序分解为逻辑上独立的部分,并单独管理它们,系统将在运行时组装这些部分。有用的工具
为了测试 REST 接口,我发现以下工具必不可少:- Google Chrome 浏览器
- POSTMan Chrome 扩展程序,用于 POST 和 GET 调用(http://www.getpostman.com)
示例路由代码
示例 crm.route 文件由以下部分组成:
// load the following dll as a reference for salesinvoices etc.
// ref : views.dll
using System;
using System.Collections.Generic;
using System.Linq;
...
上面的代码片段来自代码顶部,它允许您使用 ref 注释加载现有 DLL,并链接到您的脚本文件以访问已定义的类型。
... namespace crm { public class Customer { public string Name; public string Address; } ...在上面的命名空间内,我们定义了一个
Customer
类来映射传入的 JSON 对象 POST,因为 RaptorDB
需要 .net 类才能进行操作。如果您有现有的基础设施和应用程序,其中包含实体类型,您可以链接到这些 .net 类型,而不是在此处定义。...
public class crmrouting : IRDBRouting
{
public void Initialize(IRouteAPI api)
{
api.AddRoute(new RDBRoute { URL = "crm/customer", EntityType = typeof(Customer) });
api.AddRoute(new RDBRoute { URL = "crm/customerview", Viewname = "customer"});
...
上面的代码是一个路由表定义,它继承自 IRDBRouting
接口。在 Initialize()
方法中,我们为“crm/customer”URL 定义了一个 POST 处理程序,并将传入的 JSON 映射到 Customer
类型。
第二行将“crm/customerview”URL 的 GET 请求映射到名为“customer”的 RaptorDB
视图,该视图稍后在文件中定义。
...
api.AddRoute(new RDBRoute
{
URL = "testing/function",
function = (rdb, filter) =>
{
var q = rdb.Query<SalesItemRowsViewRowSchema>(filter);
var res = from x in q.Rows
group x by x.Product into g
select new
{
Product = g.Key,
TotalPrice = g.Sum(p => p.Price),
TotalQTY = g.Sum(p => p.QTY)
};
return res.ToList<object>();
}
});
...
Initialize()
方法中的上述路由是一个非常强大的闭包,用于计算 RaptorDB
视图上的聚合。
您可以自由地使用 .net LINQ 的全部功能处理该函数,filter 参数是用户从 REST 调用中给出的。
下面显示了上述所有内容的示例。
..
[RegisterView]
public class CustomerView : View<Customer>
{
public class CustomerRowSchema : RDBSchema
{
public string Name;
public string Address;
}
public CustomerView()
{
this.Name = "Customer";
...
在 crm.route
文件的末尾,我们为文件开头定义的 Customer
实体定义了一个 RaptorDB
视图。因此,在一个脚本文件中,我们创建了一个处理程序和处理数据的逻辑,所有这些都以类型安全的方式进行,它将在运行时编译和加载,因此我们拥有脚本的易变性和编译语言的全部功能和性能。
运行示例应用程序
让我们开始使用示例应用程序。首先,我们来查询一下。https://:8080/crm/salesinvoice?serial<100?start=100?count=10
从上面我们可以看到查询的基本结构,我们有一个基于 RaptorDB
视图‘serial<100
’的模式的查询过滤器,以及分页扩展‘start=100
’和‘count=10
’。您可以省略其中任何一个,您将获得相关数据。
在结果中,我们可以看到返回的数据行,以及查询在 TotalCount
字段中有多少行,以及返回了多少行在 Count
字段中。
要查看视图中所有数据的计数,我们可以执行以下操作,这是一个快捷方式,结果在 TotalCount
字段中。
https://:8080/crm/salesitemrows?count=0

我们可以以类似的方式调用聚合,这会计算对所有行的分组。
https://:8080/testing/function
或者带有过滤器:
https://:8080/testing/function?product="prod 1"

POST 操作稍微复杂一些,因为我们无法通过浏览器地址框完成,所以请在 Google Chrome 中打开 PostMan,从下拉列表中选择 POST,然后输入以下内容:
https://:8080/crm/customer?id=A5AE46F0-E114-4659-A4AF-F285CD00A93D
我们将以下 JSON 文档作为“原始”数据提供。通过按“发送”,我们应该会收到“posted :...”的回复。
{"name":"aa","address":"safasdfasd","age":9090,"id":"A5AE46F0-E114-4659-A4AF-F285CD00A93D"}
RaptorDB REST
的一个要求是,在 POST 时必须提供该文档的 GUID
,并且 JSON 文档中也应该包含它,但您可以省略它。
要获取原始 JSON,我们可以 GET POST URL 并提供 GUID
。
https://:8080/crm/customer?id=A5AE46F0-E114-4659-A4AF-F285CD00A93D
如果一切顺利,我们可以查询我们定义的“customer”RaptorDB
视图,并在其中看到我们的文档。
https://:8080/crm/customerview

内部工作原理
RaptorDB
的 REST 接口的核心是 HTTPListener
。这是 .net 团队的一项出色工作,但有些被低估了。为了发挥这个类的强大功能,让我们看看我们是如何使用它的:_server = new HttpListener();
_server.Prefixes.Add("http://*:" + _port + "/");
_server.Start();
while (_run)
{
var context = _server.BeginGetContext(new AsyncCallback(ListenerCallback), _server);
context.AsyncWaitHandle.WaitOne();
}
这就是全部代码!它是异步且高性能的(尽管同步版本性能差不多)。HTTPListener
接受一个它将监听的前缀,您像下面这样处理 AsyncCallBack
:
private void ListenerCallback(IAsyncResult ar)
{
var listener = ar.AsyncState as HttpListener;
var ctx = listener.EndGetContext(ar);
//do some stuff
string path = ctx.Request.Url.GetComponents(UriComponents.Path, UriFormat.Unescaped).ToLower();
RDBRoute r = null;
ctx.Response.ContentEncoding = UTF8Encoding.UTF8;
if (_routing.TryGetValue(path, out r))
{
string mth = ctx.Request.HttpMethod;
if (mth == "POST" || mth == "PUT")
ProcessPOST(_rdb, ctx, path, r);
else
ProcessGET(_rdb, ctx, path, r);
}
else
WriteResponse(ctx, 404, "route path not found : " + ctx.Request.Url.GetComponents(UriComponents.Path, UriFormat.Unescaped));
ctx.Response.OutputStream.Close();
}
因此,在上面的代码中,我们正在检查路由表中是否有匹配的路径并处理请求。
RestServer 如何工作
总的来说,RestServer()
的工作方式如下:正如您所见,RestServer
接收一个 JSON 文档,将其映射到 .net 对象,然后将其发送到 RaptorDB
,因为它期望的那样。由于原始 JSON 文档中可能包含您未预料到的更多内容,因此 RestServer
还会将原始内容保存到键/值存储中。
所有对 JSON 文档的 GET 请求都将从存储的原始 JSON 中处理,而不是在 RaptorDB
中映射的版本。
查询是从 RaptorDB
中存储的视图处理的。
附录 v1.0.1 - 网页的奇妙世界
在尝试让 REST 接口与 JavaScript 客户端一起工作时,我遇到了一些 HTTP 协议的怪异之处以及浏览器如何处理请求。我花了整整一天的时间来找出这个修复方法,当我启用 Chrome 浏览器中的调试器时,才得以解决,因为浏览器会默默地不做任何事,只有在启用调试器时才能看到错误消息。
问题是浏览器要求 JSON 负载必须与页面来自同一域地址(出于跨站点脚本攻击的考虑)。为了解决这个问题,您需要在 REST 服务器上添加以下行:
Response.AppendHeader("Access-Control-Allow-Origin", "*");
现在是精彩的部分,因为 JSON 负载可能非常大,您需要某种压缩,否则传输如此大的数据会很慢并阻塞浏览器。要进行压缩,只需执行以下操作:
byte[] b = Encoding.UTF8.GetBytes(msg);
if (b.Length > 100*1024)
{
using (var ms = new MemoryStream())
{
using (var zip = new GZipStream(ms, CompressionMode.Compress, true))
zip.Write(b, 0, b.Length);
b = ms.ToArray();
}
ctx.Response.AddHeader("Content-Encoding", "gzip");
}
ctx.Response.ContentLength64 = b.LongLength;
上述内容的优点是,它对客户端 JavaScript 完全透明,并且由服务器和浏览器处理。如您所见,如果发送的消息大于 100KB,它将被压缩。这节省了大量时间和带宽。
此外,在此版本中还添加了以下内部路由:
- RaptorDB/GetRoutes:将为您提供可用已注册路由的列表
- RaptorDB/GetViews:将为您提供已注册
RaptorDB
视图的列表 - RaptorDB/GetSchema:将为您提供您查询的视图的模式
testpage.html
为了帮助测试 REST 接口,我添加了 testpage.html 文件,您可以在下面看到它,而不是使用 POSTMan,而是使用真实的 JavaScript 客户端。
上面的页面使用 zepto.js [https://zepto.node.org.cn/] 作为 jQuery 的替代品,它更小且功能相同。

通过按下此页面上的按钮,您将获得从服务器发送的 JSON 的精美(可以说是)表格表示。您可以更改文本框中的值并获得不同的结果。
附录 v1.1.0 - 排序
在此版本中,您现在可以在分页数据时进行排序。要执行此操作,请在您的 URL 调用中附加一个 OrderBy
子句:
https://:8080/salesinvoice?serial<100 & count = 10 & orderby = serial desc
从上面可以看出,查询将返回 10 个项,按 serial 列降序排序。
此版本中的一个不错的附加功能是能够提供静态内容,因此现在如果您调用服务器的基础 URL,您将获得 testpage.html,正如您从下面的图像中看到的,我已经更改了 HTML,以便您可以轻松更改前缀并从其他系统调用函数。
之前版本
您可以在此处下载之前的版本
- 下载 SampleApp_EXE_v1.0.0.zip
- 下载 RaptorDB_rest_v1.0.0.zip
- 下载 SampleApp_EXE_v1.0.1.zip
- 下载 RaptorDB_rest_v1.0.1.zip
历史
- 初始版本 v1.0.0:2013 年 11 月 5 日
- 更新 v1.0.1:2013 年 11 月 16 日
- 使用单引号或双引号的 GUID ID 进行 POST
- HTTP 头部的访问控制对所有人开放
- 如果 JSON 超过阈值(默认为 100kb),则压缩 JSON
- 使用 ? 或 & 分隔符在查询中开始和计数
- 在 RaptorDB/GetViews、RaptorDB/GetRoutes、RaptorDB/GetSchema 下添加了信息路由
- 添加了 testpage.html
- 更新 v1.1.0:2013 年 12 月 5 日
- 为查询添加了排序
- REST 服务器现在可以提供静态内容和 testpage.html,只需调用根 URL 即可
- 删除了额外的查询重载,以支持新模型