serveme

所属分类:Websocket编程
开发工具:Dart
文件大小:0KB
下载次数:0
上传日期:2023-10-12 07:42:12
上 传 者sh-1993
说明:  模块化服务器框架,支持ConnectMe(WebSockets+PackMe)和MongoDb。,
(Modular server framework with ConnectMe (WebSockets + PackMe) and MongoDb support.,)

文件列表:
.metadata (309, 2023-12-03)
CHANGELOG.md (1428, 2023-12-03)
LICENSE (1096, 2023-12-03)
analysis_options.yaml (10484, 2023-12-03)
config.yaml (1601, 2023-12-03)
example/ (0, 2023-12-03)
example/example.dart (8197, 2023-12-03)
example/example.html (2054, 2023-12-03)
example/example.yaml (1752, 2023-12-03)
example/generated/ (0, 2023-12-03)
example/generated/example-posts.generated.dart (12404, 2023-12-03)
example/generated/example-users.generated.dart (13476, 2023-12-03)
lib/ (0, 2023-12-03)
lib/classes/ (0, 2023-12-03)
lib/classes/config.dart (2489, 2023-12-03)
lib/classes/module.dart (837, 2023-12-03)
lib/core/ (0, 2023-12-03)
lib/core/console.dart (4628, 2023-12-03)
lib/core/events.dart (1912, 2023-12-03)
lib/core/integrity.dart (4993, 2023-12-03)
lib/core/logger.dart (2807, 2023-12-03)
lib/core/mongo.dart (1372, 2023-12-03)
lib/core/scheduler.dart (1402, 2023-12-03)
lib/core/utils.dart (1067, 2023-12-03)
lib/serveme.dart (7921, 2023-12-03)
packme/ (0, 2023-12-03)
packme/compile.dart (2226, 2023-12-03)
packme/example-posts.json (876, 2023-12-03)
packme/example-users.json (973, 2023-12-03)
pubspec.yaml (549, 2023-12-03)
serveme.iml (619, 2023-12-03)
test/ (0, 2023-12-03)
test/config_test.yaml (107, 2023-12-03)
test/generated/ (0, 2023-12-03)
test/generated/test.generated.dart (1381, 2023-12-03)
test/modules/ (0, 2023-12-03)
test/modules/test.dart (236, 2023-12-03)
test/packme/ (0, 2023-12-03)
... ...

## What is ServeMe ServeMe is a simple and powerful modular server framework. It allows to easily create backend services for both mobile and web applications. Here are some of the features provided by ServeMe framework: * modular architecture allows to easily implement separate parts of the server using ServeMe Modular API; * both WebSockets and TCP sockets are supported (TCP sockets support implemented in v1.1.0); * MongoDB support out of the box, automatic database integrity validation for easy server deployment; * events API allows to dispatch and listen to any built-in or custom events in your application; * scheduler API allows to create different tasks and schedule its' execution time and period; * logging, debug and error handling tools; * console API enables to handle custom server console commands (with autocomplete, command line format validation, command info etc.); * using build-in or custom configuration files using Config API; * client connections management, broadcasting messages by criteria, listening to data from clients globally or individually; * it's integrated with [PackMe](https://pub.dev/packages/packme) binary serialization library for data transfer: it is very fast; * possibility to implement complex data transfer protocols using JSON (compiled to [PackMe](https://pub.dev/packages/packme) messages .dart files); * support of different message data types: String, Uint8List or [PackMe](https://pub.dev/packages/packme) messages; * asynchronously query data using [PackMe](https://pub.dev/packages/packme) messages: SomeResponse response = await client.query(SomeRequest()); ## Usage Here the simplest example code of a server application based on ServeMe: ```dart import 'package:serveme/serveme.dart'; Future main() async { final ServeMe server = ServeMe(); await server.run(); } ``` You should provide config.yaml (configuration file by default) in order to start the server. ```yaml port: 8080 debug: true debug_log: debug.log error_log: error.log ``` It's ready to run! Though it does nothing at this point. Now we need to implement at least one Module file where something will actually happen. It's recommended to keep your project file structure clean and put all your module files in a separate "modules" directory. Let's create some module which will listen for String messages from connected clients and echo them back: ```dart class MyModule extends Module { @override Future init() async { await Future.delayed(const Duration(seconds: 1)); // let's imitate some initialization process i.e. loading some data from Db server.log('Module initialized'); // logs message to console and debug.log file } @override void run() { server.listen((String message, ServeMeClient client) async { log('Got a message: $message'); client.send(message); }); } @override Future dispose() async { await Future.delayed(const Duration(seconds: 1)); // doing all necessary cleanup before server shutdown server.log('Module disposed'); } } ``` Now we have a module but we also need to enable it in our configuration file: ```yaml modules: - mymodule ``` Let's update our main function: ```dart Future main() async { final ServeMe server = ServeMe( modules: >{ 'mymodule': MyModule(), }, ); await server.run(); } ``` And it's ready! You can now connect to the server using your browser and test it out: ```javascript let ws = new WebSocket('ws:// 127.0.0.1:8080'); ws.onmessage = console.log; ws.send('Something'); ``` It's possible to access one module from another via [] operator applied to server object: ```dart class AnotherModule extends Module { MyModule get myModule => server['mymodule']! as MyModule; @override Future init() async { log('Here is our main module: $myModule'); } // ... } ``` And don't forget to enable your newly implemented modules in configuration file. ## WebSockets and TCP sockets ServeMe is using WebSockets by default. However it can handle pure TCP sockets as well: ```dart Future main() async { final ServeMe server = ServeMe( type: ServeMeType.tcp, modules: >{ 'mymodule': MyModule(), }, ); await server.run(); } ``` Keep in mind that String messages you will try to send via TCP sockets will be converted to Uint8List. So in order to receive String via TCP socket you need to use listen() instead of listen(). ## Configuration files By default ServeMe uses config.yaml file and ServeMeConfig class for instantiating config object accessible from any module. However it is possible to implement and use custom configuration class. ```dart class MyConfig extends Config { MyConfig(String filename) : super(filename) { optionalNumber = cast(map['optional'], fallback: null); greetingMessage = cast(map['greeting'], errorMessage: 'Failed to load config: greeting message is not set' ); } late final int? optionalNumber; late final String greetingMessage; } ``` Method cast<T>() allows to easily cast dynamic variable into typed one with specified fallback value or exception error message. Now let's update our configuration file and see hot to use custom configuration class instead of default one. ```yaml port: 8080 debug: true debug_log: debug.log error_log: error.log optional: 42 greeting: Welcome, friend! modules: - mymodule ``` ```dart Future main() async { final ServeMe server = ServeMe( configFile: 'config.yaml', configFactory: (String filename) => MyConfig(filename), modules: >{ 'mymodule': MyModule(), }, ); await server.run(); } ``` Here's how to access custom config from the module: ```dart class MyModule extends Module { // ... @override MyConfig get config => super.config as MyConfig; void printConfig() { log('optionalNumber: ${config.optionalNumber}, greetingMessage: ${config.greetingMessage}'); } // ... } ``` ## Establishing client connection to a remote server ServeMe instance allows you to create client connection to a remote WebSocket or TCP server. ```dart @override Future init() async { // Establish WebSocket connection to localhost final ServeMeClient wsConnectionClient = await server.connect( 'ws://127.0.0.1:8080', onConnect: () => log('WebSocket connection established'), onDisconnect: () => log('Disconnected from WebSocket server'), ); // Establish TCP socket connection to localhost final ServeMeClient tcpConnectionClient = await server.connect( InternetAddress('127.0.0.1', type: InternetAddressType.IPv4), port: 8177, onConnect: () => log('TCP connection established'), onDisconnect: () => log('Disconnected from TCP server'), ); } ``` Since server.connect() method returns an instance of ServeMeClient, all features such as sending/receiving PackMe messages and using asynchronous queries are available. ## Generic client class type You probably already noticed that both classes ServeMe and Module have generic client class (<ServeMeClient> by default). It's used in some server properties and methods and it is possible to implement custom client class. Here's an example: ```dart import 'dart:io'; class MyClient extends ServeMeClient { MyClient(ServeMeSocket socket) : super(socket) { authToken = socket.httpRequest!.headers.value('x-auth-token'); } late final String? authToken; } ``` We've added some custom property authToken and in order to use this class instead of default it's necessary to set clientFactory property in ServeMe constructor: ```dart Future main() async { final ServeMe server = ServeMe( clientFactory: (_, __) => MyClient(_, __), modules: >{ 'mymodule': MyModule(), }, ); await server.run(); } ``` Keep in mind that in this case all modules should be declared with the same generic class type. ```dart class MyModule extends Module { // ... void echoAuthenticatedClients() { server.listen((String message, MyClient client) async { if (client.authToken != 'some-valid-token') return; clent.send(message); }); } // ... } ``` ## Modules Every module has three mandatory methods: init(), run() and dispose(). ```dart Future init(); ``` Asynchronous method init() is invoked on server start and usually used to preload all necessary data for module to be ready to run. ```dart void run(); ``` Method run() is invoked after all modules have been successfully initialized. It's where modules start processing things and do its' job. ```dart Future dispose(); ``` Asynchronous method dispose() is used on server shutdown to finish modules operation properly (when it's necessary). ## Logs and errors Every ServeMe module has access to three methods: log(), debug() and error(). ```dart Future log(String message, [String color = _green]); ``` Method log() writes message to console and saves it to debug.log file (specified in configuration file). ```dart Future debug(String message, [String color = _reset]); ``` If debug is enabled in config then debug() writes message to console and saves it to log file. ```dart Future error(String message, [StackTrace? stack]); ``` Method error() logs error to console and writes it to error.log file (specified in configuration file). ## Console commands By default there is a single command you can use in server console: stop - which shuts down the server. However it is possible to implement any other commands using console object accessible from modules: ```dart @override void run() { console.on('echo', (String line, List args) async => log(line), aliases: ['say'], // optional similar: ['repeat', 'tell', 'speak'], // optional usage: 'echo \nEchoes specified string (max 20 characters length)', // optional validator: RegExp(r'^.{1,20}$'), // optional ); } ``` This code will add echo command which allows to echo specified string no longer that 20 characters length. * String line - command arguments string (without command itself); * List<String> args - list of arguments * aliases - use it if you need to assign multiple commands to the same command handler; * similar - list of commands which won't be recognized as valid but a suggestion of original command will be displayed; * usage - command format hint and/or short description which will be displayed if command format is invalid or command is used with --help key (or -h, -?, /?); * validator - regular expression for arguments string validation. ## Events ServeMe supports some built-in events: * ReadyEvent - dispatched once all modules are initialized, right before invoking modules run() methods; * TickEvent - dispatched every second; * StopEvent - dispatched once server shutdown initiated (either by stop command or POSIX signal); * LogEvent - dispatched on every message logging event; * ErrorEvent - dispatched on errors; * ConnectEvent - dispatched when incoming client connection established; * DisconnectEvent - dispatched when client connection is closed. You can subscribe to events using events object accessible from modules: ```dart @override void run() { events.listen((TickEvent event) async { log('${event.counter} seconds passed since server start'); }); } ``` It is also possible to implement own events and dispatch them when necessary. It's often very useful for interaction between different modules. ```dart class AnnouncementEvent extends Event { AnnouncementEvent(this.message) : super(); final String message; } ``` Now you can dispatch AnnouncementEvent in one module and listen for it in another module. ```dart // implemented in some module void makeAnnouncement() { events.dispatch(AnnouncementEvent('Cheese for everyone!')); } // implemented in some another module @override void run() { events.listen((AnnouncementEvent event) async { server.broadcast(event.message); // sends data to all connected clients }); } ``` ## Scheduler ServeMe allows to create and schedule tasks. There's a scheduler object accessible from modules: ```dart class SomeModule extends Module { late final Task task; @override Future init() async { task = Task( DateTime.now()..add(const Duration(minutes: 1)), (DateTime time) async { log('Current time is $time'); }, period: const Duration(seconds: 10), // optional skip: false, // optional ); } @override void run() { scheduler.schedule(task); } @override Future dispose() async { scheduler.cancel(task); } } ``` This module creates periodic Task which will be started in 1 minute. Note that task is cancelled on dispose. * skip - if true then periodic task will be skipped till next time if previously returned Future is not resolved yet. Default value: false. ## Connections and data transfer You can access all of your current client connections via clients object implemented in Module class: ```dart @override void run() { for (final ServeMeClient in server.clients) { // do something, don't use it for broadcasting however, use server.broadcast() instead } } ``` It's always recommended to use [PackMe](https://pub.dev/packages/packme) messages for data exchange since it gives some important benefits such as clear communication protocol described in JSON, asynchronous queries support out of the box and small data packets size. Here's a simple protocol.json file (located in packme directory) for some hypothetical client-server application (see PackMe JSON manifest format documentation [here](https://pub.dev/packages/packme)): ```json { "get_user": [ { "id": "string" }, { "first_name": "string", "last_name": "string", "age": "uint8" } ] } ``` Generate dart files: ```bash # Usage: dart run packme dart run packme packme generated ``` Before listening for any PackMe message from clients it is necessary to register message factory (which is created automatically and declared in generated dart file). ```dart @override void run() { // necessary for ServeMe to know how to parse incoming binary data server.register(protocolMessageFactory); server.listen((GetUserRequest request, ServeMeClient client) { // GetUserRequest.$response method returns GetUserResponse associated with current request. final GetUserResponse response = request.$response( firstName: 'Alyx', lastName: 'Vance', age: 19, ); }); } ``` This code listens for GetUserRequest message from clients and replies with GetUserResponse message. However sometimes it is useful to be able to add message listeners for some specific clients only, for example, logged in users only: ```dart bool _isAuthorizedToDoSomething(String codePhrase) { return codePhrase == "I am Iron Man."; } @override void run() { // Listen for some authorization request from connected clients. server.listen((AuthorizeRequest request, ServeMeClient client) { if (_isAuthorizedToDoSomething(request.codePhrase)) { client.listen(_handleGodModeRequest); client.listen(_handleAllWeaponsRequest); client.listen(_handleKillEveryoneRequest); client.send(request.$response( allowed: true, reason: 'Welcome on board!', )); } else { client.send(request.$response( allowed: false, reason: 'You are not Iron Man.', )); // Close client connection. client.close(); } }); } ``` You could see in previous examples that request.$response() method is used instead of just instantiating corresponding ResponseMessage. It's made for assigning the response to this particular request which allows us to use .query() on client side (or server side, it doesn't matter once implementation is valid on the opposite side): ```dart // let's for example obtain some data from clients once server is going offline @override Future dispose() async { int ok = 0, notOk = 0; for (final ServeMeClient client in server.clients) { // in real life situation you probably want to use asynchronous calls in parallel final AreYouOkResponse response = await client.query(AreYouOkRequest()); if (response.ok) ok++; else notOk++; } server.log('$ok clients are OK and $notOk clients are not. Now ready for shutting down.'); } ``` Method broadcast() allows to send a message to all connected clients or to some clients filtered by some criteria: ```dart // say good bye to all clients on server shut down @override Future dispose() async { // Send a String message to all connected clients. server.broadcast( 'See you later!', (ServeMeClient client) => true // optional criteria filter ); } ``` ## MongoDB ServeMe uses [mongo_dart](https://pub.dev/packages/mongo_dart) package for MongoDB support. In order to use MongoDB in modules it's necessary to specify mongo config section of your configuration file: ```yaml mongo: host: 127.0.0.1 database: test_db ``` Or in case of using replica set: ```yaml mongo: host: - 192.160.1.101:27017 - 192.160.1.102:27017 - 192.160.1.103:27017 database: test_db replica: myReplicaSet ``` There's an object db accessible from modules. This object is actually Future<Db>. Future is used to ensure that connection to database is alive and Db object is valid. ```dart import 'package:mongo_dart/mongo_dart.dart'; ``` ```dart late final List> items; @override Future init() async { // load all items within price range specified in config file items = await (await db).collection('users') .find(where.gte('price', config.minPrice).lte('price', config.maxPrice)) .toList(); } ``` ## Database integrity validation Sometimes it's necessary to ensure that server database contains all necessary collections, indexes and data for server to work properly. For this purpose ServeMe provides special integrity descriptor. It allows you to automatically create missing indexes and create mandatory documents in database on server start. Aside from validation it also allows to deploy servers with ease without extra steps for setting up database. ```da ... ...

近期下载者

相关文件


收藏者