Dart is practically not inferior to the NodeJS on the server in terms of performance, but its ecosystem is rather small by comparison. But still, sometimes you come across something interesting.
Today we will talk about a new HTTP server on dart - Dia. This is a small simple web server inspired by KoaJS. The main idea of the project: the context of the http request, which passes the queue of the middleware.
To install it, add this to your pubspec.yaml:
dependencies:
dia: ^0.0.7
It is generally very simple to use:
import 'package:dia/dia.dart';
main() {
/// Create instance of Dia
final app = App();
/// Add middleware
app.use((ctx, next) async {
/// write response on all query
ctx.body = 'Hello world!';
});
/// Listen localhost:8080
app
.listen('localhost', 8080)
.then((info) => print('Server started on http://localhost:8080'));
}
The main brick of the application is middleware. This is an asynchronous function that takes a Context and a reference to the next middleware as arguments.
The Сontext contains getters and setters for the dart.io.HttpRequest and dart.io.HttpResponse fields. If you are familiar with koa, then you will understand the basic idea of the context. Most importantly, you can use your own extended context with additional fields, such as the current user.
class MyContext extends Context{
User? user;
MyContext(HttpRequest request): super(request);
}
main() {
/// Create instance of Dia
final app = App<MyContext>();
app.use((ctx, next) async {
ctx.user = new User('test');
await next();
});
app.use((ctx, next) async {
if(ctx.user==null){
ctx.trowError(401);
}
});
/// Add middleware
app.use((ctx, next) async {
/// write response on all query
ctx.body = 'Hello world!';
});
/// Listen localhost:8080
app
.listen('localhost', 8080)
.then((info) => print('Server started on http://localhost:8080'));
}
The next - a link to the next middleware in the queue. When a middleware does not return the final result, but only changes the context, it often needs to wait for the next middleware to complete. This is necessary, for example, to add logging or error handling.
app.use((ctx, next) async {
final start = DateTime.now();
await next();
final diff = DateTime.now().difference(start).inMicroseconds;
print('microseconds=$diff')
});
There are ready-made middleware in the form of separate packages. This allows, without increasing the code base, to use only those of them that are necessary in the project. For example:
- dia_cors - middleware for add CORS headers
- dia_static - middleware for serving static files
- dia_router - middleware like as koa_router, for work with url path
- dia_body - middleware for parse request body.
Let us dwell on the last two of them in more detail.
The first one is dia_router - allows you to set middleware for specific URLs and specific http methods.
It requires a special Context with the Routing mixin. They contains additional parameters that can be passed to url
class ContextWithRouting extends Context with Routing {
ContextWithRouting(HttpRequest request) : super(request);
}
void main() {
/// create Dia app with Routing mixin on Context
final app = App<ContextWithRouting>();
/// create router
final router = Router<ContextWithRouting>('/route');
/// add handler to GET request
router.get('/data/:id', (ctx, next) async {
ctx.body = '${ctx.params}';
});
/// start server
app
.listen('localhost', 8080)
.then((_) => print('Started on http://localhost:8080'));
}
If we open the http://localhost:8080/data/12 in the browser, we will see {id: 12}
The second package that we will consider is dia_body - it allows you to receive the parameters passed with the http request. It also needs an extended context with the ParsedBody mixin.
class ContextWithBody extends Context with ParsedBody {
ContextWithBody(HttpRequest request) : super(request);
}
void main() {
final app = App<ContextWithBody>();
app.use(body());
app.use((ctx, next) async {
ctx.body = '''
query=${ctx.query}
parsed=${ctx.parsed}
files=${ctx.files}
''';
});
/// Start server listen on localhost:8080
app
.listen('localhost', 8080)
.then((info) => print('Server started on http://localhost:8080'));
}
-
ctx.query
- contain url get parameters.?param=value
-{param: value}
-
ctx.parsed
- contains json body or x-www-form-urlencoded or form-data params of http request -
ctx.files
- contains uploaded files. By default, files are loaded into the temporary system directory, but the path can be changed.
We can use the Context with all extensions and our fields at the same time:
class CustomContext extends Context with Routing, ParsedBody {
User? user;
ContextWithBody(HttpRequest request) : super(request);
}
We can run the http server version as well:
final serverContext = SecurityContext();
serverContext
.useCertificateChainBytes(await File(certificateChain).readAsBytes());
serverContext.usePrivateKey(serverKey, password: 'password');
final server = await app.listen(
'localhost', 8444,
securityContext: serverContext);
All sources of the given packages are on GitHub and published under the MIT license. I would be glad to receive any feedback, issue and pull requests!
Top comments (0)