提供Shelf组件,可以轻松创建统一的,分层的REST资源,并且只需极少的样板。
shelf_rest是shelf_route的一个替代品。 它支持shelf_route的所有功能,增加了很多功能以减少样板。
而不是导入shelf_route
import 'package:shelf_route/shelf_route.dart';
你导入shelf_rest
import 'package:shelf_rest/shelf_rest.dart';
注意:不要同时导入两者。
如果您愿意,可以继续使用它与shelf_route完全相同,例如。
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_rest/shelf_rest.dart';
void main() {
var myRouter = router()
..get('/accounts/{accountId}', (Request request) {
var account =
new Account.build(accountId: getPathParameter(request, 'accountId'));
return new Response.ok(JSON.encode(account));
});
printRoutes(myRouter);
io.serve(myRouter.handler, 'localhost', 8081);
}
class Account {
final String accountId;
Account.build({this.accountId});
Account.fromJson(Map json) : this.accountId = json['accountId'];
Map toJson() => {'accountId': accountId};
}
使用普通的Dart函数作为处理程序
由于shelf_rest自动捆绑shelf_bind,您现在可以移除大部分锅炉板。
var myRouter = router()
..get('accounts/{accountId}',
(String accountId) => new Account.build(accountId: accountId));
这里,accountId路径参数自动从请求中提取,并作为变量传递给处理函数。 此外,返回的account会自动转换为JSON。
有关与处理程序一起使用的功能的更多详细信息,请参阅shelf_bind的文档。
您可以使用addAll方法将路由分组到类中并将它们安装在给定的子路由中。
class AccountResource {
void createRoutes(Router r) {
r..get('{accountId}', (String accountId) => new Account.build(accountId: accountId));
}
}
void main() {
var myRouter = router()..addAll(new AccountResource(), path: 'accounts');
printRoutes(myRouter);
io.serve(myRouter.handler, 'localhost', 8081);
}
由于UserResource中的createRoutes方法采用类型为Router的单个参数,因此将自动调用此方法。
您可以使用Get注解,而不是实现采用路由的方法(如上面的createRoutes)。
class AccountResource {
@Get('{accountId}')
Account find(String accountId) => new Account.build(accountId: accountId);
}
对于Router上的所有方法都存在注解,例如@ Get,@ Post,@ Put,@ Delete和@AddAll,这些注解支持与相应方法完全相同的参数。
@AddAll注解用于添加嵌套路由(子资源)。 例如
class AccountResource {
@AddAll(path: 'deposits')
DepositResource deposits() => new DepositResource();
}
注意:@AddAll目前仅支持方法。 可能在未来版本中支持getter
大多数REST资源往往包含许多标准CRUD操作。
为了进一步减少样板并帮助实现一致性,shelf_rest对实现这些CRUD操作提供了特殊支持。
例如,银行帐户的RESTful资源可能具有以下类型的操作
GET /accounts?name='Freddy'
GET /accounts/1234
POST /accounts
PUT /accounts/1234
DELETE /accounts/1234
这是shelf_rest中的标准模式,可以按如下方式实现
@RestResource('accountId')
class AccountResource {
List<Account> search(String name) => .....;
Account create(Account account) => .....;
Account update(Account account) => .....;
Account find(String accountId) => ...;
void delete(String accountId) => ...;
}
@RestResource('accountId')注解用于表示支持标准CRUD操作的类,并告诉shelf_rest使用accountId作为路径变量。 DELETE的路由看起来像
DELETE /accounts/{accountId}
shelf_rest遵循标准命名约定以最小化配置。 这也有助于提高您对方法命名方式的一致性。
但是,您可以使用ResourceMethod注解覆盖默认命名
@ResourceMethod(operation: RestOperation.FIND)
Account fetchAccount(String accountId) => ...;
创建分层REST资源很常见。
例如,我们可能希望允许存款到我们的帐户,如下所示
PUT -> /accounts/1234/deposits/999
您可以使用上述标准@AddAll注解添加子资源。
@RestResource('accountId')
class AccountResource {
....
@AddAll(path: 'deposits')
DepositResource deposits() => new DepositResource();
}
DepositResource可能看起来像
@RestResource('depositId')
class DepositResource {
@ResourceMethod(method: 'PUT')
Deposit create(Deposit deposit) => ...;
}
请注意,创建操作的默认HTTP方法是POST。 当我们在调用create时知道资源的主键时经常使用PUT。
在shelf_rest中,我们通过使用ResourceMethod注解覆盖HTTP方法来实现。
要查看此操作,我们使用printRoutes函数
printRoutes(router);
您可以看到创建了以下路由
GET -> /accounts{?name} => bound to search method
POST -> /accounts => bound to create method
GET -> /accounts/{accountId} => bound to find method
PUT -> /accounts/{accountId} => bound to update method
DELETE -> /accounts/{accountId} => bound to delete method
PUT -> /accounts/{accountId}/deposits/{depositId} => bound to create method of DepositResource
请注意,任何不是现有路径变量的参数都将添加到uri模板的查询中。 所以
List<Account> search(String name) => .....;
产生
GET -> /accounts{?name}
您可以使用ResourceMethod注解添加将包含在为资源方法创建的路由中的中间件。
@ResourceMethod(middleware: logRequests)
Account find(String accountId) => ...;
同样,您可以将它们添加到Get和AddAll等所有Route注解中。 例如
@AddAll(path: 'deposits', middleware: logRequests)
DepositResource deposits() => new DepositResource();
由于shelf_bind用于从资源方法创建Shelf处理程序,因此请求参数的验证是免费的(由约束提供)。
有关详细信息,请参阅shelf_bind并约束doco。
默认情况下,验证已关闭。 您可以针对特定资源方法启用验证
@ResourceMethod(validateParameters: true)
Account find(String accountId) => ...;
您还可以通过创建新的handlerAdapter来在路由器层次结构的任何级别打开它。 例如,您可以按如下方式为所有路由打开它
var router = router('/accounts', new AccountResource(),
handlerAdapter: handlerAdapter(validateParameters: true,
validateReturn: true);
shelf_rest支持使用HATEOAS链接返回响应。 用于操作这些链接的模型位于hateoas_models包中,也可以在客户端上使用。
要使用,只需在ResourceLinksFactory类型的处理程序方法中添加一个参数。 例如
AccountResourceModel find(
String accountId, ResourceLinksFactory linksFactory) =>
new AccountResourceModel(
new Account.build(accountId: accountId), linksFactory(accountId));
这里的AccountResourceModel只是一个包含Account和HATEOAS资源链接的简单类。
class AccountResourceModel extends ResourceModel<Account> {
final Account account;
AccountResourceModel(this.account, ResourceLinks links) : super(links);
Map toJson() => super.toJson()..addAll({'account': account.toJson()});
}
查找操作的典型响应如下所示
{
"account": {
"accountId": "123",
"name": 'fred'
},
"links": [
{
"href": "123",
"rel": "self"
},
{
"href": "123",
"rel": "update"
},
{
"href": "123/deposits/{?deposit}",
"rel": "deposits.create"
}
]
}
指定路由的所有不同形式可以一起使用。 一种常见的方法是将@RestResource方法与@Get,@ Post,@ Put,@ Delete注解一起用于标准CRUD操作以及不适合标准模型的操作。
使用将Router作为其唯一参数(称为RouteableFunctions)的方法提供了更流畅的替代方案。 特别适用于像mojito这样的框架,例如,使用流畅的api扩展路由器以创建oauth路由。
shelf_rest默认使用以下约定。 每个都可以用注解覆盖。
TODO:更多doco