Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add a method to group routes and apply specific middleware to this group #393

Open
insinfo opened this issue Nov 12, 2023 · 3 comments
Open

Comments

@insinfo
Copy link

insinfo commented Nov 12, 2023

add a method to group routes and apply specific middleware to this group

  app.chain([simulatedMiddleware]).group('/api/v1', (router) {
      for (final sr in simulatedRoutes) {
        final method = sr.keys.first.toUpperCase();
        final path = sr.values.first;
        router.addRoute(method, path, (req, res) => simulatedWork);
      }
    });
@insinfo
Copy link
Author

insinfo commented Nov 12, 2023

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);
  }
}

@codekeyz
Copy link

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. 👋

@insinfo
Copy link
Author

insinfo commented Nov 23, 2023

@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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants