Знакомство с Presto — высокоскоростной веб-фреймворк на Ruby

в 10:17, , рубрики: framework, ruby, метки: ,

Краткий экскурс

Меня часто спрашивают — чего ради ещё один фреймворк если есть Rails.
Я часто отвечаю — затем что поезд не решает те задачи которые решает автомобиль.

Ещё чаще меня спрашивают — чего ради Presto если есть Sinatra.
Также часто я отвечаю — потому что автомобили отличаются в плане скорости, комплектации и удобства.

Как работается?

В плане использования, Presto предлагает в полне стандартную логику создания приложений и такой же стандартный набор инструментов — контролеры, роутинг, редиректы, рендеринг etc.
В основном работается очень легко. Всё организовано натурально и просто. Легко подаётся пониманию.

В чём разница?

Скорость

Из особенностей, в первую очередь стоит отметить высокую скорость обработки запросов.
Эталонная скорость Presto — от 4000 до 5000 запросов в секунду, в зависимости от железа.
Что такое эталонная скорость и в чём она измеряется?
Очень просто — это скорость «Hello World» приложения.

С точки зрения фреймворка, все приложения одинаковы, с единственной разницей — сколько времени приложение тратит на обработку запроса.
И поскольку «Hello World» приложение тратит менее одного процента, эту скорость можно считать эталонной.
А скорость любого приложения это эталонная скорость разделить на время которое приложение тратит на обработку запроса.

Теперь, предположим что ваш сайт обращается к БД и рендерит HTML, тратя при этом 50% времени.
Если вы используете Presto, 50% от 4000-5000 это 2000-2500, так что примерная скорость вашего сайта будет составлять около 2000 запросов в секунду.

Представьте какая скорость у вашего сайта если вы используете фреймворк с эталонной скоростью 100 запросов в секунду.

Slices

Другой важной особенностью являются Slices.
Любой модуль содержавший контролеры можно вмонтировать в любое приложение и будет работать везде одинаково.

Можно один раз написать скажем блог или форум и использовать его на любом Presto сайте.

Дальше — лучше …
Оформив слайс как гем мы получаем распределённый модуль с возможностью синхронизации.

Многоуровневое конфигурирование

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

Так например если ваш форум мигрирует с /forums/ на /forum/, всё что вам нужно сделать это перемонтировать форум слайс под /forum/.

Или же если нужно добавить миддлеваре, или поменять путь к темплэйтам, или даже template engine — всё это делается как на уровне контролера так и на уровне слайса.
Притом что конфиг контролера является персональным и имеет приоритет перед общим слайсовым конфигом.

Api

Ещё стоит отметить что Presto организован в виде множества Api.
В том смысле что инструменты не просто разбросаны внутри контролеров(или даже внутри Object класса!), а организованы по собственным Api.

Всё что связанно с HTTP находиться под http методом — http.params, http.session, http.cookies etc.

Всё что связанно с рендером находиться под view методом — view.render, view.render_partial, view.render_layout etc.

Таким образом, Presto добавляет всего-лищь 3 метода в ваши контролеры — http, view и node.

Sandbox

Другой практичной особенностью является Sandbox.

Sandbox позволяет запретить доступ к session и cookies, а также запретить redirect, forward и halt.
Можно также дать доступ к session и cookies в режиме только чтение.

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

К no-logic templates это конечно не относиться.

Inline Testing

Ну и в конце стоит отметить такую диковинку как Inline Testing.

Суть в том что можно писать тесты «не отходя от кассы»…
Тест для любого акшиона можно писать прямо в контролере, рядом с самим акшионом.

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

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

Приступаем к освоению — Install

Для начала надо установить Ruby версии 1.9.2 или выше.
Или же JRuby 1.9 mode.

Потом в терминале пишем:

$ gem install presto

Presto имеет лишь две зависимости — Rack и Tilt.
Так что процесс установки обычно занимает меньше минуты.

Controllers

Создать контролер в Presto также просто как создать и расширить класс в Ruby.
Надо просто создать класс, включить Presto::Api и адресовать через http.map

class MyController
    include Presto::Api
    http.map
end

Routing

Routing в Presto начинается с http.map

http.map устанавливает корневой адрес для всех акшионов в данном контролере.

В примере выше, http.map вызван без параметров. Это означает что контролер будет обслуживать корневой адрес сайта — /
То есть http.map идентичен http.map "/"

Типичный пример — News обслуживает /news/

class News
    
    include Presto::Api
    http.map :news

    def index
        # some logic
    end

    def edit
        # some logic
    end
end

Теперь News обслуживает 3 адреса:

  • /news/
  • /news/index
  • /news/edit

Но http.map не является окончательным словом в роутинге.
Также как http.map устанавливает корневой адрес для акшионов в данном контролере,
аддрес на котором монтирован слайс устанавливает корневой адрес для контролеров в данном слайсе.

Скажем у нас такой слайс:

module Forum

    class Posts
        include Presto::Api
        http.map :posts
        # actions
    end

    class Users
        include Presto::Api
        http.map :users
        # actions
    end

end

Создаём приложение:

app = Presto::App.new

Монтируем форум в корневой адрес:

app.mount Forum

теперь Forum будет обслуживать все акшионы Posts и Users под /posts/ и /users/ соответственно.

Но мы также можем вмонтировать форумный слайс под любой другой адрес.

app.mount Forum, '/forum'

теперь Forum будет обслуживать все акшионы Posts и Users под /forum/posts/ и /forum/users/ соответственно.

Но и это ещё не всё.
Есть ещё возможность обслуживания множества корневых адресов одним и темже контролером.
Об этом в параграфе Canonical.

Canonical Routing

Допустим ваши новости мигрировали с /Novosti/ на /news/
Но вам нужно ещё какое-то время поддерживать новости на старом адресе.
Можно конечно сделать copy/paste, но в условиях нынешних тенденций это не то не целесообразно — это просто дико.
Presto предлагает решение по элегантнее — canonical routes.
Просто задаём http.map множество параметров, каждый из которых является каноническим адресом,
кроме первого разумеется, который является корневым адресом.

Пример

class News
    
    include Presto::Api
    http.map :news, :Novosti

    def index
        http.canonical?.to_s
    end
end

Теперь News будет обслуживать /Novosti/ также как и /news/

Обратите внимание на def index, он содержит http.canonical?,
который возвращает текущий канонический адрес если запрос сделан с канонического адреса или же nil если запрос сделан с корневого адреса.
В данном контексте, если ввести в браузер /news/, ответом будет пустая строка — http.canonical? возвращает nil потому что /news/ является корневым адресом.
Если же ввести /Novosti/, ответом будет /Novosti/

Сanonical работает как на уровне контролера так и на уровне слайса.

Пример

module Forum

    class Posts
        include Presto::Api
        http.map :posts
        # actions
    end

    class Users
        include Presto::Api
        http.map :users
        # actions
    end

end

app = Presto::App.new
app.mount Forum, '/forum', '/Forums'

Теперь Forum будет обслуживать следующие адреса:

  • /forum/posts/
  • /forum/users/
  • /Forums/posts/
  • /Forums/users/

Наверное вы задались вопросом — зачем же нельзя просто монтировать один и тот же слайс на множество адресов.
Можно, но не целесообразно.
— лишняя писанина.
— лишняя память для обслуживания ещё одного слайса.
— http.canonical? не будет работать так что вы не будете знать находитесь ли вы под рутом или под каноником.

Actions

Добавить акшион также легко как создать очередной метод в Ruby.

Пример: создаём 2 акшиона — index и edit

class MyController
    
    include Presto::Api
    http.map

    def index
        # some logic
    end

    def edit
        # some logic
    end
end

Теперь MyController обслуживает 3 адреса:

  • /
  • /index
  • /edit

Действительно легко, но ведь акшионы не всегда такие тривиальные.
Как например обслуживать такой адрес: /forum/posts/top-100 или /forum/users/online/active-only
Про это детально рассказывается в параграфе Mapping.

Mapping

Идея позаимствована из Ramaze. Я использовал Ramaze фреймворк на протяжении долгово времени и нахожу превосходной идею формирования URL из имён методов.

Например, def edit; end соответствует /edit в строке браузера.
А def users_online; end соответствует /users_online
Но def users__online; end соответствует /users/online
То есть __ преобразовывается в /

По умолчанию, у Presto 3 правила преобразования:

  • "____" => ".",
  • "___" => "-",
  • "__" => "/",

Пример: def users___online; end соответствует /users-online

Пример: def users____html; end соответствует /users.html

Вы конечно можете изменить данные правила или даже добавить новые.
Для этого используется http.path_rules на уровне контролера или слайса.

Пример: преобразовываем "_" в "/" на уровне контролера

class News
    
    include Presto::Api
    http.map :news
    http.path_rules "_" => "/"

    def read_active
    end

end

Теперь read_active будет обслуживать /news/read/active адрес.

Пример: преобразовываем "_" в "-" на уровне слайса

module Ctrl; end

class Ctrl::News
    
    include Presto::Api
    http.map :news

    def search_popular
    end

end

class Ctrl::Articles

    include Presto::Api
    http.map :articles

    def show_latest
    end
end

app = Presto::App.new
app.mount Ctrl do
    http.path_rules "_" => "-"    
end

Теперь Ctrl::News#search_popular будет обслуживать /news/search/popular адрес.
Также как Ctrl::Articles#show_latest будет обслуживать /articles/show/latest адрес.

Хорошо, но это всё статические имена. Как же на счёт динамических параметров?
Например /news/delete/100
Об этом в следующем параграфе.

Parametrization

Параметризация в Presto натуральна и проста.
HTTP параметры становятся аргументами для текущего акшиона.

Допустим у нас такой контролер:

class News
    
    include Presto::Api
    http.map :news

    def edit id
        return "ID Passed: #{id}"
    end
end

Если в браузере набираем /news/edit/100, Presto сделает такой вызов — News#edit( 100 )
и в ответ получим «ID Passed: 100»

Если же набираем /news/edit/, вызов будет таким: News#edit, и так как #edit не может быть вызван без параметров,
в ответ получим ошибку «404, Page Not Found»

Для того чтобы акшион работал и без ID, надо дать параметру значение по умолчанию:

class News
    
    include Presto::Api
    http.map :news

    def edit id = 0
        return "ID Passed: #{id}"
    end
end

Если в браузере набираем /news/edit/, в ответ получим «ID Passed: 0»

Если же набираем /news/edit/100, в ответ получим «ID Passed: 100»

Можно также передать/принимать N-ое количество параметров:

class Users
    
    include Presto::Api
    http.map :users

    def details id, *columns
        return "ID: #{id} | Columns: #{columns.join(', ')} "
    end
end

Если в браузере набираем /users/details/100/name/email/status, в ответ получим «ID: 100 | Columns: name, email, status»

Если же набираем /users/details/100/, в ответ получим «ID: 100 | Columns: „

/users/details/ вернёт ошибку “404, Page Not Found»

То есть если Ruby метод будет работать с данными аргументами, будет работать и HTTP запрос.

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

Alias

Как мы уже говорили, контролеры и слайсы могут обслуживать множество адресов.
Но как же на счёт акшионов? Что если я хочу отображать идентичный контент при запросе разных акшионов.
Всё очень просто — используем http.alias

Пример

class Users
    
    include Presto::Api
    http.map :users

    def details
    end
    http.alias :details, :about, :features
end

Теперь /users/details будет обслуживать 2 дополнительных адреса — /users/about и /users/features

Стандартное «Hello World» приложение

Создаём app.rb file

require 'presto'

class HelloWorld

    include Presto::Api
    http.map
    
    def index
        "Hello World"
    end
end

app = Presto::App.new
app.mount HelloWorld
app.run server: :Thin, Port: 7890

Файл
image

Результат в браузере
image

Тест на производительность
image

На этом заканчиваем первую часть знакомства.

В следующих частях узнаем про rendering, content_type, hooks, cache, middleware, error handling, authentication, sandbox и многое другое.

Автор: slivu

  1. Андрей:

    Пример использования Presto, в настоящее время Espresso – http://espresso.mosalt.ru/

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js