Статьи

Простые пользовательские веб-серверы с Dart и Redstone

Использование Node.js для создания сценариев на стороне сервера сейчас в моде и не без оснований . Это быстрый, управляемый событиями, и, возможно, лучше всего для веб-разработчиков, он работает на JavaScript. Если ваш код переднего плана полностью на JavaScript, преимущества использования одного и того же языка на внутреннем интерфейсе очевидны. Node даже имеет отличные серверные инфраструктуры, такие как Express, которые позволяют быстро и легко создавать пользовательские веб-серверы.

Но есть ли лучший способ?

Что такое дартс?

Dart — это масштабируемый объектно-ориентированный язык программирования с открытым исходным кодом, с надежными библиотеками и средами исполнения для создания веб-приложений, серверов и мобильных приложений. Первоначально он был разработан Ларсом Баком и Каспером Лундом для Google, но с тех пор стал стандартом ECMA .

Вы можете получить все преимущества Node, а также некоторые другие, используя Dart и Redstone Framework на стороне сервера. В качестве дополнительного бонуса вы оставляете за собой причуды JavaScript. Как и Node, виртуальная машина Dart управляется событиями, асинхронна и позволяет создавать клиентские и серверные приложения на одном языке и обмениваться кодом между ними. Здесь нет места для того, чтобы рассказать обо всех преимуществах Dart по сравнению с JavaScript (возможно, в другой статье), но если вас интересует более подробная информация, перейдите по некоторым ссылкам ниже.

Преимущества Дартс

Этот список просто царапает поверхность. Проверьте онлайн книгу Dart: Up and Running для ускоренного курса на языке. Если вы знаете JavaScript, Java, PHP, ActionScript, C / C ++ или другой язык «фигурных скобок», вы обнаружите, что Dart вам знаком, и вы сможете работать с Dart в течение часа или около того.

Получить дартс

Есть много редакторов, которые поддерживают разработку Dart, и команда Dart объявила, что JetBrains WebStorm будет предпочтительным редактором в будущем, но для простоты (и бесплатности) мы будем использовать популярный Sublime Text 3 с плагином Dart. для этого урока. Хотя технически он все еще находится в бета-версии, это рекомендуемая версия для использования.

Загрузить программное обеспечение

Вам понадобится несколько программ для завершения этого урока.

Возвышенный текст 3

Если у вас еще нет Sublime Text 3, загрузите и установите версию, соответствующую вашей операционной системе. Последняя сборка на момент написания статьи — 3083.

Dart SDK

Загрузите правильный Dart SDK для вашей системы. Обратите внимание, что для этого урока вам не понадобится редактор (в настоящее время не рекомендуется) или Dartium (специальная сборка Chromium со встроенной виртуальной машиной Dart).

Разархивируйте Dart SDK и поместите папку dart-sdk любое место вашей системы. В Windows я предпочитаю C:/Program Files/dart/dart-sdk .

Настроить возвышенный текст 3

Запустите Sublime Text 3. Вам нужно будет настроить редактор для поддержки Dart.

Контроль пакетов

Если вы еще не установили Package Control , следуйте этим инструкциям, чтобы установить его сейчас. Обратите внимание, что вам нужно будет перезапустить Sublime Text 3 после завершения установки.

Dart Plugin

  1. В меню Sublime выберите Инструменты-> Командная палитра … и введите команду install .
  2. Выберите Package Control: Install Package из выпадающего списка.
  3. Введите Dart и выберите пакет Dart. Обратите внимание, что вам может потребоваться перезапустить Sublime, прежде чем все функции плагина станут доступны.
  4. В меню Sublime выберите Настройки-> Настройки пакета-> Дарт-> Настройки — Пользователь . Откроется файл настроек для плагина Dart.
  5. Введите следующий код в файл настроек и сохраните его, где /path/to/dart-sdk — это путь к папке dart-sdk в вашей системе.
 { "dart_sdk_path": "/path/to/dart-sdk" } 

Создать проект дартс

  1. В меню Sublime выберите Инструменты-> Командная палитра … и введите Dart:
  2. Выберите Dart: Stagehand, а затем консоль, чтобы создать приложение командной строки.
  3. В нижней части окна Sublime введите путь, по которому вы хотите, чтобы инструмент Dart’s Stagehand создавал ваш новый проект Dart. Обратите внимание, что целевой каталог должен быть новым или пустым. Я рекомендую назвать это что-то вроде redstone_intro .

Примечание : если во время описанного выше процесса вы видите ошибку, что Stagehand не включен, вам нужно сделать следующее из терминала:

 cd /path/to/dart-sdk/bin pub global activate stagehand 

Приобретать зависимости

После создания нового проекта откройте файл pubspec.yaml . Dart использует ваш файл pubspec для управления зависимостями вашего проекта. Замените предварительно сгенерированный раздел зависимостей в pubspec.yaml тем, который выглядит следующим образом (удалите все символы # , которые указывают на комментарий):

 dependencies: redstone: '>=0.5.21 <0.6.0' 

Сохраните файл. Sublime автоматически проинструктирует менеджера пакетов Dart, называемого Pub , приобрести все необходимые зависимости, включая инфраструктуру Redstone. Паб получит только версии Redstone в указанном диапазоне. Вы также можете заставить Sublime получать ваши зависимости с помощью горячей клавиши F7 во время редактирования pubspec.yaml .

Для получения дополнительной информации и примеров для Redstone см. Вики проекта Github.

Создать веб-сервер

Настроить простой сервер с Redstone просто. Откройте файл main.dart и удалите весь предварительно сгенерированный код. Вставьте следующий код на его место.

 import 'package:redstone/server.dart' as Server; void main() { Server.setupConsoleLog(); Server.start(); } 

Поскольку это может быть ваша первая программа Dart, давайте проанализируем этот код построчно. Разработчики, знакомые с Java, JavaScript, C # или подобными языками, обнаружат, что большинство из этих концепций сразу знакомы.

 import 'package:redstone/server.dart' as Server; 

Сначала вы сообщаете анализатору Dart, что будете использовать код из server.dart Redstone server.dart . Префикс специального package: указывает, что этот код является внешней зависимостью, приобретенной Pub. (Если хотите, вы можете проверить этот и все другие загруженные пакеты, изучив содержимое папки packages в вашем проекте.) Это импортирует классы и функции верхнего уровня Redstone в пространство имен вашей программы Dart. Поскольку он включает функции с общими именами, такими как start() , импортированный код содержится в пользовательском пространстве имен с именем Server с синтаксисом as Server .

 void main() 

Все программы Dart запускаются с помощью функции main() верхнего уровня. Dart позволяет вам необязательно указывать типы для переменных и возвращаемых значений функций, а void указывает, что main() ничего не вернет.

 Server.setupConsoleLog(); 

Вы импортировали пакет Redstone под псевдонимом Server , поэтому вы должны использовать эту ссылку при вызове его функций. Этот вызов не является строго необходимым, но он полезен при разработке. Он настраивает ведение журнала консоли для платформы Redstone, поэтому информативные сообщения будут появляться в консоли при выполнении кода Redstone.

 Server.start(); 

Эта строка вызывает функцию Redstone start() , которая запускает веб-сервер. По умолчанию он прослушивает запросы на 0.0.0.0:8080 (текущий IP-адрес на порту 8080), хотя это настраивается.

Это оно! Ваш сервер пока не отвечает на запросы сколько-нибудь значимым образом, но он слушает. Запустите код в main.dart с помощью горячей клавиши Shift+F7 . Вывод на консоль появится на панели вывода Sublime, которая по умолчанию отображается в нижней части интерфейса Sublime.

 INFO: <current date/time>: Running on 0.0.0.0:8080 

Вы можете остановить запущенное приложение, используя горячую клавишу Ctrl+Keypad0 (это Ctrl и нулевая клавиша на клавиатуре).

Примечание : вы также можете запустить / остановить сервер через терминал:

 cd /path/to/dart-sdk/bin ./dart /path/to/redstone_intro/bin/main.dart 

Чтобы получить доступ ко всем командам файла Dart через палитру команд Sublime (необходимо, если у вас нет клавиатуры), выберите в меню « Инструменты» -> «Палитра команд» и введите « Dart: , затем выберите нужную команду. Сочетание клавиш для этого — Ctrl+., Ctrl+. (удерживая Ctrl дважды коснитесь точки).

Для более удобных сочетаний клавиш, обратитесь к странице ярлыков плагина Dart.

Параметры сегмента пути

Теперь давайте заставим сервер отвечать на несколько запросов. Вы можете использовать аннотацию Route Redstone для настройки обработчика.

Здравствуйте

Добавьте следующий код в конец main.dart (после функции main() ).

 @Server.Route("/hello") String hello() { print("User soliciting greeting..."); return "Hello, Browser!"; } 

Обратите внимание, что вам все равно нужно включить ссылку на Server в аннотации, потому что это псевдоним, который вы применили к Redstone при импорте. Аннотация (начинающаяся с @ ) говорит маршрутизатору Redstone отвечать возвращаемым значением функции hello() всякий раз, когда он получает запрос в виде:

 http://localhost:8080/hello 

Если ваш скрипт сервера Dart все еще выполняется, остановите и перезапустите его, затем откройте браузер и перейдите по этому URL, чтобы увидеть сервер в действии. Вы должны увидеть строку «Hello, Browser!». Кроме того, вызов print() выведет полезное сообщение на системную консоль.

Здравствуй

Добавьте еще один блок Route в конец main.dart .

 @Server.Route("/hi") String hi() => "Hi, Browser!"; 

Этот код очень похож на предыдущий пример, но в нем используется синтаксис жирной стрелки Дартса для определения очень короткой функции. Написанная таким образом, функция hi() вернет результат одного выражения, следующего за стрелкой, в данном случае это просто строковый литерал.

Чтобы проверить этот пример в вашем браузере, используйте

 http://localhost:8080/hi 

Дополнительные параметры сегмента пути

Признание статических параметров — это хорошо, но в реальности вам часто приходится передавать динамические значения на сервер, чтобы получить индивидуальный ответ.

Макет данных

Для следующих нескольких упражнений вам необходимо добавить модель данных, которая будет служить фиктивной базой данных, а также несколько вспомогательных функций.

Выше main() , но под оператором import добавьте список пользователей.

 import 'package:redstone/server.dart' as Server; List<Map> users = [ {"id": "1", "username": "User1", "password": "123456", "type": "manager"}, {"id": "2", "username": "User2", "password": "password", "type": "programmer"}, {"id": "3", "username": "User3", "password": "12345", "type": "programmer"}, {"id": "4", "username": "User4", "password": "qwerty", "type": "secretary"}, {"id": "5", "username": "User5", "password": "123456789", "type": "secretary"} ]; void main() { Server.setupConsoleLog(); Server.start(); } 

В Dart список по сути является массивом, а карта работает как стандартный объект JavaScript (или словарь, или хэш-карта из языка со статической типизацией). Переменная users определена как список элементов Map с синтаксисом List <Map>. Буквальный синтаксис с использованием квадратных скобок и фигурных скобок должен быть знаком программистам JavaScript. Определение users выше main() делает его переменной верхнего уровня, доступной для всех функций в файле.

Вспомогательные функции

Теперь, когда у вас есть список пользователей для запроса, пришло время определить несколько вспомогательных функций для форматирования ответов сервера. Добавьте их в конец main.dart .

 Map success(String messageType, payload) { return { "messageType": messageType, "payload": payload }; } Map error(String errorMessage) { print(errorMessage); return { "messageType": "error", "error": errorMessage }; } 

Первая функция success() возвращает Map, которую она строит из двух параметров. messageType — это строка, которая будет «user» или «users», в зависимости от того, отвечает ли сервер одним пользователем или списком пользователей. Параметр payload намеренно оставлен нетипизированным, чтобы быть гибким. dynamic тип по умолчанию применяется языком дартс.

Функция error() сути делает то же самое, но возвращаемая карта заполнена значениями, соответствующими условию ошибки.

Когда один из обработчиков возвращает Map вместо простой строки, платформа Redstone автоматически сериализует ее в JSON при выходе.

Получить пользователя по идентификатору

Теперь вы готовы добавить еще один обработчик маршрута в main.dart .

 @Server.Route("/user/id/:id") Map getUserByID(String id) { print("Searching for user with ID: $id"); // convert the ID from String to int int index = int.parse(id, onError: (_) => null); // check for error if (index == null || index < 1 || index > users.length) { return error("Invalid ID"); } // get user Map foundUser = users[index - 1]; // return user return success("user", foundUser); } 

Маршрут настроен на прием двух статических параметров ( user и id ) и одного динамического параметра ( :id ). Синтаксис двоеточия указывает, что обработчик будет ожидать предоставленное пользователем значение. Код этой функции намеренно многословен и для большей ясности прокомментирован.

 print("Searching for user with ID: $id"); 

Сначала сообщение выводится на консоль сервера. Синтаксис $id использует встроенную в Dart функцию интерполяции строк (подробнее об этом позже).

 int index = int.parse(id, onError: (_) => null); 

Затем вы конвертируете входящий id из строки в целое число для использования в качестве индекса списка. int.parse() принимает значение для преобразования и, при необходимости, функцию обратного вызова для обработки любых ошибок синтаксического анализа. onError — это именованный параметр, а обратный вызов — функция жирной стрелки, которая возвращает null . Обратный вызов принимает один параметр, но, поскольку он не используется, по соглашению он имеет псевдоним _ и игнорируется. В случае, если id не может быть проанализирован в допустимое целое число, index будет присвоено возвращаемое значение функции onError , которая в этом случае равна null .

 if (index == null || index < 1 || index > users.length) { return error("Invalid ID"); } 

Если index оказывается недействительным или выходит за пределы диапазона, этот код возвращает объект ошибки с сообщением «Неверный идентификатор», используя вспомогательную функцию error() .

 Map foundUser = users[index - 1]; return success("user", foundUser); 

Если все хорошо, ваш обработчик ищет и возвращает запрошенного пользователя вызывающей стороне. Вспомогательная функция success() создает для вас сообщение Map с типом «user». Полезная нагрузка — это объект Map, содержащий данные пользователя.

В качестве теста направьте ваш браузер по следующему URL:

 http://localhost:8080/user/id/5 

Результатом будет строка в кодировке JSON, содержащая запрошенные пользовательские данные.

Получить пользователя по типу

Добавьте другой обработчик в ваш файл main.dart .

 @Server.Route("/user/type/:type") Map getUsersByType(String type) { print("Searching for users with type: $type"); // find qualifying users List<Map> foundUsers = users.where((Map user) => user['type'] == type).toList(); // check for error if (foundUsers.isEmpty) { return error("Invalid type"); } // return list of users return success("users", foundUsers); } 

Этот маршрут позволит пользователям запрашивать type а не id . Поскольку может быть несколько пользователей данного типа, вам нужно быть готовым вернуть несколько пользователей, если это необходимо.

Чтобы создать список объектов Map пользователя, соответствующих определенному типу пользователя, используйте функцию where() , которая является стандартной частью любого объекта List. Вы передаете ему функцию, которая проводит тест на сохранение для каждого элемента и возвращает true если элемент, который он проверяет, проходит. where() самом деле возвращает Iterable , предок List, поэтому вы преобразуете его в требуемый List с помощью функции toList() . Если пользователи type не найдены, foundUsers будет пустым списком, и в этом случае сервер возвращает объект ошибки.

Протестируйте новый маршрут с соответствующим URL. Объект ответа будет содержать массив JSON с двумя пользовательскими элементами:

 http://localhost:8080/user/type/programmer 

Параметры запроса

Точно так же легко использовать строки запроса и пары ключ / значение, чтобы получить то, что вам нужно от Redstone.

Добавьте этот обработчик маршрута в main.dart .

 @Server.Route("/user/param") Map getUserByIDParam(@Server.QueryParam("id") String userID) { return getUserByID(userID); } 

На этот раз вам нужно аннотировать параметр обработчика userID , чтобы он заполнялся значением параметра запроса с именем id .

 http://localhost:8080/user/param?id=2 

Обслуживание статических страниц

Что, если вы хотите, чтобы ваш сервер Dart выводил статические страницы? С помощью всего лишь нескольких строк кода вы можете получить это тоже.

Во-первых, создайте папку с именем web в качестве одноуровневого элемента в папке bin вашего проекта. Внутри новой папки создайте файл HTML с именем index.html , используя следующий код.

 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>index</title> </head> <body> <p>Hello from index.html!</p> </body> </html> 

Вам нужно еще несколько пакетов из Pub, чтобы сделать это гладко. pubspec.yaml файл pubspec.yaml и сделайте раздел зависимостей следующим образом:

 dependencies: redstone: '>=0.5.21 <0.6.0' shelf_static: '>=0.2.2 <0.3.0' path: '>=1.3.5 <1.4.0' 

Redstone построен поверх Shelf , серверной библиотеки более низкого уровня, созданной и поддерживаемой командой Dart в Google. Это позволяет использовать любое промежуточное ПО Shelf для добавления функциональности на сервер Redstone. Вы также вводите Path, чтобы помочь вам разобрать и манипулировать строками пути.

Sublime должен автоматически использовать Pub для получения новых зависимостей при сохранении pubspec.yaml .

После того, как эти пакеты были загружены в ваш проект, добавьте эти операторы import вверху main.dart .

 import 'dart:io' show Platform; import "package:path/path.dart" as Path; import 'package:shelf_static/shelf_static.dart'; 

Вы импортируете одну из основных библиотек Dart, io , чтобы получить доступ к классу Platform . Ключевое слово show позволяет импортировать только Platform , оставляя все другие функции и классы ввода-вывода вне программы.

Поскольку библиотека Path имеет функции верхнего уровня с общими именами, лучше всего использовать псевдоним, который импортируется как Path .

Добавьте две новые строки в начало main ().

 void main() { String pathToWeb = Path.normalize( "${Path.dirname(Path.fromUri(Platform.script))}/../web" ); Server.setShelfHandler( createStaticHandler(pathToWeb, defaultDocument: "index.html") ); Server.setupConsoleLog(); Server.start(); } 

Вы можете проверить, обслуживается ли index.html, перезапустив приложение сервера Dart и перейдя к корню сервера.

 http://localhost:8080/ 

Я оставлю это в качестве упражнения для читателя, чтобы исследовать Shelf и Path, но мы должны кратко обсудить одну из более полезных функций Dart: интерполяция строк. Вы можете поместить значение выражения в строку, используя ${} . Если выражение является просто идентификатором, вам нужен только $ .

 int myNumber = 5; // 5 is my favorite number String str1 = "$myNumber is my favorite number."; // 5 + 10 = 15 String str2 = "$myNumber + 10 = ${myNumber + 10}"; 

Вывод

В этом уроке я представил фантастическую альтернативу JavaScript, Node и Express на стороне сервера. Dart — более быстрый, современный язык, созданный для масштабирования до миллионов строк кода. Redstone — это всего лишь одна из многих инфраструктур для сервера, которые облегчают вашу жизнь как разработчика, но он среди моих любимых, потому что он широко использует функции аннотации кода Dart для уменьшения количества шаблонов, необходимых для настройки сложных взаимодействий сервера.

Если вы пишете свой код на стороне клиента и с помощью Dart, вы можете обмениваться кодом между клиентом и сервером, и вы получаете выгоду от избежания дорогостоящих переключений контекста, когда базы кода построены на разных языках. Во время разработки вы можете использовать специальный браузер Dartium, позволяющий быстро менять и обновлять рабочий процесс, которым разработчики JavaScript наслаждались годами. Когда весь ваш код на стороне клиента будет готов, с помощью нескольких щелчков (или записей командной строки), dart2js скомпилирует ваш код Dart в JavaScript для всех современных браузеров, сжатых, сцепленных, расшатанных по дереву и готовых к развертыванию.

Присоединяйтесь к дартс стороне.