提供shelf中间件,容许您将普通Dart功能用做货架处理程序。java
shelf_bind赋予你:数据库
shelf_bind倾向于约定优于配置,所以您能够编写必要的最小代码,但仍然能够根据须要覆盖默认值。json
shelf_bind是一个强大的绑定框架,支持:app
它能够用做独立的shelf组件,也能够做为将其与其余组件集成的框架的一部分。框架
将它与shelf_route一块儿使用的最简单方法是使用mojito或shelf_rest,由于他们的路由器已经在shelf_bind中链接。async
若是您刚开始,我建议首先查看mojito并使用此README做为有关处理程序绑定的更多详细信息。函数
若是您使用带有mojito或shelf_rest的shelf_bind,则能够跳过此独立使用部分。post
bind函数从普通的dart函数建立一个shelf Handler。ui
var handler = bind(() => "Hello World");
这会建立一个等效于的 shelf Handlerthis
var handler = (Request request) => new Response.ok("Hello World");
若是函数返回Future,那么它将映射到Future <Response>
bind(() => new Future.value("Hello World"))
如今你能够设置一个shelf-io server来为你带来急需的问候世界(awthanks)
io.serve(bind(() => "Hello World"), 'localhost', 8080);
添加到函数中的任何简单类型参数都将与同名的路径参数匹配。
名称将自动在snake_case和camelCase之间转换
(String name) => "Hello $name"
shelf_bind支持绑定到任何路径参数,包括:
它使用shelf_path访问路径参数,这意味着它将与任何使用shelf_path在Request上下文属性中存储路径参数的中间件(例如shelf_route)一块儿使用。
这也意味着它不依赖于任何特定的表示路径的格式。 例如,路径是否认义为/ greeting /:name或/ greeting / {name}或/ person {?name}或其余什么并不重要。
您还能够绑定到int这样的简单类型
(String name, int age) => "Hello $name of age $age"
支持
若是您想要支持新类型,请提交功能请求(或pull请求)
您也可使用带有默认值的可选命名参数。
(String name, {int age: 20}) => "Hello $name of age $age"
若是在上下文中未提供(或为null)命名参数,则将使用默认值。
您能够将多个路径参数绑定到您本身的类中。 高级部分对此进行了描述。
默认状况下,非简单类型的处理程序参数来自body。
这包括:
例如,下面的处理程序参数都将被假定为来自request body。
(Map myMap) => ... (List myList) => ... (Person myMap) => ...
shelf_bind目前支持JSON和FORM编码的主体。
默认状况下,shelf_bind尝试肯定请求内容类型的编码,以下所示:
您可使用@RequestBody注解覆盖此行为。 若是存在@RequestBody注解,则内容将被视为注解中提供的类型。
例如,不管请求内容类型如何,如下内容都将被视为FORM编码
(@RequestBody(format: ContentType.FORM) Map myMap) => ...
只需将其做为参数添加到函数中,便可访问shelf Request对象。
注意:因为您能够直接访问请求的全部部分,包括标题,所以您不多须要这样作。
(String name, Request request) => "Hello $name ${request.method}"
默认状况下,经过调用JSON.encode将函数的返回值编码为JSON。
例如,您能够返回地图
() => { "greeting" : "Hello World" }
这适用于任何能够编码为JSON的内容,包括任何自定义类
class SayHello { String greeting; Map toJson() => { 'greeting': greeting }; } SayHello myGreeter() => new SayHello()..greeting = "Hello World"
您能够按照“注解一节中的说明覆盖默认状态代码。
若是要彻底控制响应,能够直接返回Shelf Response
() => new Response.ok("Hello World")
shelf_bind不会对错误执行任何特定格式设置。 相反,它将它留给上游中间件来处理,例如shelf_exception_handler。
这容许您将全部错误处理保存在一个位置。
import 'package:http_exception/http_exception.dart'; () => throw new BadRequestException()
在一些shelf_exception_handler中间件中补救
var handler = const Pipeline() .addMiddleware(exceptionHandler()) .addHandler(bind(() => throw new BadRequestException()));
咱们获得一个将返回400响应的处理程序。
要调整如何执行请求路径参数的绑定,请使用@PathParam注解。
您能够更改路径名的默认映射。 例如,若是您有一个名为argOne的处理程序参数,则默认状况下会映射到名为arg_one的请求路径参数
若是您但愿将其映射到arg1,则能够按以下方式指定
(@PathParam(pathName: 'arg1') String argOne) => ...
要调整如何执行请求正文的绑定,请使用@RequestBody批注。
注意,只有一个处理程序参数能够映射到正文。
#### JSON
要强制将body始终解释为JSON,请将格式设置以下
bind(@RequestBody(format: ContentType.JSON) Person person) => "Hello ${person.name}")
####Form
bind(@RequestBody(format: ContentType.FORM) Person person) => "Hello ${person.name}")
您可使用ResponseHeaders批注覆盖成功返回处理程序方法时设置的默认状态(200)。 您还能够将location header
设置为传入请求网址。
@ResponseHeaders.created() String _create(String name) => "Hello $name"; final handler = bind(_create);
您能够将状态设置为您喜欢的任何内容
@ResponseHeaders(successStatus: 204) String _whatever(String name) => "Hello $name";
在POST上设置location
字段时,返回对象上的主键字段用于路径的最后一段。
默认状况下,主键字段为id,但能够经过指定idField参数来覆盖它。
@ResponseHeaders.created(idField: #name) Person _create(@RequestBody() Person person) => person;
name字段如今用于最后一个段。 例如,若是对http://localhost/person进行POST而且名称为fred,则该位置将设置为
location: http://localhost/person/fred
shelf_bind的主要用途之一是使用像shelf_route这样的路由器。
最简单的方法就是使用mojito或shelf_rest,由于它们提供了开箱即用的功能
当bind返回一个Handler时,你能够简单地将该处理程序传递给shelf_route的Router方法
var myRouter = router() ..get('/', bind(() => "Hello World"));
不可能轻松多了。 可是,必须将全部处理程序包装在绑定中会增长一些噪音。 为避免这种状况,咱们能够先将HandlerAdapter安装到路由中。 shelf_bind提供了一个开箱即用的功能。
var myRouter = router(handlerAdapter: handlerAdapter()) ..get('/', () => "Hello World");
如下显示了使用shelf_route做为路由的上述全部示例处理程序
import 'package:shelf/shelf.dart' as shelf; import 'package:shelf/shelf_io.dart' as io; import 'package:shelf_route/shelf_route.dart' as route; import 'package:shelf_bind/shelf_bind.dart'; import 'package:http_exception/http_exception.dart'; import 'package:shelf_exception_handler/shelf_exception_handler.dart'; import 'dart:async'; void main() { var router = route.router(handlerAdapter: handlerAdapter()) ..get('/', () => "Hello World") ..get('/later', () => new Future.value("Hello World")) ..get('/map', () => {"greeting": "Hello World"}) ..get('/object', () => new SayHello()..greeting = "Hello World") ..get('/ohnoes', () => throw new BadRequestException()) ..get('/response', () => new Response.ok("Hello World")) ..get('/greeting/{name}', (String name) => "Hello $name") ..get('/greeting2/{name}{?age}', (String name, int age) => "Hello $name of age $age") ..get('/greeting3/{name}', (Person person) => "Hello ${person.name}") ..get( '/greeting5/{name}', (String name, Request request) => "Hello $name ${request.method}") ..post('/greeting6', (Person person) => "Hello ${person.name}") ..get('/greeting8{?name}', (@PathParams() Person person) => "Hello ${person.name}"); var handler = const shelf.Pipeline() .addMiddleware(shelf.logRequests()) .addMiddleware(exceptionHandler()) .addHandler(router.handler); route.printRoutes(router); io.serve(handler, 'localhost', 8080).then((server) { print('Serving at http://${server.address.host}:${server.port}'); }); } class SayHello { String greeting; Map toJson() => { 'greeting': greeting }; } class Person { final String name; Person.build({this.name}); Person.fromJson(Map json) : this.name = json['name']; Map toJson() => { 'name': name }; }
请参阅example/binding_example.dart中项目中的更多详细示例
将多个路径参数绑定到您的类中
您可使用@PathParams注解将路径变量绑定到类的属性。
class Person { String name; } bind((@PathParams() Person person) => "Hello ${person.name}")
若是您更喜欢不可变类,那么您能够绑定到构造函数
class Person { final String name; Person.build({this.name}); }
构造函数必须对全部属性使用命名参数,而且名称必须与请求路径参数名称匹配。
默认状况下,构造函数必须称为build。 未来可使用注解覆盖它。
shelf_bind与强大的Constrain包集成,以支持处理程序函数参数的自动验证。
经过validateParameters属性启用验证到绑定功能
bind((Person person) => "Hello ${person.name}", validateParameters: true)
或者在使用shelf Router时,您能够在handlerAdapter上设置它以应用于全部路由(请参阅下面的shelf Route集成部分)
handlerAdapter: handlerAdapter(validateParameters: true)
如今让咱们用一些(人为的)约束来为Person类增添趣味。
class Person { @NotNull() @Ensure(nameIsAtLeast3Chars, description: 'name must be at least 3 characters') final String name; @NotNull() @Ensure(isNotEmpty) @Ensure(allStreetsStartWith15, description: "All streets must start with 15") List<Address> addresses; Person.build({this.name}); Person.fromJson(Map json) : this.name = json['name'], this.addresses = _addressesFromJson(json['addresses']); static List<Address> _addressesFromJson(json) { if (json == null || json is! List) { return null; } return json.map((a) => new Address.fromJson(a)).toList(growable: false); } Map toJson() => { 'name': name, 'addresses': addresses }; String toString() => 'Person[name: $name]'; } class Address { @Ensure(streetIsAtLeast10Characters) String street; Address.fromJson(Map json) : this.street = json['street']; Map toJson() => { 'street': street }; String toString() => 'Address[street: $street]'; } // The constraint functions Matcher nameIsAtLeast3Chars() => hasLength(greaterThan(3)); bool allStreetsStartWith15(List<Address> addresses) => addresses.every((a) => a.street == null || a.street.startsWith("15")); Matcher streetIsAtLeast10Characters() => hasLength(greaterThanOrEqualTo(10));
如今每当调用处理程序时,Person对象将在传递给Dart函数以前进行验证。 若是验证失败,将抛出BadRequestException(来自http_exception包),其中包含详细的约束违规。
若是你已正确配置了shelf_exception_handler,你会收到相似的响应
HTTP/1.1 400 Bad Request content-type: application/json { "errors": [ { "constraint": { "description": "all streets must start with 15", "group": "DefaultGroup", "type": "Ensure" }, "details": null, "invalidValue": { "type": "List", "value": [ "Address[street: blah blah st]" ] }, "leafObject": { "type": "Person", "value": "Person[name: fred]" }, "message": "Constraint violated at path addresses\nall streets must start with 15\n", "propertyPath": "addresses", "reason": null, "rootObject": { "type": "Person", "value": "Person[name: fred]" } } ], "message": "Bad Request", "status": 400 }
与处理程序函数参数验证相似,您可使用constrain包启用响应验证。 这是为了确保您永远不会发送无效数据。
经过validateReturn属性启用响应验证到绑定功能
(String name) => new Person(name)
若是验证失败,将抛出具备500状态的HttpException(来自http_exception包),由于这意味着您已经弄乱了代码;-)。
有关验证的更详细说明,请参阅“路径参数”部分的“验证”部分。
除了正常的请求相关数据(如路径参数,主体和头)以外,shelf_bind还支持将任意对象注入处理函数。 这些被称为自定义对象。
一般,这些对象是从与请求相关的数据中实例化的,但这不是必需的。
常见的用法是将客户端注入HTTP客户端和数据库客户端等远程服务。 可能须要以通过身份验证的用户身份调用这些服务。
将customObjects参数用于handlerAdapter或bind觉得这些对象注入您本身的工厂
bind((String name, PersonLookupClient client) => client.lookup(name), customObjects: customObjects);
var adapter = handlerAdapter(customObjects: customObjects);
customObjects参数只是从类型到工厂的映射。 工厂采用Request参数。
var customObjects = { PersonLookupClient: (req) => new Future.value(new PersonLookupClient()) }; class PersonLookupClient { Future<Person> lookup(String name) => new Future.value(new Person.build(name: name)); }
工厂可能会返回Future,在这种状况下,在将已解析的对象传递给处理程序方法以前将会解决future问题。
像mojito和shelf_rest这样的软件包会注入本身的自定义对象
有关全部选项的更多详细信息,请参阅Wiki
查看未解决的问题
个人博客即将搬运同步至腾讯云+社区,邀请你们一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2tt7f9yv2ry8g