提供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