Подписка на блог

В Телеграме помимо ссылок на заметки делюсь околодизайнерскими наблюдениями.

В Твиттере помимо ссылок на заметки пишу всякую чушь.

В Тумблере и Же-же есть автоматические трансляции. Если не работает, напишите мне: ilyabirman@ilyabirman.ru.

По РСС и Джейсон-фиду трансляции для автоматических читалок

Веб-разработка

Прицеливалка в мобильном веб-инспекторе

Вы, наверное, знаете, что если в Сафари зайти в меню Develop, когда к компьютеру подключен Айфон, то там можно открыть веб-инспектор для любой вкладки, открытой в мобильном Сафари:

Прицеливалка в мобильном веб-инспекторе

Когда наводишь на что-то в веб-инспекторе —

Прицеливалка в мобильном веб-инспекторе

Оно даже честно подсвечивается на телефоне как в обычном окне Сафари:

Прицеливалка в мобильном веб-инспекторе

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

Прицеливалка в мобильном веб-инспекторе

Если на неё нажать (Шифт-Комманд-C), потом можно кликнуть в нужный элемент на самой странице, и тогда в инспекторе покажут именно его.

Кто бы мог подумать, что это работает и в мобильном Сафари? Нужно нажать на прицел на компьютере, а потом тапнуть пальцем в нужный элемент на телефоне — и он выделится в дереве в веб-инспекторе на компьютере!

5 июля   Айфон   веб-разработка   лайфхак   Сафари

Вопрос про фоновую работу ПХП

Эгея написана на ПХП как обычное веб-приложение, отвечающее на запросы браузера к серверу. Браузер просит страницу — Эгея её генерирует и отдаёт.

Часть работы, которую делает Эгея — медленная по своей природе, например создание бекапа или индексация большого блога для поиска. Но Эгея отдаёт страницы быстро.

Это потому что в Эгее реализован механизм фоновой работы через запрос к себе. Когда нужно сделать что-то долгое, Эгея устанавливает ХТТП-соединение сама с собой, как бы делая вид, что она браузер, отправляет запрос по специальному урлу, который означает «сделай бекап» или «поиндексируй поиск», и тут же обрывает соединение. В результате выполнение скрипта заканчивается быстро, а вся долгая работа делается незаметно, как раз в ответ на этот запрос к себе. О таком методе я узнал лет двенадцать назад от Романа Иванова, и с тех пор пользуюсь.

Асинхронное выполнение на ПХП

К сожалению, с этим методом возникла проблема, когда я стал поддерживать ХТТПС. Если просто отправлять запрос и сразу закрывать соединение, как я всегда делал, не происходит вообще ничего — с точки зрения сервера всё выглядит так, как будто к нему и не обращались. Если же попробовать прочитать ответ, то приходит 400 Bad Request, потому что я как бы пытаюсь говорить с ХТТПС-сервером на простом ХТТП. К сожалению, мне почему-то так и не удалось отправить самому себе запрос по ХТТПС с помощью функции fsockopen () и её родственников, хотя я вроде бы исчитал документацию со всех сторон.

Вместо запроса к себе можно использовать register_shutdown_function (). Но я когда-то пользовался ей, и у меня осталось ощущение ненадёжности — кажется, она выполнялась не всегда, и там были какие-то особенности внутри странные, например, что все пути к файлам должны быть указаны абсолютно. Короче, чтобы ей воспользоваться, нужно переструктурировать код. Женя Степанищев ещё рассказал про fastcgi_finish_request () — похожий вариант.

Эти схемы мне не нравятся непрозрачностью: часть скрипта будет выполняться в каком-то мире, который не виден никому. В варианте с запросом к себе мне ничто не мешает зайти браузером по моему служебному урлу и посмотреть, что происходит. А тут придётся что-то специальное изобретать для отладки. Кроме того, запрос к себе, разумеется, запускает новую копию ПХП, в которой заново начинается отсчёт времени. Выполняя такой запрос, я могу теоретически захотеть сделать что-то ещё так же, и инициировать запрос третьго уровня. И все эти запросы — одинаковой природы, просто заход по урлу. Мне нравится эта однородность.

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

Ну и пока умники в твиттере не начали писать «расскажите кто-нибудь Бирману про крон», объясню, что моя задача — сделать максимально автономное решение, не требующее от пользователя никакой специальной настройки сервера. Закачал на сервер папку — всё работает. Если вы матёрый программист в свитере, вам такое не понять.

Короче, в идеале я бы хотел заставить работать свою исходную конфигурацию. Для этого мне нужно научиться делать запрос к себе по ХТТПС. Причём мне нужно просто «потрогать» нужный урл и отвалиться — мне не нужно по нему передавать никаких данных, не нужно читать ответ. Как это сделать?

Ну и если вы знаете какой-то ещё способ добиться нужного результата, лишённый всех описанных недостатков, тоже расскажите.

2017   веб-разработка   вопрос   ПХП   Эгея

Анонс доклада на Питер-ЦСС

Анонс доклада на Питер-ЦСС

16-го июня прочитаю на Питер-ЦСС доклад о декларативных АПИ на английском. Организаторы конференции ещё напоминают, что с завтрашнего дня билет стоит 4500 ₽ (сегодня ещё 4000 ₽).

Предположим, вы сделали какой-нибудь веб-компонент. Например умную выпадайку с автодополнением под названием superComplete. Тогда вы, скорее всего, предложите инициализировать его как-нибудь так:

var superCompleteInstance = new SuperComplete ('#el')

Или, если это плагин для Джейквери, то так:

$ ('#el').superComplete ()

А надо не так. Пусть оно инициализируется само, увидев знакомый класс:

<input id="address" class="supercomplete" />

Я называю это декларативными АПИ: вместо того, чтобы давать команды, ты описываешь свойства компонента параметрами. Научился я такому сто лет назад у Артёма Поликарпова с его Фоторамой, и использую в своих веб-продуктах.

Социокнопки «Лайкли» вставляют на страницу так:

<div class="likely">
  <div class="twitter">Твитнуть</div>
  <div class="facebook">Поделиться</div>
</div>

Чтобы поставить плеер «Жуэль», надо дать класс ссылке на аудиофайл:

<a href="news.mp3" class="jouele">Ilya Birman: News</a>

В «Эмёрдже» чтобы элемент появился только после полной загрузки всего содержимого, а до того отображалась крутилка, нужно завернуть его в такой див:

<div class="emerge" data-spin="true">...</div>

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

Расскажу, почему это клёво и как придумывать синтаксис таких вот декларативных АПИ.

Как сказал Стив Джобс, «Design is how it works». Это доклад про дизайн.

Другие объявленные доклады:

  • Paint the Web with CSS. On Creating Art with Code
  • Creating Magic With Houdini
  • Designing Data-Driven Products. Controlled Chaos and Evolution
  • Chinese Typography on the Web
2017   веб-разработка

Как прикрутить поисковый движок «Роза»

Я уже рассказывал, что в новой Эгее 2.6 — новый поисковый движок «Роза» Романа Парпалака. На примере Эгеи расскажу немного о том, как вы можете использовать его в своих продуктах.

Что делает Роза

Роза — движок поиска по переданным ему данным.

У Розы нет «паука» для индексации. Это специально. В случае с движком блога, например, нет никакого смысла искать ссылки, ходить по страницам, разбирать их код. И ещё убеждаться, что не индексируешь одну и ту же заметку несколько раз, потому что она встретились на страницах разных тегов. Это лишняя работа: приложение-клиент знает, как устроены данные в нём, и может отдавать их поиску сразу «в чистом виде».

Роза понимает три команды: «проиндексируй вот это», «удали из индекса вот это» и «найди всё вот по этому запросу». Как устроен индекс и как она находит — клиенту знать не нужно, это чёрный ящик. Клиент должен только обеспечить Розе доступ к хранилищу, где она будет держать индекс, и своевременно сообщать об изменениях, чтобы этот индекс был актуален.

Таким образом, поиск — это взаимная работа клиента и Розы. Прикрутить её сложнее, чем использовать движок поиска общего назначения. Зато понимание структуры данных позволяет гибко форматировать результаты и учитывать их природу при ранжировании.

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

Как прикрутить поисковый движок «Роза»

Теперь к делу.

Хранилище

Хранилище — это объект, через который Роза взаимодействует с базой данных. Клиенту нужно передавать его Розе и для индексации, и для поиска. Поэтому удобно иметь функцию, которая его отдаёт, когда нужно:

function e2_rose_storage () {
  static $pdostorage = null;

  if ($pdostorage === null) {
    $pdo = new \PDO (
      'mysql:'.
      'host='. $_db_server .';'.
      'dbname='. $_db_name. ';'.
      'charset=utf8',
      $_db_user,
      $_db_password,
    );
    $pdo -> setAttribute (\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
    $pdostorage = new PdoStorage ($pdo);
  }

  return $pdostorage;
}

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

Индексация

Задача клиента — «рассказать» Розе о данных, чтобы она построила свой индекс. Для этого нужно добавить каждую сущность (в Эгее — заметку) в индекс:

$stemmer = new PorterStemmerRussian ();
$indexer = new Indexer (e2_rose_storage (), $stemmer);
$indexable = new Indexable (
  $_note_id,
  $_note_title,
  $_note_text
);
$indexer -> index ($indexable);

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

Стеммер — это объект, к которому обращается Роза, чтобы сравнивать слова в разных падежах и формах. Его нужно передавать при индексации и при поиске. Он обособлен для того, чтобы можно было сделать свой стеммер для другого языка и подключить, не меняя ничего в индексаторе и поисковике.

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

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

Индексация одной заметки — быстрая операция, поэтому поддержание индекса в актуальном состоянии не составляет проблемы.

Но при обновлении Эгеи со старой версии, мне нужно аккуратно проиндексировать все написанные ранее заметки. В случае с моим блогом это около четырёх тысяч заметок. Поэтому просто запустить их индексацию в цикле нельзя: даже если операция уложится во временной лимит ПХП, всё это время пользователь будет думать, что сайт не отвечает. А если не уложится, то пользователь вообще ничего не увидит. Как обеспечить безболезненную фоновую индексацию — тема отдельной заметки, но важно, что это забота клиента, а не Розы.

Когда пользователь удаляет или отзывает заметку, я делаю примерно так:

$stemmer = new PorterStemmerRussian ();
$indexer = new Indexer (e2_rose_storage (), $stemmer);
return $indexer -> removeById ($_note_id);

Поиск

Чтобы искать, нам понадобится файндер (а ему понадобится стеммер):

$stemmer = new PorterStemmerRussian ();
$finder = new Finder (e2_rose_storage (), $stemmer);
$finder -> setHighlightTemplate (
  '<span class="'.CSS_CLASS_HIGHLIGHT.'">%s</span>'
);

Здесь я также настраиваю шаблон для подсветки найденных слов в результатах поиска.

Теперь можно сделать запрос и достать результаты поиска:

$rose_query = new Query ($query);
$rose_query -> setLimit (SEARCH_LIMIT); 
$resultSet = $finder -> find ($rose_query);

Тут я не могу продолжать как ни в чём не бывало и не сделать ремарку. Объекто-ориентированное программирование делает мне больно. Просто так написать нельзя:

$results = rose_find ($db_link, $query, SEARCH_LIMIT);

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

Итак, после того, как мы получили $resultSet, мы уже можем выводить результаты. Но это тот момент, когда можно попросить Розу подкрутить релевантность, что я и делаю:

foreach ($resultSet -> getFoundExternalIds () as $external_id) {
  $note_id = e2_note_id_from_rose_id ($external_id);
  $note_rec = e2_note_by_id ($note_id);
  if ($note_rec['IsFavourite']) {
    $resultSet->setRelevanceRatio ($external_id, 2);
  }
}

Так я поднимаю избранные заметки повыше.

Тут же Розу можно попросить сделать «сниппеты» — так называются фрагменты, выводящиеся в результатах поиска. Роза сама находит предложения с найденными словами, подсвечивает в них эти слова и собирает маленький кусок текста, достаточный для узнавания заметки в выдаче. Это делается так:

$snippetBuilder = new SnippetBuilder ($stemmer);
$snippetBuilder -> setSnippetLineSeparator(' · ');
$snippetBuilder -> attachSnippets ($resultSet, function (array $external_ids) {
  $result = array ();
  foreach ($external_ids as $external_id) {
    $note_id = e2_note_id_from_rose_id ($external_id);
    $note_rec = e2_note_by_id ($note_id);
    $note_pack = e2_package_note ($note_rec);
    $result[$external_id] = $note_pack['text'];
  }
  return $result;
});

Чтобы собрать сниппеты, Розе нужны полные тексты заметок — сама она их не хранит, а о структуре моей базы данных ничего не знает. Поэтому в метод attachSnippets мы тут передаём колбек, который Роза вызывает, чтобы их получить. Он, как видите, вытаскивает заметки из базы.

После этого можно собрать результаты:

foreach ($resultSet -> getItems () as $external_id => $item) {
  $note_id = e2_note_id_from_rose_id ($external_id);
  $note_rec = e2_note_by_id ($note_id);
  $note_pack = e2_package_note ($note_rec);
  $note_pack['title' ] = $item -> getHighlightedTitle ($stemmer);
  $note_pack['text' ] = '<p>'. $item -> getSnippet () .'</p>';
  $note_packs_found[] = $note_packs;
}

Я подсвечиваю найденные слова в заголовке и показываю сниппет вместо текста заметки.

Код выдачи результатов поиска неэлегантен — хотя бы потому, что я трижды прохожусь циклом по этим результатам, многократно вызываю одни и те же функции с одними и теми же параметрами. Никаких измеримых проблем эта неэлегантность не создаёт, а её исправление потребовало бы изменения АПИ Розы. Поэтому пока пусть будет так.

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

Если захотите использовать Розу в своих продуктах, своими мыслями делитесь с Романом, а не со мной.

2017   веб-разработка   программирование   Эгея

Жуэль 2.3

Жуэль 2.2

Жуэль — нормальный аудиоплеер для веба. Его развитием занимается Женя Лазарев.

У нас вышла версия 2.3. Жуэль теперь использует Хоулер (встроен, а не идёт отдельным файлом) вместо Джейплеера. Благодаря этому изменению, трафик расходуется умнее, прелоудер работает точнее и ушло много мелких ошибок. Функция data-repeat стала работать у отдельных треков. Если файл недоступен, Жуэль показывает это, а не «висит». Вёрстка лучше смотрится в разных браузерах.

Пример песенки:

Документацию см. на Гитхабе.

2017   веб-разработка   Жуэль   продукты   проекты   релиз

Интерфейс Мимика 2.0

Веб-приложения состоят из фронтенда (того, что работает у пользователя в браузере) и бэкенда (того, что происходит на сервере). Эти части разрабатывают с разной скоростью. Если при создании браузерной части сервер недоступен, разработчик обычно сильно ограничен.

Я сделал интерфейс для второй версии Мимика — инструмента веб-разработчика для имитации ответов сервера. Можно работать, как будто сервер доступен. Настроить поддельный ответ («мок») очень легко. Например, вы хотите сделать вид, что сервер ответил строчкой джейсона:

Можно настроить навороченные моки, с хитрыми ХТТП-заголовками, задержкой и чем угодно ещё:

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

Подробнее об интерфейсе — на странице проекта.

Что почитать на выходных — 147

Вот:

  1. Redesigning The Paris Metro Map. Костя Коновалов рассказывает в «Смэшинг-магазине». Любимые штуки: 30-градусная сетка без вертикалей, гармония линий, пересадка на «Пастере» и видос в конце.
  2. Could a subtle tweak to Metro’s map fix overcrowding on the Blue Line? Ещё пример, как изменение схемы метро способно разгрузить линию.
  3. Вывеска для Рубинштейна. Оказывается, тоже Сергей Рассказов делал, как и ту крутую вывеску на манеже.
  4. The Web After Tomorrow.
  5. What does cryptoanarchy mean.

Хотите стать спонсором рубрики? Пишите: ilyabirman@ilyabirman.ru

2017   будущее   веб-разработка   метро   Санкт-Петербург   чтиво

Эмуляция медленного интернета на Маке — 2

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

Поэтому теперь другой способ.

Сначала нужно найти файл /etc/pf.conf и сделать его копию. Я её назвал pf-slow.conf. В ней нужно дописать в конце строчку:

dummynet out proto tcp from any to <название домена> pipe 1

В моём случае название домена — e2, потому что я отлаживаю Эгею.

Теперь чтобы соединение с этим доменом тормозило:

sudo dnctl pipe 1 config bw 512Kbit/s delay 300
sudo pfctl -f /etc/pf-slow.conf

Поправьте килобиты по вкусу.

Чтобы работало нормально:

sudo dnctl -q flush
sudo pfctl -f /etc/pf.conf

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

2017   веб-разработка   софт

Что почитать на выходных — 145

Вот:

  1. Developers’ side projects. Джоел Сполски рассказывает, как страшно в Америке программистам жить.
  2. The Case Against Progressive Enhancement’s Flimsy Moral Foundation.
  3. Why time management is ruining our lives. «The better you get at managing time, the less of it you feel that you have». Это да. «Part of the problem is simply that thinking about time encourages clockwatching, which has been repeatedly shown in studies to undermine the quality of work». И это да. Но вот это: «Work expands to fill the time available for its completion» — я не понял, почему проблема. Это подаётся так, будто настоящая цель тайм-менеджмента — максимизация времени безделья.
  4. Apple’s 2016 in review. Очередной разнос Эпла. Что-то последнее время их пишут все кому не лень; этот очень хороший. Про Мак Про (который не обновлялся четыре года): «what they built was a device based around their own ego needs of proving their critics wrong, not a device that served the purposes of their power users». И ещё: «They really miss Katie Cotton, because they seem to have gone tone deaf on how they explain the story of the product». Вообще, увольнение Кейти — самое удивительное решение Эпла за последнее время.
  5. Сначала конструкция, потом сетка. Игорь Штанг.
  6. Великая Отечественная Война. В разное время так назывались разные войны.

Однажды в Википедии: Moving sofa problem

Спасибо спонсору рубрики — рассылке «Дизайнерский дайджест». Каждую пятницу её читатели получают большое структурированное письмо с обзором статей и книг, обучающих материалов, инструментария и вдохновляющих проектов в сфере графического дизайна. Свежий выпуск приходит сразу после подписки, присоединяйтесь.

Что почитать на выходных — 142

Вот:

  1. We’ve updated the radios and checkboxes on GOV.UK. С ума сойти. У дизайнеров британских государственных сайтов есть блог о дизайне британских государственных сайтов.
  2. Переверстка № 14. Кейсы консалтинговой компании.
  3. Активный балласт: Для чего в гонке Vendee Globe используют качающийся киль.
  4. The 100% correct way to do CSS breakpoints.
  5. Рынок. На «Словомании» про рынок, маркет, торг, базар и ярмарку.

Спасибо спонсору рубрики — брокерской компании Нэттрэйдер. Компания открывает индивидуальные инвестиционные счета, по которым государство даёт налоговый вычет. Инвестируйте деньги в акции и облигации, а потом получайте в налоговой 13% от вложений.

2016   веб-разработка   дизайн   паруса   чтиво   языки
Ранее Ctrl + ↓