Skip to content

Commit e2a7b05

Browse files
author
Mad Sheogorath
committed
TCP sockets support implemented (breaking changes).
ServeMeClient constructor now takes single ServeMeSocket argument. Future<ServeMeClient> ServeMe.connect() method added allowing to create WebSocket or TCP client connections (with the same functionality as server client connections).
1 parent 2f60bd8 commit e2a7b05

File tree

6 files changed

+138
-13
lines changed

6 files changed

+138
-13
lines changed

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## v1.1.0
2+
* TCP sockets support implemented (breaking changes).
3+
* ServeMeClient constructor now takes single ServeMeSocket argument.
4+
* Future<ServeMeClient> ServeMe.connect() method added allowing to create WebSocket or TCP client connections (with the same functionality as server client connections).
5+
16
## v1.0.2
27
* Bugfix: ESC key press caused ServeMe to crash on Linux
38

README.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## What is ServeMe
22
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:
33
* modular architecture allows to easily implement separate parts of the server using ServeMe Modular API;
4+
* both WebSockets and TCP sockets are supported (TCP sockets support implemented in v1.1.0);
45
* MongoDB support out of the box, automatic database integrity validation for easy server deployment;
56
* events API allows to dispatch and listen to any built-in or custom events in your application;
67
* scheduler API allows to create different tasks and schedule its' execution time and period;
@@ -92,6 +93,21 @@ class AnotherModule extends Module<ServeMeClient> {
9293
```
9394
And don't forget to enable your newly implemented modules in configuration file.
9495

96+
## WebSockets and TCP sockets
97+
ServeMe is using WebSockets by default. However it can handle pure TCP sockets as well:
98+
```dart
99+
Future<void> main() async {
100+
final ServeMe<ServeMeClient> server = ServeMe<ServeMeClient>(
101+
type: ServeMeType.tcp,
102+
modules: <String, Module<ServeMeClient>>{
103+
'mymodule': MyModule(),
104+
},
105+
);
106+
await server.run();
107+
}
108+
```
109+
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<Uint8List>() instead of listen<String>().
110+
95111
## Configuration files
96112
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.
97113
```dart
@@ -154,8 +170,8 @@ You probably already noticed that both classes ServeMe and Module have generic c
154170
import 'dart:io';
155171
156172
class MyClient extends ServeMeClient {
157-
MyClient(WebSocket socket, HttpHeaders headers) : super(socket, headers) {
158-
authToken = headers.value('x-auth-token');
173+
MyClient(ServeMeSocket socket) : super(socket) {
174+
authToken = socket.httpRequest!.headers.value('x-auth-token');
159175
}
160176
161177
late final String? authToken;

example/example.dart

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import 'dart:async';
2-
import 'dart:io';
32
import 'dart:math' show Random;
43
import 'package:serveme/serveme.dart';
54

@@ -63,8 +62,8 @@ class MehConfig extends Config {
6362
/// functionality such as user authorization etc.
6463
6564
class MehClient extends ServeMeClient {
66-
MehClient(WebSocket socket, HttpHeaders headers) : super(socket, headers) {
67-
userIsLocal = headers.host == '127.0.0.1';
65+
MehClient(ServeMeSocket socket) : super(socket) {
66+
userIsLocal = socket.httpRequest!.headers.host == '127.0.0.1';
6867
}
6968

7069
late final bool userIsLocal;
@@ -211,7 +210,7 @@ Future<void> main() async {
211210
/// Tell server to use our own Config class.
212211
configFactory: (_) => MehConfig(_),
213212
/// Tell server to use our own Client class.
214-
clientFactory: (_, __) => MehClient(_, __),
213+
clientFactory: (_) => MehClient(_),
215214
/// Pass our modules to server (don't forget to enable them in config).
216215
modules: <String, Module<MehClient>>{
217216
'meh': MehModule()

lib/serveme.dart

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,20 @@ part 'core/scheduler.dart';
1919
part 'core/utils.dart';
2020

2121
typedef ServeMeClient = ConnectMeClient;
22+
typedef ServeMeSocket = ConnectMeSocket;
23+
typedef ServeMeType = ConnectMeType;
2224

2325
final bool _unixSocketsAvailable = Platform.isLinux || Platform.isAndroid || Platform.isMacOS;
2426

2527
class ServeMe<C extends ServeMeClient> {
2628
ServeMe({
2729
String configFile = 'config.yaml',
30+
ServeMeType type = ServeMeType.ws,
2831
Config Function(String filename)? configFactory,
29-
C Function(WebSocket, HttpHeaders)? clientFactory,
32+
C Function(ServeMeSocket)? clientFactory,
3033
Map<String, Module<C>>? modules,
3134
Map<String, CollectionDescriptor>? dbIntegrityDescriptor,
32-
}) : _clientFactory = clientFactory, _dbIntegrityDescriptor = dbIntegrityDescriptor {
35+
}) : _type = type, _clientFactory = clientFactory, _dbIntegrityDescriptor = dbIntegrityDescriptor {
3336
config = Config._instantiate(configFile, factory: configFactory);
3437
console = Console(this);
3538
_logger = Logger(this);
@@ -47,6 +50,7 @@ class ServeMe<C extends ServeMeClient> {
4750
: InternetAddress(config._host ?? '127.0.0.1', type: InternetAddressType.IPv4);
4851
_cmServer = ConnectMe.server(address,
4952
port: config._port ?? 0,
53+
type: _type,
5054
clientFactory: _clientFactory,
5155
onLog: log,
5256
onError: error,
@@ -63,9 +67,10 @@ class ServeMe<C extends ServeMeClient> {
6367
late final Logger _logger;
6468
late final Scheduler _scheduler;
6569
late final ConnectMeServer<C> _cmServer;
70+
final ServeMeType _type;
6671
MongoDbConnection? _mongo;
6772
final Map<String, Module<C>> _modules = <String, Module<C>>{};
68-
final C Function(WebSocket, HttpHeaders)? _clientFactory;
73+
final C Function(ServeMeSocket)? _clientFactory;
6974
final Map<String, CollectionDescriptor>? _dbIntegrityDescriptor;
7075
ProcessSignal? _signalReceived;
7176
Timer? _signalTimer;
@@ -148,6 +153,25 @@ class ServeMe<C extends ServeMeClient> {
148153
_cmServer.cancel<T>(handler);
149154
}
150155

156+
Future<ServeMeClient> connect(dynamic address, {
157+
Map<String, dynamic> headers = const <String, dynamic>{},
158+
int port = 0,
159+
bool autoReconnect = true,
160+
int queryTimeout = 30,
161+
Function()? onConnect,
162+
Function()? onDisconnect,
163+
}) {
164+
return ConnectMe.connect(address,
165+
port: port,
166+
autoReconnect: autoReconnect,
167+
queryTimeout: queryTimeout,
168+
onLog: log,
169+
onError: error,
170+
onConnect: onConnect,
171+
onDisconnect: onDisconnect,
172+
);
173+
}
174+
151175
Future<bool> run() async {
152176
if (_running) return false;
153177
log('Server start initiated');

pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name: serveme
22
description: Backend server framework designed for a quick development of modular WebSocket based server applications with MongoDB integration.
3-
version: 1.0.2
3+
version: 1.1.0
44
homepage: https://github.com/sourcecaster/serveme
55
repository: https://github.com/sourcecaster/serveme
66
issue_tracker: https://github.com/sourcecaster/serveme/issues
@@ -10,7 +10,7 @@ environment:
1010
dependencies:
1111
mongo_dart: ^0.7.0+2
1212
yaml: ^3.1.0
13-
connectme: ^1.1.1
13+
connectme: ^2.0.1
1414
packme: ^1.1.5
1515
dev_dependencies:
1616
test: ^1.17.11

test/serveme_test.dart

Lines changed: 83 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import 'dart:async';
2-
import 'dart:io';
2+
import 'dart:convert';
33
import 'dart:typed_data';
44
import 'package:connectme/connectme.dart';
55
import 'package:serveme/serveme.dart';
66
import 'package:test/test.dart';
77
import 'generated/test.generated.dart';
88
import 'modules/test.dart';
99

10+
const Utf8Codec _utf8 = Utf8Codec();
11+
1012
void main() {
1113
late ServeMe<ServeMeClient> server;
1214
late ConnectMeClient client;
@@ -38,7 +40,7 @@ void main() {
3840
});
3941
});
4042

41-
group('ServeMe data exchange tests', () {
43+
group('ServeMe WebSocket data exchange tests', () {
4244
setUp(() async {
4345
timer = Timer(const Duration(seconds: 2), () => fail('Operation timed out'));
4446
server = ServeMe<ServeMeClient>(
@@ -114,6 +116,85 @@ void main() {
114116
});
115117
});
116118

119+
group('ServeMe TCP data exchange tests', () {
120+
setUp(() async {
121+
timer = Timer(const Duration(seconds: 2), () => fail('Operation timed out'));
122+
server = ServeMe<ServeMeClient>(
123+
type: ServeMeType.tcp,
124+
configFile: 'test/config_test.yaml',
125+
modules: <String, Module<ServeMeClient>>{
126+
'test': module = TestModule(),
127+
},
128+
);
129+
await server.run();
130+
client = await ConnectMe.connect('127.0.0.1', port: 31337);
131+
});
132+
133+
tearDown(() async {
134+
await client.close();
135+
await server.stop();
136+
timer.cancel();
137+
});
138+
139+
test('Client sends String to server', () async {
140+
final Completer<Uint8List> completer = Completer<Uint8List>();
141+
server.listen<Uint8List>((Uint8List message, ConnectMeClient client) async {
142+
completer.complete(message);
143+
});
144+
client.send('Test message from client');
145+
final List<int> expected = _utf8.encode('Test message from client');
146+
expect(await completer.future, expected);
147+
});
148+
149+
test('Server broadcasts String to clients', () async {
150+
final Completer<Uint8List> completer = Completer<Uint8List>();
151+
client.listen<Uint8List>((Uint8List message) {
152+
completer.complete(message);
153+
});
154+
server.broadcast('Test message from server');
155+
final List<int> expected = _utf8.encode('Test message from server');
156+
expect(await completer.future, expected);
157+
});
158+
159+
test('Client sends Uint8List to server', () async {
160+
final Completer<Uint8List> completer = Completer<Uint8List>();
161+
server.listen<Uint8List>((Uint8List message, ConnectMeClient client) async {
162+
completer.complete(message);
163+
});
164+
client.send(Uint8List.fromList(<int>[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]));
165+
expect(await completer.future, Uint8List.fromList(<int>[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]));
166+
});
167+
168+
test('Server broadcasts Uint8List to clients', () async {
169+
final Completer<Uint8List> completer = Completer<Uint8List>();
170+
client.listen<Uint8List>((Uint8List message) {
171+
completer.complete(message);
172+
});
173+
server.broadcast(Uint8List.fromList(<int>[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]));
174+
expect(await completer.future, Uint8List.fromList(<int>[3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]));
175+
});
176+
177+
test('Client sends TestResponse query to server', () async {
178+
server.register(testMessageFactory);
179+
server.listen<TestRequest>((TestRequest request, ConnectMeClient client) async {
180+
client.send(request.$response(responseParam: request.requestParam));
181+
});
182+
client.register(testMessageFactory);
183+
final TestResponse response = await client.query<TestResponse>(TestRequest(requestParam: 3.1415926535));
184+
expect(response.responseParam, 3.1415926535);
185+
});
186+
187+
test('Server sends TestResponse query to client', () async {
188+
client.register(testMessageFactory);
189+
client.listen<TestRequest>((TestRequest request) {
190+
client.send(request.$response(responseParam: request.requestParam));
191+
});
192+
server.register(testMessageFactory);
193+
final TestResponse response = await server.clients.first.query<TestResponse>(TestRequest(requestParam: 3.1415926535));
194+
expect(response.responseParam, 3.1415926535);
195+
});
196+
});
197+
117198
group('ServeMe API tests', () {
118199
setUp(() async {
119200
timer = Timer(const Duration(seconds: 2), () => fail('Operation timed out'));

0 commit comments

Comments
 (0)