Snap Framework 0.10
Snap Framework Logo

Пришла хорошая новость от бойцов команды Snap Framework — новая версия их фреймворка. Плюс у них обновился и движок шаблонов Heist. Вернее даже не так. Вышел новый Heist, а Snap обновили, чтобы поддержать нововведения Heist. Как утверждают разработчики, скорость работы шаблонизатора возросла в какое-то невероятное число раз: от 700 на простых шаблонах и до 3000+ на сложных. Причем это достигается за счет более сложных алгоритмов прекомпиляции и отказа от некоторых динамических структур.

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

Хранение сессии на клиенте

Как вы, должно быть, знаете, хранить пользовательские данные на клиентской стороне плохо: их ведь может подделать злостный хакер. Но сегодня я вам расскажу, как можно безопасно хранить сессионные данные, не волнуясь за их сохранность. Расскажу на примере Snap Framework, на котором и написан этот блог.

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

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

Skein — это просто хороший алгоритм хеширования. К сожалению, он не победил в конкуре на замену SHA-2, и название SHA-3 получил другой алгоритм. Поэтому самое время заменить Skein на новоявленный SHA-3 — Keccak.

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

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

Хозяйке на заметку: чтобы вашу сессию нельзя было увести с помощью JavaScript, добавьте в заголовок Set-Cookie параметр HttpOnly. Куки с таким параметром не появляются в document.cookie.

Осталась одна загадка, связанная с моим сайтом. Кука _session передается как сессионная и должна удаляться с закрытием браузера. Так и происходит в Opera, но не Google Chrome и Firefox. Может есть какая-то стандартизированная или неофициальная фича, которая не дает сессионным кукам удалиться?

Скоро!

Во всех известных поисковиках!

Все ваши комментарии!

Да, именно так, я сделал синхронизацию комментариев с Disqus. Теперь, даже если в вашем браузере отключен JavaScript, вы всё равно сможете прочитать всё.

Могу с уверенностью сказать, что Haskell — не самый подходящий язык для подобных вещей. На каком-нибудь php всё это можно было бы реализовать гораздо быстрее, но мы ведь не ищем легких путей. Самая большая проблема — работа с JSON. Библиотека для работы есть, и она неплохо реализована, но сказывается специфика Haskell и его статической типизации. Ну например,

{
  "code":0,
  "response":[
    {
      "forum":"dikmax",
      "parent":667510883,
      "author":{
        "username":"google-1b379da98470c64a0c4bc8c2230d616d",
        "about":"",
        "name":"...",
        "url":"",
        "joinedAt":"2012-09-25T03:14:42",
        "avatar":{
          "permalink":"...",
          ...
        },
        ...
      },
      "message":"...",
      ...
    },
    ...
  ]
}

Приблизительный процесс разбора:

  1. Что делать, если результат не является правильным JSON.
  2. Что делать, если корневой узел — не объект.
  3. Что делать, если у объекта нет свойства code.
  4. Если code — не является числом.
  5. Если code всё-таки число, но не 0.
  6. Если у объекта нет свойства response.
  7. Если response — не массив.
  8. Преобазовываем response в два массива: первый с комментариями, второй с ошибками разбора комментариев.

И когда какого-то «если» нет, компилятор выдаст ошибку и придется искать, какой вариант ты еще не предусмотрел. Т.е. компилятор заботится, чтобы твоя программа умела работать при любых данных. И это, конечно, огромный плюс программирования на Haskell.

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

Обновление блога

Развитие блога не прекращается ни на минуту (ну почти). На этот раз я сделал meta-теги в заголовке страницы для Facebook. Теперь, если добавить ссылку на мой блог, то в блок в Facebook попадет начало поста. Кстати, совершенно внезапно, вконтактик тоже правильно стал отображать описание, что не может не радовать.

В очередной раз впечатлился выразительностью Хаскеля: cколько всего можно написать одной строчкой. Вот, например, кусочек из свеженаписанного:

getDescription :: Node -> Text
getDescription = maybe emptyDescription 
  (until (not . T.null) (\_ -> emptyDescription) . getDescription') . 
  findChild (checkMainDiv . current) . fromNode

getDescription' :: Cursor -> Text
getDescription' = cutDescription . transformDescription .
  T.intercalate " " . map nodeText . filter checkParagraph .
  maybe [] siblings . firstChild

Можете сравнить с примерной калькой на JavaScript:

var getDescription_ = function (cursor) {
    var fc = firstChild(cursor)
    return cutDescription(transformDescription(
        (fc ? siblings(fc) : []).filter(checkParagraph).map(nodeText).join(" ")
    ));
}
var getDescription = function (node) {
    var mainDiv = findChild(function (cursor) {
        return checkMainDiv(current(cursor))
    }, fromNode(node));
    return mainDiv ? getDescription_(mainDiv) || emptyDescription : emptyDescription;
}

Хотя да, и в JS есть своя прелесть.

Обновление блога

Совершенно внезапно наш блог постигло самое масштабное внутреннее обновление со времен его создания. Я сделал то, что давно собирался: написал полностью свой рендерер для постов. Теперь я полностью отвечаю за внешний вид постов, а не какой-то там Text.Pandoc.Writers.HTML.

Теперь я могу реализовывать (и уже начал) всякие специальные прикольные штуки, например, другие сноски. Они теперь отображаются не внизу поста, а прямо в нужном месте при нажатии на соответсвующую ссылку мышкой1.

Самая жесть во всем написанном коде — это обработка raw-данных. Как вы, возможно, знаете, в markdown можно вставлять голый html, не обязательно валидный. А Pandoc, парсер для markdown, позволяет еще и смешивать raw-данные и специальную размету. Например, из такого текста:

<span>*Текст*</span>

будет получен следующий результат:

<span><em>Текст</em></span>

Добавим к этому, что используемый шаблонизатор heist оперирует только валидными структурами. Вот и получаем, что блоки, которые содержат raw-данные, сначала собираются в виде html-строки, а затем парсятся в валидную структуру для шаблонизатора. В то же время для любых других блоков стуктуру для heist можно генерировать напрямую.

В любом случае смотрим, изучаем, находим ошибки и сообщаем мне о них.


  1. Да-да, вот прямо как сейчас.

← СтаршеМоложе →