В этой статье я бы хотел рассказать о своем фреймворке, который я нескромно назвал Fuga Framework





Лирическое вступление


Когда я учился на втором курсе, меня пригласили разработать веб-приложение для одной лаборатории кафедры теоретической и экспериментальной физики. Само приложение было довольно простым, но имело несколько необычных возможностей. Первым делом надо было выбрать на каком языке и с помощью каких технологий начать разработку этого самого приложения. Не долго думая, я решил все сделать на PHP без использования какого-либо фреймворка. Конечно, я рассматривал все известные мне варианты, но приложение тогда казалось простым и мне не хотелось сильно заморачиваться. Через неделю я уже закончил предварительную версию приложения.

Результат работал. Но через некоторое время у меня появилось чувство неудовлетворенности и обеспокоенности, что в любой момент все может сломаться. И не зря. Когда приложение было размещено на хостинге, накопилось очень много багов. Начиная исправлять эти нелепые ошибки, я понял, что надо было все-таки воспользоваться каким-нибудь фреймворком.

В тот момент у нас начался курс «Программирование на Java», где для получения экзамена нужно было в конце семестра представить любой проект сделанный на этом языке. И у меня появилась идея сделать Java-микрофреймворк. Поскольку мне все еще нужно было переделывать корявое приложение для лаборатории, я весь семестр делал микрофреймворк именно для этого приложения. При создании этого фреймворка, я вдохновлялся популярным Play Framework’ом. В итоге у меня получилось рабочее веб-приложение для лаборатории, которое мало того было написано на Java, но оказалось гораздо стабильнее и имело возможность легко расширяться. Моему «программистскому» счастью не было предела. Вдохновившись написанием фреймворков, я его отделил от самого приложения, сделал рефакторинг и начал расширять всякими возможностями. Этот фреймворк я удачно использовал в одном учебном и еще в двух личных проектах, а также я получил несколько хороших отзывов от некоторых опытных разработчиков.

Начало


Fuga Framework — встраиваемый веб-фреймворк, требующий в зависимостях только Netty и log4j. Это позволяет использовать фреймворк не только для создания классических веб-приложении, но и встраивать его в другие приложения для простого создания веб-интерфейса. На данный момент Fuga Framework включает в себя HTTP сервер, роутер и шаблонизатор.

Любое приложение использующее Fuga Framework должно иметь как минимум три файла:
  • Главный класс
  • Класс-контроллер
  • План маршрутизации

В главном классе мы настраиваем приложение и запускаем веб-сервер:

package com.example;

import com.showvars.fugaframework.FugaApp;

public class HelloWorldApp extends FugaApp {

    @Override
    public void prepare() {
        getRouter().loadFromResources("/routes/helloworld.routesmap");
    }

    public static void main(String[] args) throws Exception {
        new HelloWorldApp().start();
    }
}

Здесь мы производим лишь настройку маршрутизатора с помощью специального файла. Вот его пример:

use com.example.controllers

GET $/ HelloWorldController.index()

В первой строчке мы сделали импорт пакета, что в дальнейшем позволит не писать полный путь с именем пакета к классам-контроллерам. В следующей строчке мы указываем маршрутизатору, что любой запрос типа GET, удовлетворяющий регулярному выражению /, нужно направить на метод index() в классе HelloWorldController. Если вы сталкивались с Play Framework, то можете заметить схожесть этого файла маршрутизации с подобным файлом в Play Framework. Но на самом деле, в Fuga Framework маршрутизатор можно настроить гораздо гибче, используя дополнительные конструкции, о которых я расскажу позже.

Итак, в файле плана маршрутизации мы направили запрос в метод index(), находящийся в классе HelloWorldController. Давайте создадим его:

package com.example.controllers;

import com.showvars.fugaframework.foundation.*

public class HelloWorldController extends Controller {

    public static Response index(Context ctx) throws Exception {
        return ok("Привет мир!");
    }
}

Любой метод, который вызывает маршрутизатор, должен быть статический и обязательно принимать первым аргументом объект типа Context. Этот объект хранит в себе ссылки на текущий запрос, включая куки и сессии, а также ссылку на инстанс сервера. Из класса Controller мы только наследуем для удобства статические методы, такие как ok(), proceed(), notFound(), redirect() и т.п.

Готово! Теперь можно запустить приложение и открыть браузер: http://localhost:8080/

Настройка


Все настройки веб-сервера, шаблонизатора и всего остального проводится методом set() объекта Configuration. Этот объект можно получить с помощью геттера в методе prepare(), либо из объекта Context в контроллере.

Маршрутизатор


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

В регулярных выражениях можно использовать capture groups, а их значения передавать в контроллер:

GET $/(.*) com.example.ExampleController.index(1)

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

GET $/(\d+) com.example.ExampleController.index(1:int, "3.14":double, "some string")

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

com.example.ExampleController.index()
GET com.example.ExampleController.index()
GET $/hello com.example.ExampleController.index()
$/hello GET com.example.ExampleController.index()

Один маршрут может направить запрос на несколько контроллеров последовательно:

GET $/(\d+) {
    HelloWorldController.check(1:int)
    HelloWorldController.index()
}

В случае если контроллер вернул значение proceed() или null, то маршрутизатор попытается выполнить следующий контроллер в списке.

Вложенные маршруты:

$/page/(\d*) {
    GET  $/page/(\d+) HelloWorldController.get(1:int)
    POST $/page/(\d+) HelloWorldController.post(1:int)
    GET {
        HelloWorldController.check(0)
        HelloWorldController.index()
    }
}

Можно использовать предопределенные методы для быстрой отдачи статики пользователю:

GET $/(.*) asset(1:String)
GET $/ view("index.html")

Шаблонизатор


В Fuga Framework шаблонизатор основан на JavaScript движке Mozilla Rhino, который встроен по умолчанию в JRE 7 и выше.

Для вывода используется тег {# <выражение> #}, куда вставляется выражение на языке JavaScript. А для вставки простого кода аналогично используется тег {% <код> %}. Также есть возможность расширения других шаблонов конструкциями вида:

@extend base.html

{% block content %}
<h2>Привет мир!</h2>
{% endblock %}

Чтобы вывести шаблон пользователю, нужно в контроллере использовать метод view() из класса Controller:

return ok(view("index.html"));

либо непосредственно в плане маршрутизации написать view() вместо контроллера:

GET $/ view("index.html")

По умолчанию шаблонизатор компилирует и кэширует шаблон один раз при первом его вызове. Но изменив параметр fuga.templates.alwaysrecompile можно добиться того, что каждый раз шаблон будет скомпилирован заново. Это позволит быстро править шаблон без перезапуска сервера.

При написании шаблонов в нашем распоряжении не только язык JavaScript. Поскольку движок Mozilla Rhino позволяет получить доступ ко всем Java классам, то мы получаем всю стандартную библиотеку, а также объекты классов Context и TemplateApi. Первый идентичен тому, что подается на вход контроллеру, а второй является классом, имеющий набор полезных функций, таких как asset(), escape() и т.п. Пример:

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html" />
        <link href="{# api.asset('css/common.css') #}" rel="stylesheet" type="text/css" />
    </head>
    {% var session = context.getSession(); %}
    <body>
        <div id="content">
           <h3>Добро пожаловать, {# session.getString('user') #}</h3>
           {% block content %}{% endblock %} 
        </div>
        <script src="{# api.asset('js/common.js') #}"></script>
        {% block js %}{% endblock %}
    </body>
</html>

Заключение


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

GitHub: https://github.com/IntCode/Fuga-Framework

Комментарии (14)


  1. Encircled
    28.08.2015 11:32
    +1

    Если честно, не совсем понятно что дает Ваш фреймворк в сравнении даже с JSP


  1. vaniaPooh
    28.08.2015 12:05
    +2

    Мне не очень нравится, что используется свой формат описания маршрутов. Это скорее всего означает, что в любой IDE или редакторе оно будет выглядеть как обычный текст даже без подсветки синтаксиса, не говоря уже о проверке ошибок. Если сделать описание маршрутов на каком-нибудь языке программирования (например, Java или Groovy), то хотя бы при ошибке в конфигурации не будет компилироваться.


    1. Showvars
      28.08.2015 13:26

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

      Согласен, с этим нужно что-то делать. Например, я планировал создать расширение для NetBeans. Учитывая что синтаксис довольно простой, это вполне реализуемо.

      Если сделать описание маршрутов на каком-нибудь языке программирования (например, Java или Groovy), то хотя бы при ошибке в конфигурации не будет компилироваться.

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


      1. Moxa
        28.08.2015 13:36

        я в своем фреймворке сделал так

        server.getUrlMapping()
                .append("/", (request, response) -> response.setBody("It's alive!"));
                .append("/books/$author/$bookName?", BooksController.class);
        


      1. vaniaPooh
        28.08.2015 16:27

        Посмотрите на Spark Framework. sparkjava.com/documentation.html


      1. ozver
        29.08.2015 08:11

        YAML?


  1. Moxa
    28.08.2015 13:17

    keep-alive что-то не хочет работать с ApacheBench


  1. ruslanys
    28.08.2015 19:40
    +1

    Очередной велосипед.


  1. ruslanys
    28.08.2015 20:36
    -1

    Глянул твой возраст, все встало на свои места.

    Хочу дать пару наставлений от чистого сердца.
    Во-первых у твоего фреймворка нет шансов, смирись. В свое время, подобным занимался чуть ли не каждый. Но поверь, в мире Java уже масса прекрасных фреймворков, проверенных миллионами программистов и испробованных в куче решений. Не трать на это время!
    Займись изучением популярных фреймворков: Spring, Struts, Play, JSF. Я бы выбрал Spring.
    Трать время на изучение технологий, которые либо помогут тебе найти работу, либо принесут деньги. Так, например, разработка Android приложений может приносить хороший пассивный доход, а изучение тех фреймворков, которые я указал — поможет найти работу.

    Успехов!


    1. SirEdvin
      29.08.2015 02:27
      +2

      У вас есть какие-то аргументы, почему нет?
      Потому что уже есть альтернативы? Другим же это не помешало.


      1. ruslanys
        29.08.2015 12:17
        +2

        Конечно есть, самое страшное, что их полно. Боюсь, что перечислю лишь малую часть имеющихся.
        1. На текущий момент полно фреймворков великолепного качества, с которыми невозможно соревноваться.
        2. Фреймворк необходимо поддерживать. Так, например, Pivotal дотирует Spring, разработкой которого занимается целое сообщество программистов по всему миру (https://spring.io/team)
        3. Фреймворк — это не только MVC и шаблонизатор, это целая инфраструктура, разработка которой может занять очень и очень много ресурсов.
        4. Какой смысл изобретать велосипед? Тем более, изобретать свои стандарты. Маппинг урлов меня вообще убил. Гляньте хотя бы Java стандарты, прежде, чем что-то придумать. (JSR 311).
        5. При поиске работы 99%, что наличие в разработке собственного фреймворка вам абсолютно не пригодится и придётся изучать фреймворк, поддерживаемый сообществом программистов и проверенный годами.
        6. Чтобы писать фреймворк, надо хотя бы примерно понимать как устроены все остальные. В чем главное отличие твоего и чем твой должен быть лучше. Сейчас я не увидел ничего абсолютно.

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

        Ну вот, хотел дать совет, а выгляжу опять занудой…


      1. ruslanys
        29.08.2015 12:27
        +1

        7. Документация и переносимость. После того, как вы перестаньте поддерживать проект на вашем фреймворке, скорее всего, следующий программист перепишет его полностью.
        8. Аргументы использовать именно этот фреймворк?
        9. Нужно иметь богатый опыт программирования, именно опыт, чтобы заниматься подобным. Сейчас я увидел фреймворк, разработанный от незнания других.

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


        1. Encircled
          29.08.2015 12:39
          +3

          Солидарен. Единственная польза от подобного рода занятий это получения хоть какого-то опыта и обучение на собственных ошибках


        1. Moxa
          29.08.2015 12:51

          Американский форум. Задаёшь вопрос, потом тебе отвечают.
          Израильский форум. Задаёшь вопрос, потом тебе задают вопрос.
          Русский форум. Задаёшь вопрос, потом тебе долго рассказывают, какой ты мудак.