Если вы хотите создать современное веб-приложение, вам обычно нужно использовать несколько разных инструментов: один набор для интерфейса, а другой - для серверной части. Кроме того, вам нужно будет изучить и использовать другой API (и часто даже другой язык программирования) для серверной части. Однако веб-API - это многофункциональный и стабильный API, а веб-браузер - стабильная, мощная и безопасная среда выполнения. Так почему же тогда мы не можем использовать эту среду выполнения и для нашей серверной части? Это вопрос, который мы задали себе в IBM Research, поэтому решили попробовать запустить браузер на сервере. Это сработало намного лучше, чем мы ожидали!

Связь между браузером

Наша первая задача заключалась в следующем: как заставить многие настольные браузеры взаимодействовать с браузером на сервере? Решение, которое мы придумали, простое: запустить обычный веб-сервер и заставить его перенаправлять запросы в серверный браузер (который мы будем называть Execution Engine). , который затем обрабатывает запросы во вкладках (т. е. загружает веб-страницу и запускает main() функцию JavaScript) и возвращает результаты. Мы создали вкладку контроллера, которая работает на механизме выполнения, который общается с веб-сервером с помощью WebSocket, а затем открывает и закрывает (или повторно использует) вкладки по запросу. Эта простая установка была всем, что требовалось для работы системы.

Представление

Первоначально мы беспокоились о том, как это может работать. В конце концов, браузеры созданы для работы на настольных компьютерах и, следовательно, не используют слишком много ресурсов, чтобы остальная часть системы оставалась отзывчивой. Однако на сервере нам нужно максимальное использование оборудования, чтобы мы могли оптимизировать пропускную способность и время отклика. Итак, мы создали экспериментальную концепцию и провели несколько тестов производительности. Мы запустили механизм выполнения в автономном режиме, что делает его более похожим на внутренний сервер. Когда мы увидели время отклика около 20 миллисекунд для полного выполнения функции туда и обратно, наши опасения развеялись! Еще несколько тестов производительности на ноутбуке показали, что в целом производительность примерно в 10 раз лучше *, чем у бессерверной платформы на основе контейнера, выполняющей ту же функцию на той же машине.

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

Удивительные преимущества

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

Теперь мы можем выполнять разработку полного стека, используя только веб-API. Нужно читать / писать сетевые ресурсы? Используйте fetch API. Нужно кэшировать данные? Используйте localStorage. Нужно размыть изображение? Используйте фильтр CSS для тега img. Нужно управлять сеансами? Используйте куки. Нужна многопоточность? Используйте Web Workers. Нужна собственная скорость компиляции (или язык, отличный от JavaScript)? Используйте WebAssembly.

У нас уже есть все инструменты, необходимые для разработки и отладки внутреннего кода на наших локальных машинах для разработки: настольный веб-браузер! Мы можем разрабатывать локально, затем загружать код на сервер, и он просто работает.

Сервер легкий, простой в установке и обслуживании. Выполнение десятков тысяч простых запросов на сервере использует менее 2 ГБ оперативной памяти.

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

У нас есть аппаратное ускорение в виде WebGL (при наличии 3D-видеокарты). Мы можем использовать это с помощью библиотек JavaScript, использующих WebGL, таких как gpu.js или Tensorflow.js.

У нас есть бесплатное распределенное управление пакетами в виде тегов <script> или <link>, которые могут извлекать контент непосредственно из CDN. Благодаря сетям CDN начальная загрузка внешних ресурсов выполняется быстро, и механизмы выполнения затем кэшируют эти ресурсы для последующих вызовов. Поддерживается управление версиями, как и проверка целостности, посредством целостности субресурсов.

Любой современный браузер может работать как механизм выполнения. Мы используем Firefox или Chrome / Chromium, поскольку они поддерживают безголовый режим, и мы можем использовать поддержку Puppeteer на сервере для автоматизации.

Мы можем добавить больше механизмов выполнения к одному экземпляру веб-сервера, просто открыв веб-браузер по URL-адресу сервера. Веб-сервер автоматически распределяет нагрузку между всеми подключенными механизмами выполнения. Мгновенное масштабирование!

Эти механизмы выполнения могут работать где угодно, в том числе внутри нашей частной сети, что позволяет использовать гибридный облачный подход. Мы могли бы даже запускать их на настольных компьютерах / ноутбуках / Raspberry Pi.

Покажи мне код

Как выглядят функции браузера? Первоначально это были полные веб-страницы с вызываемой функцией JavaScript main() и возвращаемым значением, возвращаемым в результате вызова функции. Однако мы хотели упростить эту задачу, поэтому в настоящее время поддерживаем фрагменты HTML и функции чистого JavaScript, которые затем автоматически встраиваются в полную веб-страницу с соответствующим шаблоном HTML5.

Вот базовый и полный пример JavaScript "hello world":

function main() {
    return "Hello, world!";
}

Чтобы запустить эту функцию, загрузите файл функции на существующий сервер функций браузера (или используйте инструменты локальной разработки), а затем выполните функцию, вызвав ее как конечную точку REST:

home$ curl https://server/execute/hello/function.js
Hello, world!
home$

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

Случаи применения

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

  • защитить конфиденциальные учетные данные от клиента, например вход в базу данных или ключи API
  • запустить функцию на триггере, например по расписанию или через Webhooks
  • предоставлять данные частного облака общедоступным, т.е. функция выполняется внутри частной сети
  • обойти ограничения CORS, т.е. прокси-клиенты через сервер
  • использовать функцию не-браузерами, например мобильные приложения, встроенные устройства
  • предварительный рендеринг контента для старых браузеров / встроенных устройств / смарт-часов, например визуализировать SVG, вернуть изображение
  • выполнять ресурсоемкие вычисления на сервере, например Вывод Tensorflow.js или вычисления на GPU.js
  • добавить уровень кэширования к внешнему API с ограниченной скоростью, например твиттер

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

Вот простой пример того, как мы использовали функции браузера в одном из наших проектов. Мы создали статический веб-сайт для продукта, который включал контактную форму. Мы хотели сохранить отправленные контактные формы в нашу базу данных CouchDB. Мы не хотели делать это непосредственно из браузера клиента, потому что нам нужно было бы включить CORS для всех источников в нашей базе данных (снижает безопасность), и нам нужно будет отправить токены аутентификации (с доступом на запись в базу данных) в клиент. Поэтому вместо этого наша форма отправила свои данные в функцию браузера:

<form action="/execute/form_to_couchdb.html" method="POST">
    <input type="text" name="full_name" value="">
    <input type="text" name="email_address" value="">
    <textarea name="message"></textarea>
    <input type="submit" name="submit">
</form>

В нашей функции браузера мы захватили отправленные данные формы и разместили их в новом документе в CouchDB, используя его REST API, как показано ниже:

Конечный результат был прост и очень быстро реализован. Кроме того, мы реализовали ежедневный дайджест электронной почты с отправленными контактными данными, который представляет собой функцию браузера, которую мы вызывали из cron задания (то есть по таймеру). Эта функция загрузила документы, сохраненные с момента последнего прогона дайджеста из CouchDB, создала DOM-рендеринг контента, подходящего для электронной почты, захватила innerHTML этого рендеринга и отправила его по электронной почте с помощью службы электронной почты.

Попробуйте и внесите свой вклад

Мы считаем, что едва прикоснулись к тому, что возможно при использовании веб-API в серверной части. Например, нам еще предстоит изучить, как мы можем использовать такие API, как WebAssembly, WebRTC, WebUSB, WebSocket или многие другие возможности веб-браузера; как расширения / надстройки браузера или даже Puppeteer можно использовать в качестве механизма для управления работающими вкладками рабочих (ограничения ЦП, ограничения ОЗУ, выставление счетов и т. д.); или оптимизация производительности с помощью настроек браузера, переключателей командной строки или пользовательских сборок браузера. Вот почему мы открыли исходный код функций браузера, чтобы вы могли вместе с нами участвовать в изучении и расширении этой платформы.

Исходный код и документация доступны по адресу: https://github.com/IBM/browser-functions

ПРИМЕЧАНИЕ. URL-адреса, использованные в демонстрационных видео выше, не являются общедоступными.