[API, Dart] Dart на сервере
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Недавно столкнулся с необходимостью написать REST API сервер на Dart. Оставим за рамками этой статьи почему и зачем это было надо, но первое с чем я столкнулся - выбор библиотек. Так уж сложилось, что я привык писать на NodeJS используя KoaJS в качестве веб сервера. Простая и удобная библиотека с кучей расширений для любой необходимости. А вот Dart в этом плане несколько подкачал. На момент поисков из "живых" пакетов на pub.dev был только shelf. Что-то отдаленно похожее, но по факту жутко неудобное. Неделю промучившись с оным, понял, надо писать свое, с блэкджеком... что-нибудь в стиле того же KoaJSЗнакомствоЗнакомьтесь - Dia - легковесный и простой http сервер на Dart. Основная идея проекта: контекст http-запроса, который проходит очередь middleware которые его читают и меняют при необходимости. Второй основополагающий принцип - минимализм. Только самое необходимое. Это не фреймверк а именно пакет. Для любого расширяющего функционала - отдельный пакет. Это позволяет сократить размер кодовой базы проекта подключив только необходимые пакеты.Собственно поэтому сам Dia почти ничего не умеет. Только создавать и прокидывать по очереди middleware контекст. Весь остальной функционал который необходим (например мне в моих проектах), реализован в отдельных пакетах которые мы рассмотрим чуть позже.ПрактикаА сейчас приступим к практике. Устанавливается все стандартно и просто, добавлением в pubspec.yaml соответсвующих строк:
dependencies:
dia: ^0.0.7
Используется тоже просто. Вот минимальный пример:
import 'package:dia/dia.dart';
main() {
/// Create instance of Dia
final app = App();
/// Add middleware
app.use((ctx, next) async {
/// write response on all query
ctx.body = 'Hello world!';
});
/// Listen localhost:8080
app
.listen('localhost', 8080)
.then((info) => print('Server started on http://localhost:8080'));
}
КонтекстКто знаком с KoaJS поймет практически с лету. Для остальных поясню основные моменты. app.use - добавляет в очередь middleware. Это по сути, асинхронная функция, принимающая в качестве аргументов контекст и ссылку на следующую middleware. Контекст представляет собой класс предоставляющий быстрые методы доступа к полям ответа (код ответа, тело, заголовки) и дополнительные методы типа throwError - который позволяет сразу отправить HTTP ошибку в качестве ответа.Контекст можно расширить своими полями и методами. Например добавить в него поле содержащее данные об авторизованном пользователе:
class MyContext extends Context{
User? user;
MyContext(HttpRequest request): super(request);
}
main() {
/// Create instance of Dia
final app = App<MyContext>();
app.use((ctx, next) async {
ctx.user = new User('test');
await next();
});
app.use((ctx, next) async {
if(ctx.user==null){
ctx.trowError(401);
}
});
/// Add middleware
app.use((ctx, next) async {
/// write response on all query
ctx.body = 'Hello world!';
});
/// Listen localhost:8080
app
.listen('localhost', 8080)
.then((info) => print('Server started on http://localhost:8080'));
}
Nextnext - ссылка на следующее middleware в очереди. Когда middleware не возвращает окончательный результат, а только изменяет контекст, ему часто требуется дождаться завершения следующего middleware. Это необходимо, например, чтобы добавить логирование или обработку ошибок:
app.use((ctx,next) async {
final start = DateTime.now();
await next();
final diff = DateTime.now().difference(start).inMicroseconds;
print('${ctx.request.method} ${ctx.request.uri.path} $diff ms')
});
Готовые middlewareКак я уже говорил, весь дополнительный функционал должен быть реализован в отдельных пакетах. Некоторые из них уже опубликованы:
- dia_cors - middleware для добавления CORS заголовков
- dia_static - отдает файлы на скачивание из заданной папки. Может использоваться как сервер статики
- dia_router - позволяет задать middleware для определенных url и http методов. Самое то для реализации REST API
- dia_body - разбирает http запрос и возвращает из него переданные параметры и загруженные файлы.
Рассмотрим два последних пакета более подробно, ибо с ними не все так просто.РоутерПервый из них - dia_router. Для его применения необходимо использовать контекст с миксином Routing.
class ContextWithRouting extends Context with Routing {
ContextWithRouting(HttpRequest request) : super(request);
}
void main() {
/// create Dia app with Routing mixin on Context
final app = App<ContextWithRouting>();
/// create router
final router = Router<ContextWithRouting>('/route');
/// add handler to GET request
router.get('/data/:id', (ctx, next) async {
ctx.body = '${ctx.params}';
});
/// start server
app
.listen('localhost', 8080)
.then((_) => print('Started on http://localhost:8080'));
}
Если запустить этот код и открыть в браузере ссылку http://localhost:8080/data/12 то мы увидим{id: 12}.Т.е мы не только задали специальный обработчик для фиксированного URL но и выдернули из него параметр с помощью регулярки. Кто знаком с npm пакетом koa-router оценит это удобство!ПарсерСледующий пакет - dia_body. В запросе данные передают не только в пути но и еще кучей извращенных методов. Например пихают в body голый json или шлют form-data, а некоторые, так вообще передают данные в x-www-form-urlencoded. Мало того, есть еще и те кто шлет файлы как multipart/form-data! Вот, чтобы это все обработать нам и понадобится этот пакет.Как вы наверное уже догадались, тут нам тоже необходим расширенный контекст с миксином ParsedBody:
class ContextWithBody extends Context with ParsedBody {
ContextWithBody(HttpRequest request) : super(request);
}
void main() {
final app = App<ContextWithBody>();
app.use(body());
app.use((ctx, next) async {
ctx.body = '''
query=${ctx.query}
parsed=${ctx.parsed}
files=${ctx.files}
''';
});
/// Start server listen on localhost:8080
app
.listen('localhost', 8080)
.then((info) => print('Server started on http://localhost:8080'));
}
В резултате мы увидим:
- ctx.query - параметры из URL вида ?param=value в Map<String,String>
- ctx.parsed - параметры из тела запроса, будь то json, form-data или x-www-form-urlencoded в Map<String,dynamic>
- ctx.files - загруженные файлы в Map<String,List<UploadedFile>> где String - имя параметра, UploadedFile - класс содержащий filename и File с загруженным файлом.
По дефолту, файлы загружаются во временную системную директорию, но это можно изменить используюя необязательный именованный параметр uploadDirectoryQAА как быть когда надо использовать оба эти пакета вместе? Да еще свои параметры в контекст добавить? Нет ничего проще! Именно для этого в dart и существуют миксины:
class CustomContext extends Context with Routing, ParsedBody {
User? user;
ContextWithBody(HttpRequest request) : super(request);
}
Ну а если я хочу запустить сервер в режиме SSL? Тоже все просто! Дело в том, что "под капотом" Dia использует обычный HttpServer из dart:io, так что Dia автоматически поддерживает все что поддерживает он. Например ctx.request из контекста - HttpRequest из dart:io. Так что можете использовать это при написании своих middleware. А вот так запускается сервер в режиме https:
const serverKey = 'cert/key.pem';
const certificateChain = 'cert/chain.pem';
final serverContext = SecurityContext();
serverContext
.useCertificateChainBytes(await File(certificateChain).readAsBytes());
serverContext.usePrivateKey(serverKey, password: 'password');
final server = await app.listen(
'localhost', 8444,
securityContext: serverContext);
ИтогиDia пакет новый и обкатан пока только на одном "боевом" проекте. Вероятно в нем есть баги и недоработки. Например, в коде надо навести порядок с документированием API, да еще много чего надо бы сделать. Поэтому и версия у проекта пока еще не релизная. Буду рад любой обратной связи и помощи.Проект опубликован на GitHub под MIT лицензией. Жду ваших ишью и пуллреквестов!
===========
Источник:
habr.com
===========
Похожие новости:
- [Python, Алгоритмы, API, 1С] Tesseract vs таблицы. Распознавание документов. Часть 2
- Google одержал победу в разбирательстве с Oracle, связанном с Java и Android
- [Информационная безопасность, Разработка под iOS, Разработка мобильных приложений, API, Монетизация мобильных приложений] Apple запрещает использовать рекламные SDK для создания цифрового отпечатка пользователя
- [Ненормальное программирование, Я пиарюсь, C++, ООП] CDD — Cli Driven Development
- [Python, MongoDB, Maps API] Аспекты учета и поиска геоинформационных объектов с задействованием MongoDB
- [HTML, Usability, Дизайн, Звук] «Радио, погода, время всегда под рукой» или история одного решения (железо, софт, интерфейс)
- [IT-инфраструктура, Amazon Web Services, DevOps, Serverless] Реализуем бессерверный API с AWS Gateway и Lambda (перевод)
- [CSS, API, Help Desk Software] 5 лучших программ для базы знаний
- [Мессенджеры, Программирование, Разработка мобильных приложений, API] Twilio vs Sendbird vs CONTUS MirrorFly Feature Comparsion | Twilio vs Competitors
- [Python, API, CAD/CAM] Создание удобного и наглядного keymap/hotkey для PyCharm или любой другой программы
Теги для поиска: #_api, #_dart, #_dart, #_httpserver (http-сервер), #_koa, #_webserver, #_api, #_dart
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 01:07
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Недавно столкнулся с необходимостью написать REST API сервер на Dart. Оставим за рамками этой статьи почему и зачем это было надо, но первое с чем я столкнулся - выбор библиотек. Так уж сложилось, что я привык писать на NodeJS используя KoaJS в качестве веб сервера. Простая и удобная библиотека с кучей расширений для любой необходимости. А вот Dart в этом плане несколько подкачал. На момент поисков из "живых" пакетов на pub.dev был только shelf. Что-то отдаленно похожее, но по факту жутко неудобное. Неделю промучившись с оным, понял, надо писать свое, с блэкджеком... что-нибудь в стиле того же KoaJSЗнакомствоЗнакомьтесь - Dia - легковесный и простой http сервер на Dart. Основная идея проекта: контекст http-запроса, который проходит очередь middleware которые его читают и меняют при необходимости. Второй основополагающий принцип - минимализм. Только самое необходимое. Это не фреймверк а именно пакет. Для любого расширяющего функционала - отдельный пакет. Это позволяет сократить размер кодовой базы проекта подключив только необходимые пакеты.Собственно поэтому сам Dia почти ничего не умеет. Только создавать и прокидывать по очереди middleware контекст. Весь остальной функционал который необходим (например мне в моих проектах), реализован в отдельных пакетах которые мы рассмотрим чуть позже.ПрактикаА сейчас приступим к практике. Устанавливается все стандартно и просто, добавлением в pubspec.yaml соответсвующих строк: dependencies:
dia: ^0.0.7 import 'package:dia/dia.dart';
main() { /// Create instance of Dia final app = App(); /// Add middleware app.use((ctx, next) async { /// write response on all query ctx.body = 'Hello world!'; }); /// Listen localhost:8080 app .listen('localhost', 8080) .then((info) => print('Server started on http://localhost:8080')); } class MyContext extends Context{
User? user; MyContext(HttpRequest request): super(request); } main() { /// Create instance of Dia final app = App<MyContext>(); app.use((ctx, next) async { ctx.user = new User('test'); await next(); }); app.use((ctx, next) async { if(ctx.user==null){ ctx.trowError(401); } }); /// Add middleware app.use((ctx, next) async { /// write response on all query ctx.body = 'Hello world!'; }); /// Listen localhost:8080 app .listen('localhost', 8080) .then((info) => print('Server started on http://localhost:8080')); } app.use((ctx,next) async {
final start = DateTime.now(); await next(); final diff = DateTime.now().difference(start).inMicroseconds; print('${ctx.request.method} ${ctx.request.uri.path} $diff ms') });
class ContextWithRouting extends Context with Routing {
ContextWithRouting(HttpRequest request) : super(request); } void main() { /// create Dia app with Routing mixin on Context final app = App<ContextWithRouting>(); /// create router final router = Router<ContextWithRouting>('/route'); /// add handler to GET request router.get('/data/:id', (ctx, next) async { ctx.body = '${ctx.params}'; }); /// start server app .listen('localhost', 8080) .then((_) => print('Started on http://localhost:8080')); } class ContextWithBody extends Context with ParsedBody {
ContextWithBody(HttpRequest request) : super(request); } void main() { final app = App<ContextWithBody>(); app.use(body()); app.use((ctx, next) async { ctx.body = ''' query=${ctx.query} parsed=${ctx.parsed} files=${ctx.files} '''; }); /// Start server listen on localhost:8080 app .listen('localhost', 8080) .then((info) => print('Server started on http://localhost:8080')); }
class CustomContext extends Context with Routing, ParsedBody {
User? user; ContextWithBody(HttpRequest request) : super(request); } const serverKey = 'cert/key.pem';
const certificateChain = 'cert/chain.pem'; final serverContext = SecurityContext(); serverContext .useCertificateChainBytes(await File(certificateChain).readAsBytes()); serverContext.usePrivateKey(serverKey, password: 'password'); final server = await app.listen( 'localhost', 8444, securityContext: serverContext); =========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 01:07
Часовой пояс: UTC + 5