Skip to content

Commit

Permalink
Update ws echo server (#6)
Browse files Browse the repository at this point in the history
* Update server

* HijackException
  • Loading branch information
PlugFox authored Jul 17, 2023
1 parent 392ee1a commit 44d1f0a
Show file tree
Hide file tree
Showing 19 changed files with 825 additions and 519 deletions.
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"runTestsOnDevice": false,
"templateFor": "test",
"toolArgs": [],
"args": ["--port=8080", "--isolates=2"]
"args": ["--port=8080", "--isolates=2", "--verbose=4"]
}
]
}
52 changes: 31 additions & 21 deletions server/bin/server.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,40 @@
import 'dart:async';
import 'dart:io' as io;

import 'package:ws_server/src/shared_server.dart';
import 'package:ws_server/src/util/options.dart';
import 'package:ws_server/src/util/shutdown_handler.dart';
import 'package:shelf/shelf.dart' as shelf;
import 'package:shelf/shelf_io.dart' as shelf_io;
import 'package:ws_server/src/middleware/handle_errors.dart';
import 'package:ws_server/src/middleware/injector.dart';
import 'package:ws_server/src/middleware/log_pipeline.dart';
import 'package:ws_server/src/router/rest_router.dart';
import 'package:ws_server/src/router/websocket.dart';
import 'package:ws_server/src/util/cors.dart';
import 'package:ws_server/src/util/run_server.dart';

/// Starts the server.
/// dart run server/bin/server.dart
void main(List<String> arguments) => Future<void>(() async {
// Allow shutdown via Ctrl+C
$shutdownHandler().whenComplete(() => io.exit(0)).ignore();
print('Press Ctrl+C to exit.');
void main(List<String> arguments) => runServer<void>(
config: null,
serve: _serve,
arguments: arguments,
);

// Extract startup options
final options = $extractOptions(arguments);

// Isolate pool
final connection = (
address: io.InternetAddress.anyIPv4,
port: options.port,
);
for (var i = 1; i <= options.isolates; i++) {
SharedServer(connection: connection, label: 'Server-$i')();
}
print(
'Serving ${options.isolates} handlers at '
'http://${connection.address.host}:${connection.port}',
/// The entry point for the isolate.
void _serve(io.InternetAddress address, int port, [config]) =>
Future<void>(() async {
//fine('Starting isolate ${Isolate.current.debugName ?? 'unknown'}');
final pipeline = shelf.Pipeline()
.addMiddleware(handleErrors())
.addMiddleware(injector(<String, Object>{}))
.addMiddleware(webSocket(path: '/connect'))
.addMiddleware(logPipeline())
.addMiddleware(cors())
.addHandler($restRouter);
await shelf_io.serve(
pipeline,
address,
port,
poweredByHeader: 'WS Server',
shared: true,
);
});
6 changes: 3 additions & 3 deletions server/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# docker-compose down
# docker-compose logs -f

version: '3.9'
version: "3.9"

services:
server:
Expand All @@ -16,7 +16,7 @@ services:

# https://github.com/Shopify/toxiproxy
toxiproxy:
image: 'ghcr.io/shopify/toxiproxy:2.5.0'
image: "ghcr.io/shopify/toxiproxy:2.5.0"
container_name: toxiproxy
ports:
- target: 8474
Expand All @@ -33,4 +33,4 @@ services:
- server
#environment:
# - LOG_LEVEL=debug
command: ['-config', '/config/toxiproxy.json','-host', '0.0.0.0']
command: ["-config", "/config/toxiproxy.json", "-host", "0.0.0.0"]
175 changes: 0 additions & 175 deletions server/lib/src/middleware/errors.dart

This file was deleted.

61 changes: 61 additions & 0 deletions server/lib/src/middleware/handle_errors.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import 'dart:convert';
import 'dart:io' as io;

import 'package:shelf/shelf.dart';
import 'package:stack_trace/stack_trace.dart' as st;
import 'package:ws_server/src/util/responses.dart';

/// Response encoder
final Converter<Map<String, Object?>, String> _responseEncoder =
const JsonEncoder().cast<Map<String, Object?>, String>();

/// Middleware that catches all errors and sends a JSON response with the error
/// message. If the error is not an instance of [HttpException], it will be
/// wrapped into one with the status code 500.
Middleware handleErrors({bool showStackTrace = false}) => (Handler handler) =>
(Request request) => Future.sync(() => handler(request))
.then<Response>((Response response) => response)
.catchError(
(Object error, StackTrace? stackTrace) {
final result = error is HttpException
? error
: HttpException(
statusCode: io.HttpStatus.internalServerError,
code: 'internal',
message: 'Internal Server Error',
data: <String, Object?>{
'path': request.url.path,
'query': request.url.queryParameters,
'method': request.method,
'headers': request.headers,
'error': showStackTrace
? error.toString()
: _errorRepresentation(error),
if (showStackTrace && stackTrace != null)
'stack_trace': st.Trace.format(stackTrace),
},
);
return Response(
result.statusCode,
body: _responseEncoder.convert(result.toJson()),
headers: <String, String>{
'Content-Type': io.ContentType.json.value,
},
);
},
test: (Object error) => error is HijackException ? false : true,
);

String _errorRepresentation(Object? error) => switch (error) {
FormatException _ => 'Format exception',
HttpException _ => 'HTTP exception',
UnimplementedError _ => 'Unimplemented error',
UnsupportedError _ => 'Unsupported error',
RangeError _ => 'Range error',
StateError _ => 'State error',
ArgumentError _ => 'Argument error',
TypeError _ => 'Type error',
OutOfMemoryError _ => 'Out of memory error',
StackOverflowError _ => 'Stack overflow error',
_ => 'Unknown error',
};
6 changes: 6 additions & 0 deletions server/lib/src/middleware/log_pipeline.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import 'package:l/l.dart';
import 'package:shelf/shelf.dart' as shelf;

shelf.Middleware logPipeline() => shelf.logRequests(
logger: (msg, isError) => isError ? l.w(msg) : l.i(msg),
);
10 changes: 3 additions & 7 deletions server/lib/src/router/health_check.dart
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import 'dart:io' as io;
import 'dart:async';

import 'package:shelf/shelf.dart' show Request, Response;
import 'package:ws_server/src/util/responses.dart';

Response $healthCheck(Request request) => Response.ok(
'{"data": {"status": "ok"}}',
headers: <String, String>{
'Content-Type': io.ContentType.json.value,
},
);
FutureOr<Response> $healthCheck(Request request) => Responses.ok(null);
19 changes: 19 additions & 0 deletions server/lib/src/router/long_polling.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import 'dart:async';

import 'package:shelf/shelf.dart' as shelf;
import 'package:ws_server/src/util/responses.dart';

FutureOr<shelf.Response> $longPolling(shelf.Request request) async {
final requestTime = DateTime.now().toUtc();
final ms = switch (request.url.queryParameters['duration']) {
String value => int.tryParse(value) ?? 12000,
_ => 12000,
};
await Future<void>.delayed(Duration(milliseconds: ms));
final responseTime = DateTime.now().toUtc();
return Responses.ok(<String, Object?>{
'request': requestTime.toIso8601String(),
'response': responseTime.toIso8601String(),
'duration': responseTime.difference(requestTime).inMilliseconds.abs(),
});
}
Loading

0 comments on commit 44d1f0a

Please sign in to comment.