专栏首页Google DartDart服务器端 shelf_rest包 原

Dart服务器端 shelf_rest包 原

介绍

提供Shelf组件,可以轻松创建统一的,分层的REST资源,并且只需极少的样板。

shelf_restshelf_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

使用RestResource注解

大多数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) => ...;

同样,您可以将它们添加到GetAddAll等所有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);

HATEOAS支持

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默认使用以下约定。 每个都可以用注解覆盖。

  • create ... POST

TODO:更多doco

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 对象序列化与反序列化 原

    南郭先生
  • AngularDart4.0 指南- 依赖注入 顶

    依赖注入是一个重要的应用程序设计模式。 它的用途非常广泛,几乎所有人都称之为DI。

    南郭先生
  • AngularDart4.0 英雄之旅-教程-07路由 顶

    如果该应用程序尚未运行,请启动该应用程序。 在进行更改时,请通过重新加载浏览器窗口来保持运行。

    南郭先生
  • 用建造者模式实现链式赋值,代码真清爽

    前段时间写了个项目,一个类的属性那叫一个多啊。刚开始直接写一堆set代码,后来set代码实在是太多了,真心看不下去了,用建造者模式重构了一下,嗯,看起来舒服多了...

    Java识堂
  • 构造方法、封装、关键字(this、static)和代码块的介绍

    构造方法在初始化一个类的对象时进行调用,它没有返回值,方法名与类名相同,而成员方法是则是由对象主动调用,它有返回值,表现为对象的操作行为。

    java架构师
  • 算法复杂度分析与最大子串问题算法复杂度分析最大子序列问题

    算法复杂度分析 算法复杂度基本定义 算法复杂度分析基于以下四条定义: 如果存在常数c与$n_{0}$使$N \geq n_{0} $时,有$T(N) \leq ...

    月见樽
  • 《Golang从入门到跑路》之指针

    在Go语言中的值类型有:int、float、bool、string、array、struct。它们都有对应的指针类型,比如:*int、*float 等。

    极客运维圈
  • Jdbc源码详解(二):获取connection

    木东居士
  • git 合并策略

    不清楚 git 冲突的表示方法,不了解 git 的合并原理,不知道 git 解冲突的多种策略。即便如此,大多数人依然可以正常使用 git 完成合...

    walterlv
  • 聊聊rocketmq的registerConsumer与unregisterConsumer

    本文主要研究一下rocketmq的registerConsumer与unregisterConsumer

    codecraft

扫码关注云+社区

领取腾讯云代金券