Last active
May 27, 2019 10:40
-
-
Save Hecatoncheir/96e381ee3e441d301b0c61316cb5035a to your computer and use it in GitHub Desktop.
Dart http router
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /// HttpMethods - набор методов запросов который обрабатывает сервер. | |
| /// https://en.wikipedia.org/wiki/Hypertext_Transfer_Protocol#Request_methods | |
| abstract class HttpMethods { | |
| /// options - метод позволяет клиенту определять | |
| /// параметры и / или требования, связанные с ресурсом, | |
| /// или возможности сервера, до отправки запроса к самому ресурсу. | |
| /// https://ru.wikipedia.org/wiki/HTTP#OPTIONS | |
| static const String options = 'OPTIONS'; | |
| /// get - запрашивает представление указанного ресурса. | |
| /// Запросы с использованием GET должны только извлекать данные и не должны | |
| /// иметь никакого другого эффекта. | |
| /// https://ru.wikipedia.org/wiki/HTTP#GET | |
| static const String get = 'GET'; | |
| /// https://ru.wikipedia.org/wiki/HTTP#POST | |
| static const String post = 'POST'; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| library http_router; | |
| import 'dart:async' show Stream; | |
| import 'dart:convert' show json; | |
| import 'dart:io' show HttpRequest, HttpStatus; | |
| import 'package:pedantic/pedantic.dart' show unawaited; | |
| import 'package:logging/logging.dart' show Logger; | |
| import 'http_methods.dart' show HttpMethods; | |
| /// Объект для отправки сообщений в лог. | |
| Logger log = Logger('HttpRouter'); | |
| /// HttpRouter - роутер обрабатывающий запросы проверяющий url | |
| class HttpRouter { | |
| /// staticOptionsResources - запросы без переменных в пути. | |
| Map<String, Function> staticOptionsResources; | |
| /// staticGetResources - список ресурсов c адресами которые не | |
| /// содержат переменные кроме версии api. | |
| Map<String, Function> staticGetResources; | |
| /// dynamicGetResources - список ресурсов c адресами которые содержат | |
| /// переменные которые нужно разобрать прежде чем передать в обработчик. | |
| Map<String, Function> dynamicGetResources; | |
| /// staticPostResources - список ресурсов c адресами которые не | |
| /// содержат переменные кроме версии api. | |
| Map<String, Function> staticPostResources; | |
| /// dynamicPostResources - список ресурсов c адресами которые содержат | |
| /// переменные которые нужно разобрать прежде чем передать в обработчик. | |
| Map<String, Function> dynamicPostResources; | |
| Stream<HttpRequest> _requests; | |
| /// Конструктор класса | |
| HttpRouter(this._requests) { | |
| staticOptionsResources = {}; | |
| staticGetResources = {}; | |
| dynamicGetResources = {}; | |
| staticPostResources = {}; | |
| dynamicPostResources = {}; | |
| Future<void> defaultPipeline(HttpRequest request) => | |
| _httpRequestHandler(request); | |
| _requests?.listen(defaultPipeline); | |
| } | |
| /// _httpRequestHandler - обработчик запросов к ресурсам сервера. | |
| Future<void> _httpRequestHandler(HttpRequest request) async { | |
| unawaited(callHandlerOfPath(request.uri.path, request.method, request)); | |
| } | |
| /// callHandlerOfPath - метод для вызова обработчика | |
| /// определенного метода и пути. | |
| Future<void> callHandlerOfPath( | |
| String path, String method, HttpRequest request) async { | |
| if (method == HttpMethods.get) { | |
| unawaited( | |
| _handleGetRequest(path, request).catchError(_handleRequestError)); | |
| } | |
| if (method == HttpMethods.post) { | |
| unawaited( | |
| _handlePostRequest(path, request).catchError(_handleRequestError)); | |
| } | |
| } | |
| Future<void> _handleRequestError(exception) async { | |
| log.warning(json.encode({ | |
| 'Event': 'Exception', | |
| 'Details': json.encode({'Exception': exception}) | |
| })); | |
| } | |
| Future<void> _sendNotFountPathOfAPI(HttpRequest request) async { | |
| request.response.statusCode = HttpStatus.notFound; | |
| request.response.write(json.encode({ | |
| 'Error': 'Path of API not found', | |
| 'Details': {'Request path': request.uri.path} | |
| })); | |
| unawaited(request.response.close()); | |
| } | |
| Future<void> _handleGetRequest(String path, HttpRequest request) async { | |
| final String staticRouterPath = await findRouterPathByRequestPath( | |
| staticGetResources.keys.toList(), path); | |
| final String dynamicRouterPath = await findRouterPathByRequestPath( | |
| dynamicGetResources.keys.toList(), path); | |
| if (staticRouterPath == null && dynamicRouterPath == null) { | |
| unawaited(_sendNotFountPathOfAPI(request)); | |
| return; | |
| } | |
| if (staticRouterPath != null) { | |
| staticGetResources[staticRouterPath](request); | |
| } else { | |
| final routerPath = await findRouterPathByRequestPath( | |
| dynamicGetResources.keys.toList(), path); | |
| final variables = | |
| await getVariablesFromRequestPathByRouterPath(routerPath, path); | |
| dynamicGetResources[dynamicRouterPath](request, variables); | |
| } | |
| } | |
| Future<void> _handlePostRequest(String path, HttpRequest request) async { | |
| final String staticRouterPath = await findRouterPathByRequestPath( | |
| staticPostResources.keys.toList(), path); | |
| final String dynamicRouterPath = await findRouterPathByRequestPath( | |
| dynamicPostResources.keys.toList(), path); | |
| if (staticRouterPath == null && dynamicRouterPath == null) { | |
| unawaited(_sendNotFountPathOfAPI(request)); | |
| return; | |
| } | |
| if (staticRouterPath != null) { | |
| staticPostResources[staticRouterPath](request); | |
| } else { | |
| final routerPath = await findRouterPathByRequestPath( | |
| dynamicPostResources.keys.toList(), path); | |
| final variables = | |
| await getVariablesFromRequestPathByRouterPath(routerPath, path); | |
| dynamicPostResources[dynamicRouterPath](request, variables); | |
| } | |
| } | |
| /// findRouterPathByRequestPath - нахождение пути роута по пути из запроса. | |
| Future<String> findRouterPathByRequestPath( | |
| List<String> routerPaths, String requestPath) async { | |
| String routerPath; | |
| for (String path in routerPaths) { | |
| if (await compareRouterPathAndRequestPath(path, requestPath) == true) { | |
| routerPath = path; | |
| break; | |
| } | |
| } | |
| return routerPath; | |
| } | |
| /// getSegmentsOfPath - разбивание пути на сегменты. | |
| Future<List<String>> getSegmentsOfPath(String path) async => | |
| path.split('/').where((segment) => segment.isNotEmpty).toList(); | |
| /// compareRouterPathAndRequestPath - метод для нахождения | |
| /// пути роута по пути из запроса сравнивая их. | |
| /// TODO: понизить сложность | |
| Future<bool> compareRouterPathAndRequestPath( | |
| String routerPath, String requestPath) async { | |
| final routerPathSegments = await getSegmentsOfPath(routerPath); | |
| final requestPathSegments = await getSegmentsOfPath(requestPath); | |
| final List<bool> comparedSegmentsResults = []; | |
| if (requestPathSegments.length == routerPathSegments.length) { | |
| for (String requestPathSegment in requestPathSegments) { | |
| final int indexOfSegment = | |
| requestPathSegments.indexOf(requestPathSegment); | |
| final String routerPathSegment = routerPathSegments[indexOfSegment]; | |
| if (routerPathSegment.contains(':')) { | |
| comparedSegmentsResults.add(true); | |
| continue; | |
| } | |
| if (requestPathSegment.contains(routerPathSegment)) { | |
| comparedSegmentsResults.add(true); | |
| } else { | |
| comparedSegmentsResults.add(false); | |
| } | |
| } | |
| } | |
| bool isCompare = false; | |
| if (comparedSegmentsResults.isNotEmpty && | |
| !comparedSegmentsResults.contains(false)) isCompare = true; | |
| return isCompare; | |
| } | |
| /// getVariablesFromRequestPathByRouterPath - метод возвращает map данных | |
| /// из пути запрсоа. | |
| /// {':some_key_from_router_path' : 'some_value_from_request_path'} | |
| Future<Map<String, String>> getVariablesFromRequestPathByRouterPath( | |
| String routerPath, String requestPath) async { | |
| final routerPathSegments = await getSegmentsOfPath(routerPath); | |
| final requestPathSegments = await getSegmentsOfPath(requestPath); | |
| final variables = <String, String>{}; | |
| for (String routerPathSegment in routerPathSegments) { | |
| if (routerPathSegment.contains(':')) { | |
| final int index = routerPathSegments.indexOf(routerPathSegment); | |
| variables[routerPathSegment] = requestPathSegments[index]; | |
| } | |
| } | |
| return variables; | |
| } | |
| /// isStaticPath - проверяет содержит ли путь какие-либо переменные или же | |
| /// должен точно совпадать с путем запроса к серверу. | |
| bool isStaticPath(String path) { | |
| bool _isStatic; | |
| if (path.contains(':')) { | |
| _isStatic = false; | |
| } else { | |
| _isStatic = true; | |
| } | |
| return _isStatic; | |
| } | |
| /// options - добавление обработка зпросов с методом options. | |
| void options({String path, Function handler}) { | |
| staticOptionsResources[path] = handler; | |
| } | |
| /// get - добавление обработчика зпросов с методом get. | |
| void get({String path, Function handler}) { | |
| if (isStaticPath(path)) { | |
| staticGetResources[path] = handler; | |
| } else { | |
| dynamicGetResources[path] = handler; | |
| } | |
| } | |
| /// post - добавление обработчика зпросов с методом post. | |
| void post({String path, Function handler}) { | |
| if (isStaticPath(path)) { | |
| staticPostResources[path] = handler; | |
| } else { | |
| dynamicPostResources[path] = handler; | |
| } | |
| } | |
| /// containsHandlerOfPath - проверка наличия обрабатываемого пути. | |
| bool containsHandlerOfPath(String path) { | |
| var _isPathContains = false; | |
| try { | |
| if (staticOptionsResources.containsKey(path) || | |
| staticPostResources.containsKey(path) || | |
| dynamicPostResources.containsKey(path) || | |
| staticGetResources.containsKey(path) || | |
| dynamicGetResources.containsKey(path)) _isPathContains = true; | |
| } on Exception catch (exception) { | |
| log.warning(json.encode({ | |
| 'Event': 'Exception', | |
| 'Details': json.encode({'Exception': exception}) | |
| })); | |
| } | |
| return _isPathContains; | |
| } | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'dart:convert'; | |
| import 'dart:io'; | |
| import 'dart:math'; | |
| import 'package:logging/logging.dart'; | |
| final Logger log = Logger('HttpUtils'); | |
| /// HttpUtils - набор вспомогательных методов. | |
| class HttpUtils { | |
| /// getBodyOfRequest - возвращает тело запроса. | |
| static Future<Map<String, dynamic>> getBodyOfRequest( | |
| HttpRequest request) async { | |
| try { | |
| String encodedBody; | |
| try { | |
| /// Получение body post запроса | |
| encodedBody = await request.transform(utf8.decoder).join(); | |
| } catch (exception) { | |
| log.warning(json.encode({ | |
| 'Event': 'Exception', | |
| 'Details': json.encode({'Exception': exception}) | |
| })); | |
| /// Если body пустой в ответ отправляется сообщение об | |
| /// ошибке со статусом NOT_ACCEPTABLE | |
| if (encodedBody == null || encodedBody.isEmpty) return null; | |
| } | |
| /// Если body запроса не пустое его нужно декодировать | |
| Map<String, dynamic> decodedBody; | |
| try { | |
| /// В теле POST запроса должна содержаться json структура | |
| decodedBody = json.decode(encodedBody); | |
| } catch (exception) { | |
| log.warning(json.encode({ | |
| 'Event': 'Exception', | |
| 'Details': json.encode({'Exception': exception.toString()}) | |
| })); | |
| return null; | |
| } | |
| return decodedBody; | |
| } catch (exception) { | |
| log.warning(json.encode({ | |
| 'Event': 'Exception', | |
| 'Details': json.encode({'Exception': exception.toString()}) | |
| })); | |
| } | |
| return null; | |
| } | |
| } | |
| /// getRandomPort - функция генерирует случайное число в заданном диапазоне. | |
| int getRandomPort({int min, int max}) { | |
| final random = Random(); | |
| final index = random.nextInt(max - min); | |
| final ports = List.generate(max - min, (i) => min + i); | |
| return ports[index]; | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment