upgrade dependencies - version 1.9

This commit is contained in:
Felix Blaschke
2024-02-23 19:36:04 +01:00
parent 1c3b2b4a9f
commit 5b2750e784
25 changed files with 116 additions and 133 deletions

View File

@ -1,3 +1,7 @@
## 1.9.0
- Upgrade dependencies
## 1.8.0
- `shelfRun()` lifecycle hooks `onWillClose` and `onClosed` supports asynchronous execution

View File

@ -7,7 +7,6 @@ the Shelf platform. It's a great base to **start off** your apps fast, while
**maintaining full compatibility** with the **Shelf** ecosystem.
<!-- #code doc_files/quickstart.dart -->
```dart
import 'package:shelf_plus/shelf_plus.dart';
@ -18,10 +17,9 @@ Handler init() {
app.get('/', () => 'Hello World!');
return app;
return app.call;
}
```
<!-- // end of #code -->
It comes with a lot of awesome features, like **zero-configuration** initializer, build-in **hot-reload**
@ -29,35 +27,29 @@ and a **super powerful** and **intuitive router upgrade**. Continue reading and
you can't ever code without **Shelf Plus**.
<!-- #toc -->
## Table of Contents
[**Router Plus**](#router-plus)
- [Routes API](#routes-api)
- [Middleware](#middleware)
- [ResponseHandler](#responsehandler)
- [Cascading multiple routers](#cascading-multiple-routers)
[**Middleware collection**](#middleware-collection)
- [setContentType](#setcontenttype)
- [typeByExtension](#typebyextension)
- [download](#download)
[**Request body handling**](#request-body-handling)
- [Object deserialization](#object-deserialization)
- [Custom accessors for model classes](#custom-accessors-for-model-classes)
- [Custom accessors for third party body parser](#custom-accessors-for-third-party-body-parser)
[**Shelf Run**](#shelf-run)
- [Custom configuration](#custom-configuration)
- [Multithreading](#multithreading)
[**Examples**](#examples)
- [Enable CORS](#enable-cors)
- [Rest Service](#rest-service)
- [WebSocket chat server](#websocket-chat-server)
@ -70,7 +62,6 @@ It **shares the same [routing logic](https://pub.dev/documentation/shelf_router/
but allows you to handle responses in a very simple way.
<!-- #code doc_files/router_plus_intro.dart -->
```dart
var app = Router().plus;
@ -86,7 +77,6 @@ app.get('/file', () => File('path/to/file.zip'));
app.get('/person', () => Person(name: 'John', age: 42));
```
<!-- // end of #code -->
The core mechanic is called **ResponseHandler** which continuously refines a data structure,
@ -96,11 +86,9 @@ This extensible system comes with support for text, json, binaries, files, json
You can access the **Router Plus** by calling the **`.plus`** getter on a regular Shelf Router.
<!-- #code doc_files/router_plus_upgrade.dart -->
```dart
var app = Router().plus;
```
<!-- // end of #code -->
### Routes API
@ -110,11 +98,9 @@ methods. You basically use an HTTP verb, define a route to match and specify a h
that generates the response.
<!-- #code doc_files/routes_api_verb.dart -->
```dart
app.get('/path/to/match', () => 'a response');
```
<!-- // end of #code -->
You can return any type, as long the **ResponseHandler** mechanism has a capable
@ -125,7 +111,6 @@ object, specify it as the first parameter. Any other parameter will match the
route parameters, if defined.
<!-- #code doc_files/routes_api_signature.dart -->
```dart
app.get('/minimalistic', () => 'response');
@ -138,7 +123,6 @@ app.get('/customer/<id>', (Request request) {
return 'response: ${request.routeParameter('id')}';
});
```
<!-- // end of #code -->
### Middleware
@ -146,7 +130,6 @@ app.get('/customer/<id>', (Request request) {
Router Plus provides several options to place your middleware ([Shelf Middleware](https://pub.dev/documentation/shelf/latest/shelf/Middleware.html)).
<!-- #code doc_files/middleware_intro.dart -->
```dart
var app = Router().plus;
@ -158,19 +141,16 @@ app.get('/request1', () => 'response', use: middlewareB);
// combine middleware with + operator
app.get('/request2', () => 'response', use: middlewareB + middlewareC);
```
<!-- // end of #code -->
You can also apply middleware dynamically inside a route handler, using the `>>` operator.
<!-- #code doc_files/middleware_in_requesthandler.dart -->
```dart
app.get('/request/<value>', (Request request, String value) {
return middleware(value) >> 'response';
});
```
<!-- // end of #code -->
### ResponseHandler
@ -193,7 +173,6 @@ ResponseHandler process the **return value** of a route handler, until it matche
_Example:_
<!-- #code doc_files/response_handler_example.dart -->
```dart
import 'dart:io';
@ -228,7 +207,7 @@ Handler init() {
onClose: (ws) => ws.send('Bye!'),
));
return app;
return app.call;
}
class Person {
@ -240,7 +219,6 @@ class Person {
Map<String, dynamic> toJson() => {'name': name};
}
```
<!-- // end of #code -->
#### Custom ResponseHandler
@ -249,7 +227,6 @@ You can add your own ResponseHandler by using a [Shelf Middleware](https://pub.d
created with the `.middleware` getter on a ResponseHandler function.
<!-- #code doc_files/response_handler_custom.dart -->
```dart
// define custom ResponseHandler
ResponseHandler catResponseHandler = (Request request, dynamic maybeCat) =>
@ -260,16 +237,13 @@ app.use(catResponseHandler.middleware);
app.get('/cat', () => Cat());
```
<!-- // end of #code -->
<!-- #code doc_files/response_handler_custom_cat.dart -->
```dart
class Cat {
String interact() => 'Purrrrr!';
}
```
<!-- // end of #code -->
### Cascading multiple routers
@ -279,6 +253,23 @@ So, you can also use it in a [Shelf Cascade](https://pub.dev/documentation/shelf
This package provides a `cascade()` function, to quickly set up a cascade.
<!-- #code doc_files/cascade.dart -->
```dart
import 'package:shelf_plus/shelf_plus.dart';
void main() => shelfRun(init);
Handler init() {
var app1 = Router().plus;
var app2 = Router().plus;
app1.get('/maybe', () => Response.notFound('no idea'));
app2.get('/maybe', () => 'got it!');
return cascade([app1.call, app2.call]);
}
```
<!-- // end of #code -->
```dart
import 'package:shelf_plus/shelf_plus.dart';
@ -307,13 +298,11 @@ to simplify common tasks.
Sets the `content-type` header of a `Response` to the specified **mime-type**.
<!-- #code doc_files/mw_set_content_type.dart -->
```dart
app.get('/one', () => setContentType('application/json') >> '1');
app.get('/two', () => '2', use: setContentType('application/json'));
```
<!-- // end of #code -->
<!-- #space1 -->
@ -324,11 +313,9 @@ Sets the `content-type` header of a `Response` to the **mime-type** of the
specified **file extension**.
<!-- #code doc_files/mw_type_by_extension.dart -->
```dart
app.get('/', () => '<h1>Hi!</h1>', use: typeByExtension('html'));
```
<!-- // end of #code -->
<!-- #space1 -->
@ -339,6 +326,15 @@ Sets the `content-disposition` header of a `Response`, so browsers will download
server response instead of displaying it. Optionally you can define a specific **file name**.
<!-- #code doc_files/mw_download.dart -->
```dart
app.get('/wallpaper/download', () => File('image.jpg'), use: download());
app.get('/invoice/<id>', (Request request, String id) {
File document = pdfService.generateInvoice(id);
return download(filename: 'invoice_$id.pdf') >> document;
});
```
<!-- // end of #code -->
```dart
app.get('/wallpaper/download', () => File('image.jpg'), use: download());
@ -357,7 +353,6 @@ You can access it by calling the `.body` getter on a [Shelf Request](https://pub
It comes with build-in support for text, JSON and binary.
<!-- #code doc_files/request_body_intro.dart -->
```dart
app.post('/text', (Request request) async {
var text = await request.body.asString;
@ -369,7 +364,6 @@ app.post('/json', (Request request) async {
return 'You send me: ${person.name}';
});
```
<!-- // end of #code -->
<!-- #space1 -->
@ -380,14 +374,11 @@ A recommended way to deserialize a json-encoded object is to provide a
**reviver function**, that can be generated by code generators.
<!-- #code doc_files/request_body_deserialize1.dart -->
```dart
var person = await request.body.as(Person.fromJson);
```
<!-- // end of #code -->
<!-- #code doc_files/request_body_deserialize2.dart -->
```dart
class Person {
final String name;
@ -400,7 +391,6 @@ class Person {
}
}
```
<!-- // end of #code -->
<!-- #space1 -->
@ -411,23 +401,19 @@ You can add own accessors for model classes by creating an
extension on `RequestBodyAccessor`.
<!-- #code doc_files/request_body_ext_model1.dart -->
```dart
extension PersonAccessor on RequestBodyAccessor {
Future<Person> get asPerson async => Person.fromJson(await asJson);
}
```
<!-- // end of #code -->
<!-- #code doc_files/request_body_ext_model2.dart -->
```dart
app.post('/person', (Request request) async {
var person = await request.body.asPerson;
return 'You send me: ${person.name}';
});
```
<!-- // end of #code -->
<!-- #space1 -->
@ -438,6 +424,14 @@ You can plug-in any other body parser by creating an
extension on `RequestBodyAccessor`.
<!-- #code doc_files/request_body_ext_third_party.dart -->
```dart
extension OtherFormatBodyParserAccessor on RequestBodyAccessor {
Future<OtherBodyFormat> get asOtherFormat async {
return ThirdPartyLib().process(request.read());
}
}
```
<!-- // end of #code -->
```dart
extension OtherFormatBodyParserAccessor on RequestBodyAccessor {
@ -452,7 +446,6 @@ extension OtherFormatBodyParserAccessor on RequestBodyAccessor {
Shelf Run is **zero-configuration** web-server initializer with **hot-reload** support.
<!-- #code doc_files/shelf_run_intro.dart -->
```dart
import 'package:shelf_plus/shelf_plus.dart';
@ -462,7 +455,6 @@ Handler init() {
return (Request request) => Response.ok('Hello!');
}
```
<!-- // end of #code -->
It's important to use a dedicated `init` function, returning a [Shelf Handler](https://pub.dev/documentation/shelf/latest/shelf/Handler.html),
@ -489,11 +481,9 @@ Shelf Run uses a default configuration, that can be modified via **environment v
You can override the default values with optional parameters in the `shelfRun()` function.
<!-- #code doc_files/shelf_run_override_default.dart -->
```dart
void main() => shelfRun(init, defaultBindPort: 3000);
```
<!-- // end of #code -->
### Multithreading
@ -505,7 +495,6 @@ You can enable the [shared](https://api.dart.dev/stable/2.16.0/dart-io/HttpServe
**Example of an application using multiple isolates**
<!-- #code doc_files/isolates.dart -->
```dart
import 'dart:isolate';
import 'package:shelf_plus/shelf_plus.dart';
@ -529,10 +518,9 @@ Handler init() {
return 'Hello from isolate: ${Isolate.current.debugName}';
});
return app;
return app.call;
}
```
<!-- // end of #code -->
You can test this application and compare different count of isolates:
@ -546,7 +534,6 @@ xargs -I % -P 8 curl "http://localhost:8080" < <(printf '%s\n' {1..400})
&nbsp;
&nbsp;
<!-- // end of #space -->
## Examples
@ -556,7 +543,6 @@ xargs -I % -P 8 curl "http://localhost:8080" < <(printf '%s\n' {1..400})
[CORS](https://developer.mozilla.org/docs/Web/HTTP/CORS) can be enabled by using the [shelf_cors_headers](https://pub.dev/packages/shelf_cors_headers) package:
<!-- #code example/example_cors/bin/example_cors.dart -->
```dart
import 'package:shelf_cors_headers/shelf_cors_headers.dart';
import 'package:shelf_plus/shelf_plus.dart';
@ -573,7 +559,6 @@ Handler init() {
return app;
}
```
<!-- // end of #code -->
### Rest Service
@ -583,7 +568,6 @@ Implementation of a CRUD, rest-like backend service. ([Full sources](example/exa
**example_rest.dart**
<!-- #code example/example_rest/bin/example_rest.dart -->
```dart
import 'dart:io';
@ -636,13 +620,11 @@ Handler init() {
return app;
}
```
<!-- // end of #code -->
**person.dart**
<!-- #code example/example_rest/bin/person.dart -->
```dart
import 'package:json_annotation/json_annotation.dart';
import 'package:uuid/uuid.dart';
@ -672,7 +654,6 @@ class Person {
static Person fromJson(Map<String, dynamic> json) => _$PersonFromJson(json);
}
```
<!-- // end of #code -->
### WebSocket chat server
@ -682,7 +663,6 @@ Implementation of a WebSocket-based chat application. ([Full sources](example/ex
**example_websocket_chat.dart**
<!-- #code example/example_websocket_chat/bin/example_websocket_chat.dart -->
```dart
import 'dart:io';
@ -729,5 +709,4 @@ Handler init() {
return app;
}
```
<!-- // end of #code -->

View File

@ -10,5 +10,5 @@ Handler init() {
app2.get('/maybe', () => 'got it!');
return cascade([app1, app2]);
return cascade([app1.call, app2.call]);
}

View File

@ -20,5 +20,5 @@ Handler init() {
return 'Hello from isolate: ${Isolate.current.debugName}';
});
return app;
return app.call;
}

View File

@ -12,5 +12,5 @@ Handler init() {
return middleware(value) >> 'response';
});
// #end
return app;
return app.call;
}

View File

@ -18,5 +18,5 @@ Handler init() {
// combine middleware with + operator
app.get('/request2', () => 'response', use: middlewareB + middlewareC);
// #end
return app;
return app.call;
}

View File

@ -16,5 +16,5 @@ Handler init() {
return download(filename: 'invoice_$id.pdf') >> document;
});
// #end
return app;
return app.call;
}

View File

@ -9,5 +9,5 @@ Handler init() {
app.get('/two', () => '2', use: setContentType('application/json'));
// #end
return app;
return app.call;
}

View File

@ -7,5 +7,5 @@ Handler init() {
// #begin
app.get('/', () => '<h1>Hi!</h1>', use: typeByExtension('html'));
// #end
return app;
return app.call;
}

View File

@ -7,5 +7,5 @@ Handler init() {
app.get('/', () => 'Hello World!');
return app;
return app.call;
}

View File

@ -14,5 +14,5 @@ Handler init() {
return 'You send me: ${person.name}';
});
return app;
return app.call;
}

View File

@ -12,7 +12,7 @@ Handler init() {
return 'You send me: ${person.name}';
});
// #end
return app;
return app.call;
}
extension PersonAccessor on RequestBodyAccessor {

View File

@ -17,5 +17,5 @@ Handler init() {
return 'You send me: ${person.name}';
});
// #end
return app;
return app.call;
}

View File

@ -16,7 +16,7 @@ Handler init() {
app.get('/cat', () => Cat());
// #end
return app;
return app.call;
}
class Cat {

View File

@ -31,7 +31,7 @@ Handler init() {
onClose: (ws) => ws.send('Bye!'),
));
return app;
return app.call;
}
class Person {

View File

@ -24,5 +24,5 @@ Handler init() {
app.get('/person', () => Person(name: 'John', age: 42));
// #end
return app;
return app.call;
}

View File

@ -6,5 +6,5 @@ Handler init() {
// #begin
var app = Router().plus;
// #end
return app;
return app.call;
}

View File

@ -16,5 +16,5 @@ Handler init() {
return 'response: ${request.routeParameter('id')}';
});
// #end
return app;
return app.call;
}

View File

@ -7,5 +7,5 @@ Handler init() {
// #begin
app.get('/path/to/match', () => 'a response');
// #end
return app;
return app.call;
}

View File

@ -19,7 +19,7 @@ Handler init() {
app.get('/file', () => File('thesis.pdf'));
return app;
return app.call;
}
class Person {

View File

@ -15,5 +15,5 @@ Handler init() {
app.get('/', () => 'world', use: greeterMiddleware);
return app;
return app.call;
}

View File

@ -51,7 +51,7 @@ class _RouterPlusHandler {
dynamic result;
try {
result = Function.apply(handler, [request, ...map.values.toList()]);
result = Function.apply(handler, [request, ...map.values]);
} on NoSuchMethodError catch (_) {
try {
result = handler();
@ -115,7 +115,7 @@ class RouterPlus {
_RouterPlusHandler(handler, [
..._middlewareList,
if (localMiddleware != null) localMiddleware,
]));
]).call);
_routeAdded = true;
}
@ -128,7 +128,7 @@ class RouterPlus {
_RouterPlusHandler(handler, [
..._middlewareList,
if (use != null) use,
]));
]).call);
_routeAdded = true;
}

View File

@ -1,24 +1,24 @@
name: shelf_plus
description: Shelf Plus is a quality of life addon for your server-side development within the Shelf platform.
version: 1.8.0
version: 1.9.0
repository: https://github.com/felixblaschke/shelf_plus
environment:
sdk: ">=2.17.0 <4.0.0"
dependencies:
shelf: ^1.4.0
shelf_router: ^1.1.3
shelf_static: ^1.1.1
shelf_hotreload: ^1.4.0
shelf_web_socket: ^1.0.3
shelf: ^1.4.1
shelf_router: ^1.1.4
shelf_static: ^1.1.2
shelf_hotreload: ^1.5.0
shelf_web_socket: ^1.0.4
mime_type: ^1.0.0
web_socket_channel: ^2.3.0
web_socket_channel: ^2.4.4
dev_dependencies:
lints: ^2.0.1
test: ^1.22.1
dio: ^4.0.0
json_annotation: ^4.7.0
json_serializable: ^6.5.4
build_runner: ^2.3.3
lints: ^3.0.0
test: ^1.25.2
dio: ^5.4.1
json_annotation: ^4.8.1
json_serializable: ^6.7.1
build_runner: ^2.4.8

View File

@ -18,7 +18,7 @@ void main() {
return 'ok';
});
server = await runTestServer(app);
server = await runTestServer(app.call);
await dio.Dio().post('${server.host}/route', data: 'hello');
@ -37,7 +37,7 @@ void main() {
return 'ok';
});
server = await runTestServer(app);
server = await runTestServer(app.call);
await dio.Dio().post('${server.host}/route', data: 'hello');
@ -53,7 +53,7 @@ void main() {
return 'ok';
});
server = await runTestServer(app);
server = await runTestServer(app.call);
await dio.Dio().post('${server.host}/route', data: {'a': '1', 'b': 2});
@ -70,7 +70,7 @@ void main() {
return 'ok';
});
server = await runTestServer(app);
server = await runTestServer(app.call);
await dio.Dio().post('${server.host}/route',
data: Person(name: 'john', age: 42).toJson());

View File

@ -25,7 +25,7 @@ void main() {
app.add('get', '/add-get', () => 'add-get');
app.all('/all', () => 'all');
server = await runTestServer(app);
server = await runTestServer(app.call);
expect(await server.fetchBody<String>('get', '/get'), 'get');
expect(await server.fetchBody<String>('head', '/head'), '');
@ -49,7 +49,7 @@ void main() {
app.get('/b2/<name>', (Request request) => 'b');
app.get('/b3/<name>', (Request request, String name) => 'b:$name');
server = await runTestServer(app);
server = await runTestServer(app.call);
expect(await server.fetchBody<String>('get', '/a1'), 'a');
expect(await server.fetchBody<String>('get', '/a2'), 'a');
@ -73,7 +73,7 @@ void main() {
app.get('/persons', () => persons);
app.get('/persons/john', () => persons.where((p) => p.firstName == 'John'));
server = await runTestServer(app);
server = await runTestServer(app.call);
var r1 = await server.fetch('get', '/object');
expect(r1.headers['content-type']?.first, 'application/json');
@ -109,7 +109,7 @@ void main() {
server = await runTestServer(Pipeline()
.addMiddleware(addResponseHandler([catHandler]))
.addHandler(app));
.addHandler(app.call));
expect(await server.fetchBody<String>('get', '/cat'), 'Purrr!');
});
@ -119,7 +119,7 @@ void main() {
app.get('/cat', () => Cat(), use: catHandler.middleware);
server = await runTestServer(app);
server = await runTestServer(app.call);
expect(await server.fetchBody<String>('get', '/cat'), 'Purrr!');
});
@ -130,7 +130,7 @@ void main() {
app.get('/number', () => '1', use: wrapBody('b') + wrapBody('c'));
server = await runTestServer(app);
server = await runTestServer(app.call);
expect(await server.fetchBody<String>('get', '/number'), 'a(b(c(1)))');
});
@ -143,7 +143,7 @@ void main() {
app.use(returnHello());
server = await runTestServer(app);
server = await runTestServer(app.call);
expect(await server.fetchBody<String>('get', '/'), 'hello');
expect(await server.fetchBody<String>('post', '/dynamic_route'), 'hello');
@ -156,7 +156,7 @@ void main() {
app.get('/person', () => Person(firstName: 'John', lastName: 'Doe'));
server = await runTestServer(app);
server = await runTestServer(app.call);
var r1 = await server.fetch('get', '/person');
expect(r1.headers['content-type']?.first, 'application/json');
@ -172,7 +172,7 @@ void main() {
return File('test/test_data/$path');
});
server = await runTestServer(app);
server = await runTestServer(app.call);
var r1 = await server.fetch('get', '/bird');
expect(r1.headers[HttpHeaders.contentTypeHeader]?.first, 'image/jpeg');
@ -193,7 +193,7 @@ void main() {
app.get('/image', () => File('test/test_data/bird.jpg'),
use: download(filename: 'bird.jpg'));
server = await runTestServer(app);
server = await runTestServer(app.call);
var r = await server.fetch('get', '/image');
expect(r.headers['content-disposition']?.first,
@ -205,7 +205,7 @@ void main() {
app.get('/data', () => wrapBody('a') >> 'b');
server = await runTestServer(app);
server = await runTestServer(app.call);
var r = await server.fetch('get', '/data');
expect(r.data, 'a(b)');
@ -219,7 +219,7 @@ void main() {
app.get('/html2', () => '<h1>Headline</h1>', use: typeByExtension('html'));
server = await runTestServer(app);
server = await runTestServer(app.call);
var r1 = await server.fetch('get', '/html1');
expect(r1.headers[HttpHeaders.contentTypeHeader]?.first, 'text/html');
@ -235,7 +235,7 @@ void main() {
app1.get('/a', () => Response.notFound(''));
app2.get('/a', () => 'ok');
server = await runTestServer(cascade([app1, app2]));
server = await runTestServer(cascade([app1.call, app2.call]));
var r = await server.fetch('get', '/a');
expect(r.data, 'ok');
@ -248,7 +248,7 @@ void main() {
return 'Hi ${request.routeParameter('name')}, I like ${request.routeParameter('action')}';
});
server = await runTestServer(app);
server = await runTestServer(app.call);
var r = await server.fetch('get', '/sports/john');
expect(r.data, 'Hi john, I like sports');
@ -258,10 +258,10 @@ void main() {
var app = Router().plus;
var subapp = Router().plus;
app.mount('/prefix/', subapp);
app.mount('/prefix/', subapp.call);
subapp.get('/data', () => 'ok');
server = await runTestServer(app);
server = await runTestServer(app.call);
var r = await server.fetch('get', '/prefix/data');
expect(r.data, 'ok');
@ -273,7 +273,7 @@ void main() {
app.get('/bird1', () => File('test/test_data/bird.jpg').readAsBytesSync());
app.get('/bird2', () => File('test/test_data/bird.jpg').openRead());
server = await runTestServer(app);
server = await runTestServer(app.call);
var r1 = await server.fetch('get', '/bird1');
expect(r1.headers[HttpHeaders.contentTypeHeader]?.first,
@ -298,7 +298,7 @@ void main() {
});
});
server = await runTestServer(app);
server = await runTestServer(app.call);
final channel = WebSocketChannel.connect(
Uri.parse('${server.websocketHost}/ws'),
@ -327,13 +327,13 @@ void main() {
var app = Router().plus;
app.mount('/', restrictedApp);
app.mount('/', restrictedApp.call);
app.get('/public', () {
return 'public data';
});
server = await runTestServer(app);
server = await runTestServer(app.call);
var r = await server.fetch('get', '/public');
expect(r.data, 'public data');