Skip to content

Instantly share code, notes, and snippets.

@Hecatoncheir
Last active May 27, 2019 10:40
Show Gist options
  • Select an option

  • Save Hecatoncheir/96e381ee3e441d301b0c61316cb5035a to your computer and use it in GitHub Desktop.

Select an option

Save Hecatoncheir/96e381ee3e441d301b0c61316cb5035a to your computer and use it in GitHub Desktop.
Dart http router
/// 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';
}
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;
}
}
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