Про странные вещи в мире программирования.

Пока сам с таким не столкнешься, глазам своим не поверишь. Такой баг сразу и не найдешь.

Многие базы данных корректно работают с датами. Если взять дату — 31.12.2017, и отнять от нее один месяц, то должна получиться дата — 30.11.2017:

db=# select '2017-12-31'::timestamp - interval '1 month' new_date;
      new_date       
---------------------
 2017-11-30 00:00:00
(1 строка)
MariaDB [(none)]> select '2017-12-31' - interval 1 month new_date;
+------------+
| new_date   |
+------------+
| 2017-11-30 |
+------------+
1 row in set (0.00 sec)

Но php почему-то считает иначе:

$dt = date_create("2017-12-31");
$dt->modify("-1 month");
echo $dt->format('Y-m-d')."\n";

2017-12-01

Интересно то, что разработчики php не считают такое поведение багом, а особенностью реализации алгоритма работы с датами.

Синхронизация времени на разных серверах

Самый правильный способ синхронизации часов на серверах — это использовать какой-нибудь общий тайм сервер, и синхронизироваться с ним с помощью ntpd или ntpdate. Однако иногда возникают ситуации, когда таймсервер в локальной сети не настроен или недоступен, а выход во внешнюю сеть закрыт.

Первый вариант решения проблемы — использование какого-нибудь локального HTTP-сервера в качестве тайм-сервера. На все http-запросы сервер должен отвечать, и в заголовке ответа будет присутствовать параметр Date, то есть примерно так:

telnet 10.11.12.105 80
Trying 127.0.0.1...
Connected to 10.11.12.105.
Escape character is '^]'.
GET / HTTP/1.0

HTTP/1.0 200 OK
Date: Mon, 14 Sep 2017 19:43:59 GMT
Connection: close
Content-Type: text/html; charset=UTF-8
Content-Length: 1496
….

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

root# date -s "$(curl -sD - 10.11.12.105 | grep -E '^[[:space:]]*[dD]ate:' | sed 's/^[[:space:]]*[dD]ate:[[:space:]]*//' | head -1l | awk '{print $1, $3, $2,  $5 ,"GMT", $4 }' | sed 's/,//')"

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

ssh root@10.11.12.102 date --utc $(date --utc "+%m%d%H%M%Y.%S")

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

Network Manager, wvdial и T-Mobile

Давненько я ничего не писал. Сейчас я в отпуске, в солчнечной Черногории. И интернет у меня мобильный, от местного оператора T-mobile. Обычно я использую для выхода в интернет свой телефон с местной симкой в качестве модема, и звонилку wvdial со следующими настройками:

# cat /etc/wvdial.conf
[Dialer Defaults]
Init1 = ATZ
Init2 = ATQ0 V1 E1 S0=0 &C1 &D2 +FCLASS=0
Init3 = AT+CGDCONT=1,”IP”,”tmcg-wnw”
Modem = /dev/ttyACM0
Phone = *99#
Idle Seconds = 30000
Modem Type = USB Modem
Stupid Mode = 1
Compuserve = 0
Baud = 460800
Auto DNS = on
New PPPD = Yes
Dial Command = ATDT
Ask Password = 0
ISDN = 0
Password = 38167
Username = 38167

Это отлично работает, если сеть управляется через Wicked в OpenSuSe Leap 42.1. Но если сеть управляется через Network Manager, то при использовании звонилки настройки DNS сетью не подхватываются. Однако, в Network Manager предусмотрена возможность подключения к сети через модем, только стандартные настройки для оператора T-Mobile в Черногории не подходят. Для этого оператора указана точка доступа для тарифных планов Postpaid, требующих заключение договора с оператором. Для Prepaid необходимо поменять настройки точки доступа с tmcg-data на tmcg-wnw:

Настройки T-Mobile

Про формат дампов mysql

Смеркалось. Прилетела задача, залить в mysql таблицу, в которой есть несколько миллиардов записей. Консольная утилита отказывалась ее заливать, с очень непонятным сообщением об ошибке:

mysql -u root -p -D dbname <dump.sql
ERROR 2006 (HY000) at line 2024: MySQL server has gone away

Выяснилось, что за количество вносимых записей за один оператор отвечает параметр настройки max_allowed_packet, и его значение рекомендуется увеличить. Однако, выставить его в число, превышающее 4194304, не получается; и 4 млн записей за раз оказалось недостаточно для загрузки в базу такого дампа.

Выход был найден, пришлось дамп снять со следующими параметрами:

mysqldump -u root -p --opt --skip-extended-insert --skip-quick  dbname tablename >dump.sql

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

Логирование трафика в tcpdump

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

nohup /usr/sbin/tcpdump -vvv -C 50 -W 5 -s0 'host 192.168.0.15 and port 5060' -w /tcpdump_logs/192.168.0.15.cap >/dev/null 2>/dev/null &

Директория /tcpdump_logs должна быть доступна для записи для пользователя pcap

Firefox 40 и проигрывание mp3 в браузере

Случайно заметил, при открытии mp3 файла в firefox 40 файл не проигрывался, при этом выдавалось следующее сообщение об ошибке:

Video can’t be played because file is corrupt

При этом прослушивать перевод, например, в Google Translate или в Яндекс.Словари не представлялось возможным, файлы просто не проигрывались.

Я довольно долго разбирался, в чем дело, и выяснил — в Firefox внедрили нативную поддержку некоторых audio форматов, в том числе и mp3. Но не все кодеки доступны из коробки, некоторые требуют лицензионной чистоты, что не соответствует политике Mozilla Foundation. В общем, надо было браузеру явным образом указать на возможность fallback на плагин, умеющий проигрывать неподдерживаемый кодек. В Linux это делается путем установки 2-х настроек в about:config:

media.gstreamer.enabled => true
media.gstreamer.enable-blacklist => false

После указанных манипуляций firefox стал проигрывать mp3 файлы, даже при запуске из командной строки.

Логирование HTTP трафика для отладки запросов AJAX в Linux

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

Выглядит это так:

1. В браузере прописываем использование прокси по адресу 127.0.0.1 порт 5678
2. В терминале открываем сессию ssh с локальным порт-форвардингом (можно и со сжатием трафика), например так:

$ ssh -C -D 5678 username@myhost.com

3. В другом терминале запускаем tcpdump и слушаем трафик на интерфейсе lo порт 5678:

# tcpdump -vvv -s0 ‘port 5678′ -w “/home/username/http.pcap” -i lo

Далее в браузере выполняем необходимые действия, после чего останавливаем tcpdump. Смотреть записанный трафик лучше всего с помощью strings:

$ strings /home/username/http.pcap

Иногда в выводе может быть немного мусора из-за keepalive запросов, но в целом видно все, что требовалось узнать.

Запуск джоба чаще, чем 1 раз в минуту

Старый добрый cron не позволял ставить в расписание задачи, которые бы запускались чаще, чем 1 раз в минуту. Приходилось решать подобные задачи совсем другими способами. Но теперь есть systemd, который умеет это делать.

Пример такой конфигурации:

server:/etc/systemd/system # cat my.service
[Unit]
Description=My Script

[Service]
Type=simple
ExecStart=/path/to/my/script.sh
StartLimitInterval=0
StartLimitBurst=0

server:/etc/systemd/system # cat my.timer
[Unit]
Description=Runs job every 10 seconds

[Timer]
OnBootSec=1sec
OnCalendar=*-*-* *:*:0,10,20,30,40,50
Unit=my.service

[Install]
WantedBy=multi-user.target

Запуск таймера:

# systemctl start my.timer

Добавление в автозагрузку:

# systemctl enable my.timer

Мониторинг работы:

# journalctl -f

Новый сервис: онлайн форматирование JSON

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

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

Если погуглить, такие сервисы уже есть. Но либо тяжелые, либо медленные, либо обвешанные рекламой. В результате сделал свой сервис, сам пользуюсь и другим рекомендую.

В качестве теста можно использовать строку JSON, например, такую:

{"data":[{"t1":"John","t2":"Doe"},{"t1":"Anna","t2":"Smith" },{"t1":"Peter" ,"t2":"Jones"}]}

На выходе будет удобно читаемый JSON:

{
   "data" : [
      {
         "t2" : "Doe",
         "t1" : "John"
      },
      {
         "t2" : "Smith",
         "t1" : "Anna"
      },
      {
         "t2" : "Jones",
         "t1" : "Peter"
      }
   ]
}

Обычным людям такой сервис вряд ли будет не нужен, но программистам – пригодится.

Как сделать скриншот страницы с нестандартным разрешением в Firefox

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

В меню заходим в Tools / Web Developer / Developer Toolbar (или жмем Shift + F2), и в консоли пишем:

resize to 1920 5000
screenshot /tmp/output.png

И все, в /tmp/output.png будет сохранен скриншот области Firefox разрешением 1920×5000