Разработка, HowTo

PHP: дело о загадачном пробеле

%d0%b2%d1%8b%d0%b4%d0%b5%d0%bb%d0%b5%d0%bd%d0%b8%d0%b5_103Или история о том, как побились картинки.

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

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

Чтож. Очень вероятно, что какой-то вредный скрипт оказался с пробелом. Мы же помним вро стандартные открывающиеся и закрывающиеся теги php, которые принесли много головной боли.

Но как среди тысяч строк кода выяснить, где начинается вывод злобного пробела?

  1. отключить вывод самой картинки и убедится, что пробел все еще выводится.
  2. отключить буферизацию вывода на стороне сервера — убедится, что пробел есть
  3. в самом конце скрипта поставить setcookie или header — в логах пояится сообщение вида «Warning: Cannot modify header information — headers already sent by (output started at file.php:XXX)». Где file.php — это соответсвенно файл, а XXX — номер строки, где начался вывод.

Чтобы отключить буферизацию нужно прописать в конфигах php.ini

output_buffering=0

Или в конфигах апача или htaccess

php_flag "output_buffering" Off
HowTo, linux

OpenWRT: блокировка рекламы

1280px-openwrt_logo-svgЕсть отличная прошивка на базе всеми любимого линупса для всякой разной техники вроде роутеров и микрокомпьютеров — это OpenWRT.

А поскольку эта штука работает в качестве локального днс-сервера, то грех не научить ее блокировать на корню всякие даблклик.нет и другое непотребство.

Рассматривать будем ситуацию при которой у нас установлена дефолтная конфигурация c dnsmasq.

Сначала нам потребуется поставить пакет dnsmasq-full взамен стандартного (он чуть больше по объему и тянет больше зависимостей, и предоставляет больше возможностей по настройке).

# opkg update
# opkg remove dnsmasq
# opkg install dnsmasq-full
# /etc/init.d/dnsmasq restart

Посмотрим на файл /tmp/etc/dnsmasq.conf. Нас будут интересовать следующие два параметра.

addn-hosts=/tmp/hosts
conf-dir=/tmp/dnsmasq.d

Буквально это значит, что дополнительные файлы в формате /etc/hosts хранятся в /tmp/hosts, а дополнительные конфиги — в /tmp/dnsmasq.d.

Теперь нам потребуется скрипт, который будет скачивать листы. Поместим его в /root/bin/adblock.sh

#!/bin/sh
echo "download adblock rules"

if [ ! -d /tmp/dnsmasq.d ]; then
 mkdir /tmp/dnsmasq.d
fi

if [ ! -d /tmp/hosts ]; then
 mkdir /tmp/hosts
fi

touch /tmp/dnsmasq.d/adblock.conf

wget -O /tmp/dnsmasq.d/adblock.conf "http://pgl.yoyo.org/adservers/serverlist.php?hostformat=dnsmasq&showintro=0&mimetype=plaintext"

echo "" > /tmp/hosts/adblock

wget -O /tmp/adblock https://hosts-file.net/ad_servers.txt
sed 's/^\(.*\).$/\1/' /tmp/adblock >> /tmp/hosts/adblock
wget -O /tmp/adblock https://adaway.org/hosts.txt
sed 's/^\(.*\).$/\1/' /tmp/adblock >> /tmp/hosts/adblock
wget -O /tmp/adblock http://winhelp2002.mvps.org/hosts.txt
sed 's/^\(.*\).$/\1/' /tmp/adblock >> /tmp/hosts/adblock

if [ -f /tmp/adblock ]; then
 rm /tmp/adblock
fi

/etc/init.d/dnsmasq enabled && /etc/init.d/dnsmasq restart

Первым мы скачиваем файл с блокировками в формате dnsmasq, а последующие запросы — это блокировки в формате hosts. Соответственно раскладываем их по разным папкам и рестартим сервис.

Не забываем поставить +x на файл.

Теперь нужно добавить этот скрипт в крон дабы он обновлялся.

 # crontab -e

и пишем туда что-то вроде

* */12 * * *  /bin/sh /root/bin/adblock.sh

Это означает, что раз в 12 часов списки будут обновляться.

Естественно, что надо включить крон. По дефолту он выключен.

# /etc/init.d/cron enable
# /etc/init.d/cron enabled && /etc/init.d/cron start

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

А для этого нам потребуется создать файл /etc/hotplug.d/iface/50-adblock примерно следующего вида.

#!/bin/sh
[ ifup = "$ACTION" -a "$DEVICE" = eth1 ] && {
	/bin/sh /root/bin/update_adblock.sh
}

Где eth1 — это ваш wan-интерфейс.

Все. Можно перезагрузить роутер для проверки что все настроено корректно.

Рекламные домены вроде doubleclick.de должны резолвиться либо на 0.0.0.0, либо на 127.0.0.1.

$ nslookup  doubleclick.de
Server:		192.168.0.1
Address:	192.168.0.1#53

Name:	doubleclick.de
Address: 127.0.0.1

Чтобы точно убедиться, что все работает как надо — проверяйте что сразу после поднятия wan появились файлы /tmp/dnsmasq.d/adblock.conf и /tmp/hosts/adblock.

Учтите, что в сумме размер списков блокировки составит более 100 тысяч записей. И если роутер слабоват, то придется какой-то из списков отключать.

И да — это не панацея. Это просто хороший способ обезопасить электронные читалки, телефоны и разную мелкую технику, которая ходит в инет от баннеров и прочего добра.

Источники:

Обработка изображений, Разработка

JavaScript: копируем img в canvas

Существует несколько способов скопировать имеющееся на странице изображение (img) в canvas. Конечно все они сводятся к простейшему drawImage, но каждый из этих методов применим в своем контексте.

Первый и самый очевидный (а еще универсальный) — загрузить изображение при помощи объекта Image. Он работает только в случае, когда у img есть атрибут src.

var img = new Image();
var srcImg = document.getElementById('src-img');
img.onload = function() {
    // создаем (или получаем) канву
    var backCanvas = document.getElementById('dest-canvas');
    backCanvas.width = this.width;
    backCanvas.height = this.height;
    var backCtx = backCanvas.getContext('2d');

    // рисуем
    backCtx.drawImage(this, 0,0);
};
img.setAttribute('src', srcImg.src);

Недостаток — еще один запрос в сеть.

Второй способ — менее универсален. Картинка у нас уже есть — почему ее нельзя нарисовать на канве?

var backCanvas = document.getElementById('dest-canvas');
var srcImg = document.getElementById('src-img');
backCanvas.width = srcImg.naturalWidth;
backCanvas.height = srcImg.naturalHeight;

var backCtx = backCanvas.getContext('2d');

// рисуем картинку
backCtx.drawImage(srcImg, 0,0);

И вот тут мы используем naturalWidth/naturalHeight. Зачем?

Все очень просто. Если в первом случае width и height означали размеры картинки, то во втором — это будут физические размеры элемента img на странице. И если картинка внутри него будет отмасштабирована, то мы мы скопируем только часть картинки размером width на height.

Как на иллюстрации ниже (картинка там имеет разрешение в десять раз больше нежели физические размеры img и масштабируется).

Выделение_001

А свойства naturalWidth/naturalHeight не поддерживаются в ряде браузеров. поэтому способ универсальным назвать нельзя.

linux

Xmonad: Фиксим менюшки у saleae logic

2016-06-22-22:49:47_409x324 Есть довольно хороший бюджетный логический анализатор от saleae (и масса совместимых китайских клонов).

Что примечательно — это то, что ребята из этой конторы написали софт под все платформы (и не забыли линупс конечно же).

Софт написан на qt. И выглядит как бы совсем нестандартно.

В обычных DE с ним все нормально, но во всяких WM наступают грабли из-за нестандартной реализации менюшек. В частности очень сильно страдает xmonad. В нем при попытке открыть менюшки появляется цветная рамка и меню пропадает.

Фикс проблемы есть у саппорта saleae — нужно заигнорить окна с определенным классом добавив соответствующее правило к оконным хукам.

className =? "Logic" --> doIgnore
Разработка, HowTo

PHP: array_map и ключи массива

Интересно, почему такой вопрос часто всплывает на форумах или где-то еще?

Сначала постараюсь ответить на вопрос «нафига?», а потом на вопрос «как?».

Мы привыкли, что в в питоне, js и множестве других языков и фреймворков есть функция map, которая применяет некоторую функцию-обработчик к каждому элементу в списке данных и возвращающую результат в том же порядке. Пруф.

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

Этот постыдный момент из жизни структуры привел к тому, что одним из самых частых применений массивов (в пхп конечно же) стало создание отображений категория=>какое-то значение.

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

Т.е. показать пользователю сам параметр, а скобках его категорию.

Как мы делаем это в языках (фреймворках), которые разделяют понятие массива и хеш-таблицы? Перебираем объект, который содержит категории и формируем по нему массив выводимых данных. Наверное так.

И тут мы открываем руководство по php и видим, что она применяет некий колбек ко всем элементам массива и после этого возвращает новый массив.

Удобно же! Применили колбек к массиву и получили обработанный.

$array = [
  'category1' => 'first category',
  'category2' => 'second category',
];

var_dump(array_map(function(....

И что-то не заладилось.🙂 Ключи в колбек не попадают. Можно сделать все через foreach, но тогда нам потребуется еще одна переменная. А тут все было просто и наглядно.

И мы идем в гугель: «php array_map with keys».

А зачем гуглить-то?

$array = [
  'category1' => 'first category',
  'category2' => 'second category',
];

var_dump(array_map(function($key, $value) {
  return "{$key} => {$value}";
}, array_keys($array), $array));

И все отлично работает. И притом правильно. Относительно конечно же. И никаких вам лишних переменных.

А тем временем в коде вновь и вновь появляются конструкции вида

$array = [
  'category1' => 'first category',
  'category2' => 'second category',
];

$description = [];

foreach ($array as $key => $value) {
  $description[] = "{$key} => {$value}";
}

Или еще хуже.

$array = [
  'category1' => 'first category',
  'category2' => 'second category',
];

$description = [];

array_walk($array, function($value, $key) {
  $description[] = "{$key} => {$value}";
});

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

Вариант же с array_walk на понятность никак не претендует (как и решение подобной задачи при помощи .each в jquery).

Разработка, HowTo

Git: откат мержа, который не был запушен

Иногда мы делаем мерж некоторой ветки в master и случается такое, что нам нужно по какой либо причине откатить ветку (мы не делали push еще в удаленный репозитарий), либо просто пытаемся сделать git pull забыв, что есть непрокоммиченые изменения.

При git pull мы увидим следующую картину

Git Pull Failed
You have not concluded your merge (MERGE_HEAD exists). Please, commit your changes before you can merge.

И чтобы откатить наш неудачный мерж не нужно делать никаких reset —hard HEAD=1.

Достаточно сделать

git reset --hard ORIG_HEAD

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

HowTo, linux

Как жить в локальной сети без dns для локальных ресурсов

Extra-BonjourИли сказ о том, как перестать бояться и начать раздавать динамические адреса в локальной сети.

Задача:

  • необходимо автоматизировать распределение имен различным устройствам в сети.

Проблемы

  • доисторический (ископаемый) роутер, который не умеет dd-wrt/openwrt и иже. А вместе с этим он не умеет статические адреса или локальный dns.
  • много iot-желаза в локальной сети к которому хочется получать доступ по имени (доменному конечно же).
  • Очень много железа, которое появляется в сети лишь на короткое время, а доступ к нему по сети нужен (ну не прописывать же ему постоянно статику?)
  • большое количество скриптов автоматизации, которым надо откуда-то брать именя устройств.

Проблему можно решить несколькими путями:

  • Поставить слабую железку, поставить на нее bind, поднять локальную доменную зону и убрать с роутера роль dhcp и dns-сервера. Минус в том, что слабой железки может и не быть.
  • Поменять роутер на менее доисторический. Минус в том, что роутера может не быть под рукой.
  • Воспользоваться протоколом zeroconf. Минусы тоже есть — возможный конфликт имен устройств.

Если с первыми двума вариантами все более-менее понятно, то на третьем стоит остановиться подробно. Так как он решает проблему наименее затратным способом.

Протокол описывает:

  • назначение адресов устройствам в сети (диапазон 169.254.*)
  • разрешение имен
  • обнаружение сервисов

Поскольку адреса у устройств уже есть (dhcp же), то нас будет интересовать только та часть протокола, где рассказывается про обнаружение сервисов и разрешение имен. Это mDNS+DNSSD.

В nix\bsd за эту часть протокола отвечает сервис avahi

В ряде дистрибутивов он включен и нормально настроен сразу.

На примере федоры посмотрим как его поставить и настроить.

$ sudo dnf install avahi-daemon avahi-utils
$ sudo systemctl enable avahi-daemon
$ sudo systemctl start avahi-daemon

Если у вас в сети уже есть устройства, где активирован avahi, то можно посмотреть на то, найдет ли оно какие-либо устройства

$ avahi-browse -alr
+   eth0 IPv4 workstation                                   Remote Disk Management local
=   eth0 IPv4 workstation                                   Remote Disk Management local
   hostname = [workstation.local]
   address = [192.168.1.10]
   port = [22]
   txt = []

Как видим что-то нашло.

Мы можем попробовать его попинговать.

$ ping workstation.local
ping: unknown host workstation.local

Если вы увидели такую картину, то это означает лишь одно — mdns для получения имен доменов у вас не подключен.

Чтобы его включить требуется отредактировать /etc/nsswitch.conf.

В строчку hosts нужно добавить mdns_minimal [NOTFOUND=return] перед dns.

hosts: files mdns_minimal [NOTFOUND=return] dns myhostname mymachines

После перезагрузки или перезапуска соотвествующего сервиса пингуем снова.

$ ping -c 4 workstation.local
PING workstation.local (192.168.1.10) 56(84) bytes of data.
64 bytes from workstation.local (192.168.1.10): icmp_req=1 ttl=64 time=0.449 ms
64 bytes from workstation.local (192.168.1.10): icmp_req=2 ttl=64 time=0.469 ms
64 bytes from workstation.local (192.168.1.10): icmp_req=3 ttl=64 time=0.467 ms
64 bytes from workstation.local (192.168.1.10): icmp_req=4 ttl=64 time=0.393 ms

--- workstation.local ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 3004ms
rtt min/avg/max/mdev = 0.393/0.444/0.469/0.037 ms

Если у вас очень медленно резолвятся локальные домены, то стоит попробовать использовать модуль mdns4_minimal.

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

При желании поднять zeroconf можно как на ардуине, так и на модулях esp8266.

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

Например торренты часто используют retracker.local для обозначения внутрисетевого трекера.