Open insinfo opened 10 months ago
maybe something like that
import 'dart:async';
import 'dart:convert';
import 'dart:isolate';
import 'package:args/args.dart';
import 'package:prometheus_client/format.dart' as format;
import 'package:prometheus_client/prometheus_client.dart';
import 'package:prometheus_client/runtime_metrics.dart' as runtime_metrics;
import 'package:shelf/shelf.dart';
import 'package:shelf/shelf_io.dart' as io;
import 'package:shelf_router/shelf_router.dart';
import 'package:shelf_router/src/router_entry.dart';
import 'package:stack_trace/stack_trace.dart';
import 'package:dart_segment_fault/dependencies/shelf_cors_headers_base/shelf_cors_headers_base.dart';
import 'package:dart_segment_fault/dependencies/stream_isolate/stream_isolate.dart';
// to test
// xargs -I % -P 8 curl "http:/192.168.66.123:3161/api/v1/protocolo/processos/public/site/2023/10" < <(printf '%s\n' {1..400})
//./wrk -t12 -c400 -d30s http://172.30.82.2:3350
const defaultHeaders = {'Content-Type': 'application/json;charset=utf-8'};
final streamIsolates = <Map<int, BidirectionalStreamIsolate>>[];
void main(List<String> args) async {
final parser = ArgParser()
..addOption('address', abbr: 'a', defaultsTo: '0.0.0.0')
..addOption('port', abbr: 'p', defaultsTo: '3161')
..addOption('isolates', abbr: 'j', defaultsTo: '3');
final argsParsed = parser.parse(args);
final arguments = [argsParsed['address'], int.parse(argsParsed['port'])];
final numberOfIsolates = int.parse(argsParsed['isolates']);
for (var i = 0; i < numberOfIsolates - 1; i++) {
final streamIsolate = await StreamIsolate.spawnBidirectional(isolateMain,
debugName: i.toString(), argument: [i, ...arguments]);
streamIsolates.add({i: streamIsolate});
streamIsolate.stream.listen((event) => receiveAndPass(event, i));
}
}
/// receive msg from isolate and send to all isolates
void receiveAndPass(event, int idx) {
streamIsolates.forEach((item) {
item.values.first.send(event);
});
}
Stream isolateMain(Stream inc, dynamic args) {
final arguments = args as List;
int id = arguments[0];
String address = arguments[1];
int port = arguments[2];
final streamController = StreamController.broadcast();
final reg = CollectorRegistry(); //CollectorRegistry.defaultRegistry;
// Register default runtime metrics
runtime_metrics.register(reg);
// Register http requests total
final http_requests_total = Counter(
name: 'http_requests_total', help: 'Total number of http api requests');
http_requests_total.register(reg);
// listen msg from main
inc.listen((msg) {
http_requests_total.inc();
});
_startServer([id, streamController, reg, address, port]);
return streamController.stream;
}
void _startServer(List args) async {
final streamController = args[1] as StreamController;
final reg = args[2] as CollectorRegistry;
String address = args[3];
int port = args[4];
final app = Router();
routes(app, reg);
final handler =
Pipeline().addMiddleware(corsHeaders()).addMiddleware((innerHandler) {
return (request) async {
// Every time http_request is called, increase the counter by one
final resp = await innerHandler(request);
if (!request.url.path.contains('metrics')) {
//send msg to main
streamController.add('+1');
}
return resp;
};
})
//.addMiddleware(logRequestsCustom())
.addHandler(app);
final server = await io.serve(handler, address, port, shared: true);
server.defaultResponseHeaders.remove('X-Frame-Options', 'SAMEORIGIN');
print('Serving at http://${server.address.host}:${server.port}');
}
void routes(Router app, CollectorRegistry reg) {
app.get('/', (Request request) async {
return Response.ok('shelf');
});
// Register a handler to expose the metrics in the Prometheus text format
app.get('/metrics', (Request request) async {
final buffer = StringBuffer();
final metrics = await reg.collectMetricFamilySamples();
format.write004(buffer, metrics);
return Response.ok(
buffer.toString(),
headers: {'Content-Type': format.contentType},
);
});
app.group('/api/v1', (router) {
for (final sr in simulatedRoutes) {
final method = sr.keys.first.toUpperCase();
final path = sr.values.first;
router.add(method, path, simulatedWork);
}
},middleware: simulatedMiddleware);
}
class RouterContext {
final Router _router;
final String basePath;
final Middleware? middleware;
RouterContext(
this._router, {
required this.basePath,
this.middleware,
});
void add(
String verb, String route, FutureOr<Response> Function(Request) handler) {
if (middleware != null) {
_router.add(verb, basePath + route, middleware!(handler));
} else {
_router.add(verb, basePath + route, handler);
}
}
}
extension RouterExtension on Router {
void group(String basePath, Function(RouterContext) callback,
{Middleware? middleware}) {
final ctx = RouterContext(this, basePath: basePath);
callback(ctx);
}
}
FutureOr<Response> Function(Request) simulatedMiddleware(innerHandler) {
return (request) async {
final resp = await innerHandler(request);
return resp;
};
}
Future<Response> simulatedWork(Request request) async {
try {
final listItems = List.generate(150, (int index) => {'name': 'Jon_$index'});
return Response.ok(
jsonEncode(listItems),
headers: defaultHeaders,
);
} catch (e, s) {
print('simulatedWork@all $e $s');
return Response.badRequest();
}
}
final simulatedRoutes = [
{'get': '/administracao/paises'},
{'get': '/administracao/ufs'},
{'get': '/administracao/municipios'},
{'get': '/administracao/modulos'},
{'get': '/administracao/permissoes/:numCgm/:anoExercicio'},
{'put': '/administracao/permissoes/:numCgm/:anoExercicio'},
{'get': '/administracao/escolaridades'},
{'get': '/administracao/tiposlogradouro'},
{'get': '/administracao/orgaos'},
{'get': '/administracao/unidades'},
{'get': '/administracao/departamentos'},
{'get': '/administracao/setores'},
{'get': '/administracao/gestao'},
{'get': '/administracao/usuarios'},
{'get': '/administracao/usuarios/:numcgm'},
{'post': '/administracao/usuarios'},
{'put': '/administracao/usuarios'},
{'get': '/administracao/cgm'},
{'get': '/administracao/cgm/:cgm'},
{'get': '/administracao/auditorias'},
{'post': '/administracao/auditorias'},
{'get': '/administracao/configuracao'},
{'get': '/administracao/configuracao/by/filtro'},
{'get': '/administracao/funcionalidades'},
{'get': '/administracao/menu/:cgm'},
{'get': '/administracao/organograma/hierarquia'},
{'get': '/administracao/acoes'},
//auth
{'post': '/change/pass'},
{'get': '/auth/check/permissao/:cgm'},
{'post': '/auth/login'},
{'post': '/auth/check'},
{'get': '/auth/check/toke'},
//cgm
{'get': '/cgm/full'},
{'get': '/cgm/full/:cgm'},
{'delete': '/cgm'},
{'post': '/cgm/full'},
{'post': '/cgm/full/interno'},
{'put': '/cgm/full'},
{'get': '/cgm/atributos'},
{'get': '/cgm/atributos/:cgm'},
{'get': '/cgm/categoriashabilitacao'},
{'get': '/cgm/tiposlogradouro'},
//
{'get': '/estatistica/processos/ano'},
{'get': '/estatistica/processos/periodo/setor/primero/tramite'},
{'get': '/estatistica/processos/situacao'},
{'get': '/estatistica/processos/classificacao'},
{'get': '/estatistica/processos/assunto'},
//
{'get': '/norma/normas'},
//
{'get': '/protocolo/processos/favoritos/cgm/:cgm'},
{'post': '/protocolo/processos/favoritos'},
{'post': '/protocolo/processos/favoritos/:codProcesso/:anoExercicio'},
{'put': '/protocolo/processos/favoritos'},
{'delete': '/protocolo/processos/favoritos/:id'},
{'delete': '/protocolo/processos/favoritos/:codProcesso/:anoExercicio'},
{'get': '/protocolo/acoes/favoritas/cgm/:cgm'},
{'post': '/protocolo/acoes/favoritas'},
{'post': '/protocolo/acoes/favoritas/:codAcao'},
{'put': '/protocolo/acoes/favoritas'},
{'delete': '/protocolo/acoes/favoritas/:id'},
{'delete': '/protocolo/acoes/favoritas/:codAcao'},
{'get': '/protocolo/assuntos'},
{'post': '/protocolo/assuntos'},
{'put': '/protocolo/assuntos'},
{'delete': '/protocolo/assuntos'},
{'get': '/protocolo/assuntos/:codAssunto/:codClassificacao'},
{'get': '/protocolo/classificacoes'},
{'post': '/protocolo/classificacoes'},
{'put': '/protocolo/classificacoes'},
{'delete': '/protocolo/classificacoes'},
{'get': '/protocolo/despachospadrao'},
{'post': '/protocolo/despachospadrao'},
{'put': '/protocolo/despachospadrao'},
{'delete': '/protocolo/despachospadrao'},
{'get': '/protocolo/tramites'},
{'post': '/protocolo/tramites'},
{
'put':
'/protocolo/tramites/:codClassiOld/:codAssuntoOld/:ordemOld/:exercicioOld'
},
{'delete': '/protocolo/tramites'},
{'get': '/protocolo/processos/:anoExercicio/:codProcesso'},
{'get': '/protocolo/processos/em/apenso/:anoExercicio/:codProcesso'},
{'get': '/protocolo/processos/apenso/a/:anoExercicio/:codProcesso'},
{'get': '/protocolo/processos/andamentos/:anoExercicio/:codProcesso'},
{
'get':
'/protocolo/processos/despachos/:anoExercicio/:codProcesso/:codAndamento/:codUsuario/:timestamp'
},
{'get': '/protocolo/processos'},
{'get': '/protocolo/processos/areceber'},
{'get': '/protocolo/processos/aemcaminhar'},
{'get': '/protocolo/processos/byfiltros'},
{'get': '/protocolo/processos/aapensara'},
{'get': '/protocolo/processos/adesapensar'},
{'get': '/protocolo/processos/adespachar'},
{'get': '/protocolo/processos/aalterar'},
{'get': '/protocolo/processos/acancelar'},
{'get': '/protocolo/processos/aarquivar'},
{'get': '/protocolo/processos/adesarquivar'},
{'post': '/protocolo/processos'},
{'post': '/protocolo/processos/implantar'},
{'put': '/protocolo/processos'},
{'post': '/protocolo/processos/receber/lote'},
{'post': '/protocolo/processos/despachar/lote'},
{'post': '/protocolo/processos/encaminhar/lote'},
{'post': '/protocolo/processos/cancelarencaminhamento/lote'},
{'post': '/protocolo/processos/apensar/lote'},
{'post': '/protocolo/processos/desapensar/lote'},
{'post': '/protocolo/processos/arquivar/lote'},
{'post': '/protocolo/processos/desarquivar/lote'},
{'post': '/protocolo/processos/anexos'},
{'get': '/protocolo/processos/anexos/:codProcesso/:anoExercicio'},
{'get': '/protocolo/situacoes'},
{'get': '/protocolo/historicoarquivamento'},
{'post': '/protocolo/historicoarquivamento'},
{'put': '/protocolo/historicoarquivamento'},
{'delete': '/protocolo/historicoarquivamento'},
{'get': '/protocolo/listagemprocessos'},
{'post': '/protocolo/listagemprocessos'},
{'get': '/protocolo/documentos'},
{'post': '/protocolo/documentos'},
{'put': '/protocolo/documentos'},
{'delete': '/protocolo/documentos'},
{'get': '/protocolo/atributosprotocolo'},
{'post': '/protocolo/atributosprotocolo'},
{'put': '/protocolo/atributosprotocolo'},
{'delete': '/protocolo/atributosprotocolo'},
{'get': '/protocolo/processos/public/site/:anoExercicio/:codProcesso'},
];
Middleware logRequestsCustom(
{void Function(String message, bool isError)? logger}) =>
(innerHandler) {
final theLogger = logger ?? _defaultLogger;
return (request) {
var startTime = DateTime.now();
var watch = Stopwatch()..start();
return Future.sync(() => innerHandler(request)).then((response) {
var msg = _message(startTime, response.statusCode,
request.requestedUri, request.method, watch.elapsed);
theLogger(msg, false);
return response;
}, onError: (Object error, StackTrace stackTrace) {
if (error is HijackException) throw error;
var msg = _errorMessage(startTime, request.requestedUri,
request.method, watch.elapsed, error, stackTrace);
theLogger(msg, true);
// ignore: only_throw_errors
throw error;
});
};
};
String _formatQuery(String query) {
return query == '' ? '' : '?$query';
}
String _message(DateTime requestTime, int statusCode, Uri requestedUri,
String method, Duration elapsedTime) {
return '${requestTime.toIso8601String()} '
'${elapsedTime.toString().padLeft(15)} '
'${method.padRight(7)} [$statusCode] ' // 7 - longest standard HTTP method
'${requestedUri.path}${_formatQuery(requestedUri.query)}'
' isolate: ${Isolate.current.debugName}';
}
String _errorMessage(DateTime requestTime, Uri requestedUri, String method,
Duration elapsedTime, Object error, StackTrace? stack) {
var chain = Chain.current();
if (stack != null) {
chain = Chain.forTrace(stack)
.foldFrames((frame) => frame.isCore || frame.package == 'shelf')
.terse;
}
var msg = '$requestTime\t$elapsedTime\t$method\t${requestedUri.path}'
'${_formatQuery(requestedUri.query)}\n$error';
return '$msg\n$chain';
}
void _defaultLogger(String msg, bool isError) {
if (isError) {
print('[ERROR] $msg');
} else {
print(msg);
}
}
Hey @insinfo, I love shelf for what they were able to build. I'm building this Backend Framework that should let you do this seamlessly. Also, it allows you to use Shelf Middle-wares directly.
Peep the repository and let me know what you think. https://github.com/codekeyz/pharaoh
Also, I am looking for contributors. 👋
@codekeyz Although I admire your dedication to creating another framework, I think that instead of creating another framework we could join efforts in developing the angel framework here https://github.com/dukefirehawk/angel
add a method to group routes and apply specific middleware to this group