Архив рубрики: веб

Белое на… Каком?

Малоконтрастный текст (например, светло-серый на белом) плохо читается, как, впрочем, и белый на светло-сером или вовсе голубом. В борьбу за право читать с комфортом даже поисковые системы вступили — насколько помню, и Google, и Bing (про Яндекс не уверен, но возможно) учитывают доступность сайтов при поиске — сайты с хорошей доступностью (в смысле accessibility как возможность воспринять) занимают в нём позиции ближе к началу. Аналогичная ситуация и с мобильной доступностью — вес сайта, скорость загрузки и его отображение на телефонах тоже важны.

Есть разные инструменты для подбора хороших цветов и некоторые совсем рядом — в отладчике браузера — в случае Хрома на Линуксе и Windows он открывается клавишей F12. Отладчик Google Chrome показывает предупреждение, если в паре цветов контраст недостаточен и позволяет исправить цвет текста для соответствия уровням AA и AAA (см. стандарты WCAG 2.0 или ГОСТ Р 52872-2019). Но оно не может подобрать допустимый цвет, когда фон светлый, а текст белый — предлагает лишь поменять белый на точно такой же цвет, не трогая фон. Выход — поменять цвета местами — тогда подбор будет доступен.

Отладчик Google Chrome

Как перенаправить посетителя на HTTPS, если Апач глубоко

Время от времени помогаю знакомым старта́перам в части, относящейся к вебу. В процессе обнаруживаю странное: есть сайты, которыми не сильно можно порулить, даже если и хочется: хостинг-провайдер предоставляет лишь возможность править апачный конфигурационный файл .htaccess. Всё бы ничего, но между файлами и посетителем сайта не только Apache — там ещё и Nginx, чья конфигурация вообще недоступна.

Сейчас в 2023 году редко какой сайт не использует HTTPS (этот — один из них, но сейчас не о нём речь). Если сайт сделан на какой-нибудь CMS, там вполне может существовать возможность указать в настройках основной адрес сайта и включить перенаправление на него — так можно перенаправляться на использование HTTPS и в WordPress, и в Open Journal Systems.

Но что делать, если сайт — статичный? Вроде бы, можно обойтись средствами самого́ Апача, а точнее, его модуля mod_rewrite — напишем в .htaccess:

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{HTTPS} off
  RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>

Этот код включает механизм перенаправлений, проверяет, используется ли HTTPS и если нет — перенаправляет посетителя. Только в нашем случае условие RewriteCond всегда истинно, что вызывает циклические перенаправления. Причина — Apache в конкретном случае работает всегда по HTTP, поэтому значение переменной HTTPS — всегда off. С другими переменными аналогично — например, SERVER_PORT всегда равен 80.

В случае, когда есть доступ к настройкам Nginx, проблем нет — всё настраивается там. Но если нет, приходится искать другие переменные, которые однозначно покажут, как именно посетитель обращается к сайту. Nginx, передавая запрос Апачу, добавляет в него поля, чьи имена начинаются с X-Forwarded — среди них есть и X-Forwarded-Proto, где хранится запрашиваемый посетителем протокол — http или https. В итоге работоспособным оказался такой код:

<IfModule mod_rewrite.c>
  RewriteEngine On
  RewriteCond %{HTTP:X-Forwarded-Proto} !https
  RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
</IfModule>

Может возникнуть вопрос — нельзя ли в условии вместо !https написать более короткое http? Оказалось, что нет — сама по себе строка без знака равенства не проверяется на совпадение, а сопоставляется с образцом, то есть, происходит pattern matching. А такому условию удовлетворяли бы оба рассмотренных значения и условие всегда оказывалось бы истинным, что снова бы вызвало циклические перенаправления.

Узковатое меню

Open Journal Systems 3 вышла уже шесть лет назад — в 2016 году, но за всё это время русское меню, отображаемое администратору в шапке страниц в теме оформления, включенной по умолчанию (Manuscript), слишком узкое:

OJS 3 с узким административным меню

Я этот баг 🐞 видел, но как-то не до него было — я поддерживал старые журналы, сделанные ещё на OJS 2. Сейчас посмотрел — исправляется буквально изменением одного байта — github.com/pkp/ojs/pull/3618

Меню стало чуть шире

Когда пулл-реквест будет принят и это изменение появится в новых версиях OJS, пока не знаю, но кому не терпится, может добавить собственное стилевое правило:

@media (min-width: @screen-desktop) {
    .pkp_navigation_user {
        ul {
            width: 13em;
        }
    }
}

Переводы для темы оформления Manuscript лежат в plugins/themes/default/locale — можно найти слово «Администрирование» на разных языках и сравнить ширину:

grep -R 'msgid "navigation.admin"' -A 1 \
| sort \
| perl -nle 'print qq{<li title="$1">$2</li>} if m{^(.+)/.*msgstr "(.*)"}'
Список переводов
Ширина голубой части — 10em

Оказывается русское слово — самое длинное. И самое широкое, потому что буква и шире, чем i.

Цветной вывод в отладочную консоль браузера

Метод console.log достаточно популярен у пишущих на JavaScript — он позволяет выводить отладочную информацию, справляясь как со скалярными данными, так и с объектами.

Недавно выяснил, что иногда console.log ведёт себя подобно функции printf из си-подобных языков: первый свой аргумент воспринимает как описание формата, где ищет начинающиеся с процента последовательности, а выводимые по описанному формату данные начинаются со второго аргумента. Так, например,

console.log('%d', 77/64)

выведет целое число 1, а

console.log('%f', 77/64)

выдаст 1.203125, что получилось бы и без '%f'. При этом бо́льшая часть знакомых по Си или Перлу форматов вообще не работает — встретив такое, console.log посчитает первый аргумент обычной строкой, которую надо вывести — так

console.log('%x', 48879)

выведет %x 48879 вместо ожидаемого слова beef.

Но зато у console.log нашёлся формат %c, позволяющий применить к выводимому тексту стили, заданные вторым параметром — так можно раскрашивать вывод

Пример использования — codepen.io/jscottsmith/pen/VLzMLo

Скачивание видеороликов с попутным их склеиванием

Если что-то в интернете можно посмотреть, значит, это можно и скачать, хоть это и не всегда удобно.

Вот, например, челябинский провайдер «Интерсвязь» всюду развесил камеры видеонаблюдения да видеодомофоны и даёт возможность своим абонентам смотреть не только то, что происходит прямо сейчас, но и архив за последнюю неделю. При этом архивную запись с общедоступной камеры скачать можно — на странице есть соответствующая кнопка, но вот с камеры домофона — нельзя.

Как известно, если нельзя, но очень хочется, то можно.

Метод

1. Заходим на страницу с архивом, находим нужный фрагмент, открываем отладчик (клавишей F12), переключаемся на вкладку Network — так и в Google Chrome, и в Mozilla Firefox.

2. Запускаем воспроизведение — отладчик покажет выполненные запросы.

3. Когда запросы перестанут идти (то есть, всё нужно, уже скачалось), можно, щёлкнув по ним правой кнопкой, выбрать пункт “Save all as HAR” — запросы и ответы на них будут сохранены единым файлом с расширением HAR — на самом деле это JSON.

В отладчике открыта вкладка Network

4. Берём какой-нибудь HAR-extractor (есть всякие варианты — мне подошёл первый попавшийся, написанный на Node.JS), натравливаем его на HAR-файл, получаем папку, в одной из подпапок которой будут лежать видеофайлы с расширением TS.

5. Смотрим эти видеофайлы — под линуксом с гномом можно использовать стандартный видеоплеер totem или всеядный mpv (ну или mplayer, из которого он и вырос).

6. Создаём текстовый файл, каждая строка которого состоит из слова file, пробела и имени файла в апострофах:

file 'cam42_16-10-2020_26778689266.ts'
file 'cam42_16-10-2020_26778699914.ts'

7. Склеиваем мелкие видеофрагменты в один большой файл:

ffmpeg -f concat -i список.txt -codec copy результат.mp4

Всё!


Аналогичный способ — сохранение кучи мелких файликов через HAR — можно использовать для того, чтоб добыть тайлы — небольшие квадратные картинки из которых состоят интерактивные карты на сайтах, а потом, делая свою карту, разместить эти тайлы где-нибудь у себя, чтоб не создавать лишнюю нагрузку на тайлопровайдера — они этого не любят: OpenStreetMap просит не брать тайлы непосредственно у них, а фонд ВикиМедиа всё время клянчит денег на поддержку функционирования Википедии. Реальный пример такого подхода — карта Global SUSU на сайте Южно-Уральского университета: тайлы там сгенерированы Википедией из общедоступных данных OpenStreetMap, но хранятся на одном из университетских серверов.

Симметричный шестиугольный блок

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

<style>
    .out, .in {
        display: inline-block;
        overflow: hidden;
    }
</style>
<body>
    <div class="out" style="transform: skew(α)">
        <div class="in" style="transform: skew(β)">
            <img src="picture.png" alt="Board" style="transform: skew(γ)">
        </div>
    </div>

где α, β и γ — углы, на которые проводится скос элементов.

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

Для углов α = 20° (20deg в CSS), β = −30°, γ = 40° и картинки, состоящей из квадратов результат будет таким:

Можно заметить, что для возвращения картинки к нормальному положению углы α и γ должны совпадать. Угол β больше, чем α, но не в два раза — меньше. Для того, чтобы углы картинки обрезались симметрично, надо, чтобы тангенсы углов α и β относились как 1 к −2.

Можно CSS-правило трансформации записать в виде матрицы, где указывается тангенс: transform: matrix(1, 0, тангенс, 1, 0, 0) — тогда вместо вычисления углов путём взятия арктангенса, который есть, например, в дополнительных пакетах к Sass, но не в нём самом (и уж тем более не в CSS), можно будет пользоваться простым умножением:

$tan: .5;

.out, .in {
    display: inline-block;
    overflow: hidden;
}

.out, .in img {
    transform: matrix(1, 0, $tan, 1, 0, 0);
}

.in {
    transform: matrix(1, 0, -2*$tan, 1, 0, 0);
}

Результат — группа из стоя́щих друг на друге квадратов режется по диагонали:

Для $tan: .577350269 (tg 30°) углы будут кратны тридцати градусам:

Треугольники

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

Дополнение/: если наружному элементу .out задать нулевую высоту строки line-height: 0, то линии разреза переноситься не будут ни в Хроме, ни в Файрфоксе — станет как надо.

Подсчёт числа DOM-элементов при тестировании через Test::Mojo

Модуль Test::Mojo позволяет проверить, нашлось ли нужное количество DOM-элементов:

$t = $t-&gt;element_count_is('div.foo[x=y]', 5);
$t = $t-&gt;element_count_is('html body div', 30, 'thirty elements');

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

$t-&gt;get_ok("$ROOT/some.xml");
my @items = $t-&gt;tx-&gt;res-&gt;dom-&gt;find('rss channel item')-&gt;each;
ok scalar @items &gt; 42, 'More than 42 items in some.xml';

Правильный тип для офисных файлов

Перловый микрофреймворк Mojolicious::Lite умеет сам выдавать существующие файлы, но при этом не все из них получают верный тип содержимого Content-type: некоторые файлы прикидываются обычными текстовыми (text/plain), другие же вообще не имеют никакого типа — браузер такие файлы может воспринимать как угодно. В состав фреймворка входит модуль Mojolicious::Types, в котором определены MIME-типы для наиболее популярных (most common) расширений файлов:

appcache -> text/cache-manifest
atom     -> application/atom+xml
bin      -> application/octet-stream
css      -> text/css
gif      -> image/gif
gz       -> application/x-gzip
htm      -> text/html
html     -> text/html;charset=UTF-8
ico      -> image/x-icon
jpeg     -> image/jpeg
jpg      -> image/jpeg
js       -> application/javascript
json     -> application/json;charset=UTF-8
mp3      -> audio/mpeg
mp4      -> video/mp4
ogg      -> audio/ogg
ogv      -> video/ogg
pdf      -> application/pdf
png      -> image/png
rss      -> application/rss+xml
svg      -> image/svg+xml
txt      -> text/plain;charset=UTF-8
webm     -> video/webm
woff     -> font/woff
woff2    -> font/woff2
xml      -> application/xml,text/xml
zip      -> application/zip

странно, но среди них нет ни одного, относящегося к офисным пакетам. На сайте Microsoft | Developer можно найти список типов — Office 2007 File Format MIME Types for HTTP Content Streaming. Чтобы не задумываться, какие же из этих типов брать, можно сохранить все — если есть какой-нибудь конфигурационный файл, он вполне подойдёт для хранения такого списка. В моём случае конфигурационные файлы обычно в формате YAML — это удобно.

---
# ... ещё какие-нибудь параметры

# MS Office types
types:
    rtf:    application/rtf
    doc:    application/msword
    dot:    application/msword
    docx:   application/vnd.openxmlformats-officedocument.wordprocessingml.document

Где-нибудь перед запуском приложения надо добавить типы (допустим, конфигурация приложения доступна через $config)

while ( my ( $ext, $type ) = each %{ $config-&gt;{'types'} } ) {
    app-&gt;types-&gt;type($ext =&gt; $type);
}

Правда, при выдаче файла тип его содержимого придётся всё-таки указать явно, иначе Mojolicious вернёт тип только для знакомых файлов, а для офисных будет undef

my $type = $self-&gt;app-&gt;types-&gt;mapping-&gt;{$ext}-&gt;[0];
if (my $asset = $self-&gt;app-&gt;static-&gt;file("/$path/$id.$ext")) {
    $self-&gt;res-&gt;headers-&gt;content_type($type);
    return $self-&gt;reply-&gt;asset($asset);
}
else {
    return $self-&gt;reply-&gt;not_found;
}

Теперь и офисные файлы возвращаются нормально.

Надо ругаться в боевом режиме

В состав перлового микрофреймворка Mojolicious::Lite входит класс Mojo::Log, который не просто пишет сообщения в лог, а ещё и учитывает важность сообщений — в отладочном режиме пишет много, а боевом — мало. Обнаружилось, что сообщения об ошибках имеют тот же класс debug, что и остальные сообщения

[Tue Nov 13 12:25:51 2018] [debug] Format html
[Tue Nov 13 12:25:51 2018] [debug] 200 OK (0.076118s, 13.137/s)
[Tue Nov 13 12:25:53 2018] [debug] GET "/some/url"
[Tue Nov 13 12:25:53 2018] [debug] Template "not_found.development.html.ep" not found
[Tue Nov 13 12:25:53 2018] [debug] Rendering template "not_found.html.ep"
[Tue Nov 13 12:25:53 2018] [debug] Rendering template "layouts/error.html.ep"
[Tue Nov 13 12:25:53 2018] [debug] Rendering cached template "layouts/admin.html.ep"
[Tue Nov 13 12:25:53 2018] [debug] Rendering cached template "search_form_navbar.html.ep"
[Tue Nov 13 12:25:53 2018] [debug] Format html
[Tue Nov 13 12:25:53 2018] [debug] 404 Not Found (0.004841s, 206.569/s)

из-за чего при запуске в продакшне под реальной нагрузкой в лог не попадает почти ничего — например, там нет сообщения об ошибках 404 Not Found.

Возможно, где-нибудь во внутренностях Mojo можно исправить такую ситуацию, но мы пойдём другим путём — применим хук after_dispatch:


hook after_dispatch =&gt; sub {
    my $self = shift;
    my $req  = $self-&gt;req;
    my $res  = $self-&gt;res;

    if ( $res-&gt;code &gt;= 400 ) {
        $self-&gt;app-&gt;log-&gt;error(join ' ',
            '"' . $req-&gt;method . ' ' . $req-&gt;url . '"',
            $res-&gt;code,
            $req-&gt;headers-&gt;referrer || '-',
            '"' . $req-&gt;headers-&gt;user_agent . '"',
        );
    }
};

after_dispatch

Результат — в логе появились сообщения об ошибках:

Sending children hup signal
[Tue Nov 13 12:30:46 2018] [error] "GET /none" 404 - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36"
[Tue Nov 13 12:44:50 2018] [error] "GET /SW/Rplayer.html" 404 - "Mozilla/5.0 (compatible; Linux x86_64; Mail.RU_Bot/2.0; +http://go.mail.ru/help/robots)"