<?xml version="1.0" encoding="utf-8"?> 
<rss version="2.0"
  xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd"
  xmlns:atom="http://www.w3.org/2005/Atom">

<channel>

<title>Максим Кузнецов</title>
<link>https://maxkuznetsov.ru/</link>
<description>Простыми словами о веб-разработке</description>
<author>Максим Кузнецов</author>
<language>ru</language>
<generator>E2 (v3559; Aegea)</generator>

<itunes:owner>
<itunes:name>Максим Кузнецов</itunes:name>
<itunes:email></itunes:email>
</itunes:owner>
<itunes:subtitle>Простыми словами о веб-разработке</itunes:subtitle>
<itunes:image href="" />
<itunes:explicit></itunes:explicit>

<item>
<title>[Инструкция] Как работать с Git</title>
<guid isPermaLink="false">49</guid>
<link>https://maxkuznetsov.ru/all/instrukciya-kak-rabotat-s-git/</link>
<pubDate>Tue, 15 Jul 2025 11:15:15 +0300</pubDate>
<author>Максим Кузнецов</author>
<comments>https://maxkuznetsov.ru/all/instrukciya-kak-rabotat-s-git/</comments>
<description>
&lt;p&gt;(Это инструкция, которую приходится копировать из проекта в проект, поэтому размещаю и в блоге.)&lt;/p&gt;
&lt;h2&gt;Gitflow&lt;/h2&gt;
&lt;p&gt;Идейно система работы с ветками берётся из Gitflow, но с упрощениями.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://maxkuznetsov.ru/pictures/gitflow.png" width="1043" height="371" alt="" /&gt;
&lt;/div&gt;
&lt;h2&gt;Общее&lt;/h2&gt;
&lt;p&gt;В нашем проекте ветка &lt;b&gt;develop&lt;/b&gt; — ветка, которая содержит стабильный результат, который можно зарелизить на прод.&lt;/p&gt;
&lt;p&gt;Следуя этому принципу, в рамках работы по спринту в ветку develop вливаются фичи/задачи:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;стабильного качества (по мнению разработчика (всегда) и менеджера (аппрув менеджера требуется, если этого требует конкретная задача);&lt;/li&gt;
&lt;li&gt;из спринта, который в работе (то есть те задачи, которые должны быть в ближайшем релизе).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Если ведется работа по фиче, которая не войдет в ближайший релиз, она не вливается в develop.  Тестирование такой задачи можно проводить, подключая ветку фичи на свободную дев площадку (не вливая ее в develop ветку).&lt;/p&gt;
&lt;h2&gt;Правила именования&lt;/h2&gt;
&lt;p&gt;Каждый коммит и пулл-реквест должны именоваться в следующем формате:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;[Sentry] EDS-1004: &lt;пояснение&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;В квадратных скобках можно указать часть системы, которая затрагивается — это упростит поиск коммита, если что-то сломается. Списка частей системы нет, выбирайте на свой вкус. Из общих советов — это должно быть что-то широкое, чтобы был понятен контекст, но не слишком широкое, чтобы не терялся смысл.&lt;/p&gt;
&lt;p&gt;EDS-1004 — даёт понимание по какой задаче была изменена строка в кода (через git blame), а потом по задаче можно найти ПОЧЕМУ это было сделано.&lt;br /&gt;
Плюс почти все таск-трекеры позволяют сконнектиться с Gitlab/Github/etc, и тогда коммиты и ПРы будут показываться в таск-трекре. А в Gitlab/Github все упоминания задач будут отображаться ссылками на таск-трекер автоматически.&lt;/p&gt;
&lt;h2&gt;Работа с ветками&lt;/h2&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Из ветки main (или аналогичных prod/master) создается ветка develop.&lt;/li&gt;
&lt;li&gt;На каждую фичу создаётся новая ветка вида feature/XXX-short-description от ветки develop. XXX — номер задачи из таск-трекера, &lt;short-description&gt; — краткое описание задачи на английском языке. Например: `feature/TASK-1777-change-forms-for-clients`.&lt;/li&gt;
&lt;li&gt;Когда фича готова (это значит, что разработчик считает, что фича выполнена корректно и протестирована локально) ветка фичи отправляется через Пулл-реквест в ветку develop. При создании Пулл-реквеста обязательно нужно выбрать галку «удалить ветку после мерджа», чтобы не разводить кладбище веток в git.&lt;/li&gt;
&lt;li&gt;Разработчик не забывает регулярно подтягивать ветку develop в свою ветку фичи в процессе разработки, чтобы уменьшить количество конфликтов при последующем MR в develop.&lt;/li&gt;
&lt;li&gt;Для релиза ветку develop мерджим в ветку main через MR в Gitlab/Githab/etc.&lt;/li&gt;
&lt;li&gt;Если в ветке prod обнаруживается критичный баг, то от main создается ветка hotfix/XXX-short-description.&lt;/li&gt;
&lt;li&gt;Как только исправление на ветке hotfix завершено, она мержится с ветками main, а затем с develop.&lt;/li&gt;
&lt;li&gt;После каждого мерджа тестируем в первую очередь новый функционал, потом все остальное.&lt;/li&gt;
&lt;li&gt;Если разработчик не успевает сделать свою задачу до релиза, то ветка фичи не мерджится в ветку develop.&lt;/li&gt;
&lt;li&gt;Если фича большая и не попадает в ближайший релиз, то разработку продолжаем в той же ветке под фичу, пока ее полностью не сделаем. Не забываем делать git pull, чтобы подтягивать все изменения c ветки develop. Это позволит подтягивать все изменения текущего спринта и разрешать конфликты как можно быстрее. Свою задачу можно протестировать на стейдже, залив её туда без мерджа.&lt;/li&gt;
&lt;/ol&gt;
</description>
</item>

<item>
<title>[Инструкция] Как деплоить проект через Deployer</title>
<guid isPermaLink="false">48</guid>
<link>https://maxkuznetsov.ru/all/kak-deploit-proekt-cherez-deployer/</link>
<pubDate>Tue, 15 Jul 2025 10:54:36 +0300</pubDate>
<author>Максим Кузнецов</author>
<comments>https://maxkuznetsov.ru/all/kak-deploit-proekt-cherez-deployer/</comments>
<description>
&lt;p&gt;(Инструкция, которую приходится копировать из проекта в проект, поэтому пусть будет и в блоге.)&lt;/p&gt;
&lt;p&gt;Предполагается, что Deployer доступен уже в проекте, либо как файл `./dep`, либо как установленный composer пакет через `./vendor/bin/dep`. Он добавляется и хранится в гит-репозитории.&lt;/p&gt;
&lt;p&gt;Также важно настроить конфиги, указав хост в `./deploy.php`. Они также хранятся в репозитории.&lt;/p&gt;
&lt;h2&gt;Обычные / регулярные деплои&lt;/h2&gt;
&lt;p&gt;`./dep deploy &lt;host&gt;`, где в качестве &lt;host&gt; можно выбрать любой хост из deploy.php файла. В этом файле у хоста указана нужная ветка. Однако, делать это обычно не нужно, так как на проекте настрое Gitlab CI (см файл `.gitlab-ci.yaml`), который зарелизит код по коммиту.&lt;/p&gt;
&lt;h2&gt;Как сменить деплой-ветку для хоста&lt;/h2&gt;
&lt;p&gt;Предположим, вы хотите сменить на `dev` ветку с `develop` на `feature/TASK-82-history-mypractice`.&lt;/p&gt;
&lt;h3&gt;Простой и быстрый вариант для быстрых тестов&lt;/h3&gt;
&lt;p&gt;Вы можете это сделать, поменяв `→set(’branch’, ’feature/TASK-82-history-mypractice’)` в deploy.php для хоста `dev`, а потом запустить `./dep deploy dev`.&lt;/p&gt;
&lt;p&gt;Этот способ хорош, если хочется быстро показать ветку, и мы не ожидаем, что будет много фиксов, так как Gitlab CI всё ещё останется настроен на `develop` ветку. И поэтому:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;при коммите в `develop` ветку, Gitlab CI запустит `./dep deploy dev` и задеплоит `feature/TASK-82-history-mypractice` на `dev`. Но это неправильно, так как мы не хотим деплоить эту ветку на дев по коммиту в `develop`, мы хотим деплоить по коммиту в ветку `feature/TASK-82-history-mypractice`&lt;/li&gt;
&lt;li&gt;при коммите в `feature/TASK-82-history-mypractice` ветку CI ничего не сделает, так как в версии конфига `.gitlab-ci.yaml` из этой ветки нет никакого правила для ветки `feature/TASK-82-history-mypractice`.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Правильный вариант с обновлением Gitlab CI правил&lt;/h3&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Переключиться в `develop` ветку, изменить branch в deploy.php для нужного хоста и в `.gitlab-ci.yaml` изменить параметр `only:` для этого же хоста.&lt;/li&gt;
&lt;li&gt;Закоммитить. CI теперь не запустит деплой на `dev`, так как мы поменяли ветку на `feature/TASK-82-history-mypractice`.&lt;/li&gt;
&lt;li&gt;Переключиться в ветку `feature/TASK-82-history-mypractice` в git и вмерджить в неё свежий `develop`. Вместе с develop придут изменения для `deploy.php` и `.gitlab-ci.yaml`. Поэтому если сразу закоммитить вмёрдженное, то Gitlab CI запустит деплой feature-ветки на `dev`.&lt;/li&gt;
&lt;li&gt;Важно! Перед мерджем ветки в `develop` нужно изменить `deploy.php` и `.gitlab-ci.yaml` и заменить feature-ветку в них обратно на `develop`. Потом смерджить и Gitlab CI, увидев этот коммит в develop, сразу продеплоит `develop` на `dev`.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Как переключить хост с feature1-ветки на другую feature-2 ветку:&lt;/h3&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Переключиться в develop ветку и изменить в ней `deploy.php` и `.gitlab-ci.yaml`, указав feature2 вместо feature1 для нужного хоста.&lt;/li&gt;
&lt;li&gt;Вмерджить develop ветку сначала в feature1 ветку, потом в feature2 ветку.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Удобные команды&lt;/h2&gt;
&lt;ol start="1"&gt;
&lt;li&gt;`./dep ssh &lt;host&gt;` — команда подключится к хосту по конфигу из deploy.php и сделает перейдёт в папку current релиза.&lt;/li&gt;
&lt;li&gt;`./dep run «&lt;command&gt;»` — запускает команду на удалённых серверах. После запуска она спросит на каких серверах запустить.&lt;/li&gt;
&lt;li&gt;`./dep &lt;task&gt; &lt;host&gt;` — запускает любую определённую в deploy.php `task(’&lt;name&gt;’, &lt;callback&gt;)` на нужно сервере.&lt;/li&gt;
&lt;li&gt;Можно указать «теги» для серверов и запускать всё выше написанное сразу пачками.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Первый деплой / создание окружения&lt;/h2&gt;
&lt;ol start="1"&gt;
&lt;li&gt;`./dep deploy develop2` — первый раз закончится с ошибкой, но Deployer создаст структуру файлов по пути из deploy.php для develop2. Он создаст папки
&lt;ol start="1"&gt;
  &lt;li&gt;`/var/www/dev.project.name/releases` — тут будут располагаться папки с каждым новым релизом и копиться последние N штук.&lt;/li&gt;
  &lt;li&gt;`/var/www/dev.project.name/shared` — тут хранятся файлы, которые должны быть сохранены и перенесены между релизами. Например, логи, web/assets, storage, etc. Эти папки задаются в deploy.php как shared_folder и shared_files. Во время деплоя эти папки будут заменены на симлинки из shared папки.&lt;/li&gt;
  &lt;li&gt;`/var/www/dev.project.name/current` — симлинк на последний успешный релиз из папки releases.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;После создания структуры нужно перенести/создать файлы в shared:
&lt;ol start="1"&gt;
  &lt;li&gt;`./shared/.env`&lt;/li&gt;
  &lt;li&gt;`./shared/web/XXXXXXX` — можно посмотреть, какие папки создадутся на сервере, либо какие папки указаны в shared_folder в deploy.php (с учётом тех, что указаны во фреймворк-специфичном recipe, подключённом в deploy.php).&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Повторить деплой `./dep deploy develop2`&lt;/li&gt;
&lt;li&gt;Также нужно залить данные в БД / импортнуть из sql дампа по параметрам из `.env`.&lt;/li&gt;
&lt;/ol&gt;
</description>
</item>

<item>
<title>Удаление старых локальных веток в Git-репозитории</title>
<guid isPermaLink="false">47</guid>
<link>https://maxkuznetsov.ru/all/udalenie-staryh-lokalnyh-vetok-v-git-repozitorii/</link>
<pubDate>Thu, 27 Feb 2025 22:57:05 +0300</pubDate>
<author>Максим Кузнецов</author>
<comments>https://maxkuznetsov.ru/all/udalenie-staryh-lokalnyh-vetok-v-git-repozitorii/</comments>
<description>
&lt;p&gt;На долгих проектах при следовании gitflow или похожих подходов с feature-ветками часто накапливаются локальные ветки, которые уже были смёрджены в develop.&lt;/p&gt;
&lt;p&gt;Старые ненужные ветки можно удалить одной командой:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;git branch --merged develop | grep -v &amp;quot;^\*\\|develop\\|main\\|master&amp;quot; | xargs -n 1 git branch -d&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;При этом текущие ветки, несмердженные в develop, останутся.&lt;/p&gt;
</description>
</item>

<item>
<title>Установка и настройка Clickhouse на EC2</title>
<guid isPermaLink="false">46</guid>
<link>https://maxkuznetsov.ru/all/ustanovka-i-nastroyka-clickhouse-na-ec2/</link>
<pubDate>Mon, 13 Nov 2023 15:14:10 +0300</pubDate>
<author>Максим Кузнецов</author>
<comments>https://maxkuznetsov.ru/all/ustanovka-i-nastroyka-clickhouse-na-ec2/</comments>
<description>
&lt;p&gt;Нашёл отличный и исчерпывающий гайд по установке и подготовке Clickhouse для продакшена на AWS EC2.&lt;br /&gt;
Допустим, у нас уже есть VPC, в которую хочется добавиться сервер с CH.&lt;br /&gt;
Единственно, в качестве инстанса я взял машину попроще — m7g.large (~$60/mo) на Ubuntu и Arch64 архитектуре.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://anthonynsimon.com/blog/clickhouse-deployment/"&gt;https://anthonynsimon.com/blog/clickhouse-deployment/&lt;/a&gt;&lt;/p&gt;
</description>
</item>

<item>
<title>Как узнать общий вес базы данных и найти самые тяжёлые таблицы</title>
<guid isPermaLink="false">45</guid>
<link>https://maxkuznetsov.ru/all/kak-uznat-obschiy-ves-bazy-dannyh-i-nayti-samye-tyazhyolye-tabli/</link>
<pubDate>Tue, 31 Oct 2023 12:13:08 +0300</pubDate>
<author>Максим Кузнецов</author>
<comments>https://maxkuznetsov.ru/all/kak-uznat-obschiy-ves-bazy-dannyh-i-nayti-samye-tyazhyolye-tabli/</comments>
<description>
&lt;p&gt;Вес баз данных&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;SELECT table_schema AS &amp;quot;Database&amp;quot;, 
ROUND(SUM(data_length + index_length) / 1024 / 1024 / 1024, 2) AS &amp;quot;Size (GB)&amp;quot; 
FROM information_schema.TABLES 
GROUP BY table_schema;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Размер таблиц, включая отдельно данные и отдельно индексы.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;SELECT CONCAT(table_schema, '.', table_name),
       CONCAT(ROUND(table_rows / 1000000, 2), 'M')                                    `rows`,
       CONCAT(ROUND(data_length / ( 1024 * 1024 * 1024 ), 2), 'G')                    DATA,
       CONCAT(ROUND(index_length / ( 1024 * 1024 * 1024 ), 2), 'G')                   idx,
       CONCAT(ROUND(( data_length + index_length ) / ( 1024 * 1024 * 1024 ), 2), 'G') total_size,
       ROUND(index_length / data_length, 2)                                           idxfrac
FROM   information_schema.TABLES
ORDER  BY data_length + index_length DESC
LIMIT  20;&lt;/code&gt;&lt;/pre&gt;</description>
</item>

<item>
<title>Как добавить новый сервис в systemd/systemctl и запускать его при старте  Ubuntu</title>
<guid isPermaLink="false">44</guid>
<link>https://maxkuznetsov.ru/all/ubuntu-systemd-systemctl-on-startup/</link>
<pubDate>Wed, 10 May 2023 17:35:55 +0300</pubDate>
<author>Максим Кузнецов</author>
<comments>https://maxkuznetsov.ru/all/ubuntu-systemd-systemctl-on-startup/</comments>
<description>
&lt;h3&gt;Задача&lt;/h3&gt;
&lt;p&gt;Есть docker-compose файл, поднимающий Zabbix. Есть небольшая обёртка в виде Makefile, которая позволяет запускать и останавливать docker-compose. Нужно добавить запуск этой команды при старте/рестарте системы.&lt;/p&gt;
&lt;h3&gt;Дано&lt;/h3&gt;
&lt;p&gt;Всё лежит в `/var/www/zabbix-server/`.&lt;br /&gt;
Содержание файла Makefile:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;...
docker-up:
        docker-compose up -d

docker-down:
        docker-compose stop&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Решение&lt;/h3&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Создать новый файл `vim /etc/systemd/system/zabbix.service`.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;[Unit]
Description=Run Zabbix Docker Containers on Startup

[Service]
RemainAfterExit=True
Restart=always
RestartSec=1
WorkingDirectory=/var/www/zabbix-server
ExecStart=/usr/bin/make docker-up
ExecStop=/usr/bin/make docker-down

[Install]
WantedBy=default.target&lt;/code&gt;&lt;/pre&gt;&lt;ol start="2"&gt;
&lt;li&gt;chmod 644 /etc/systemd/system/zabbix.service&lt;/li&gt;
&lt;li&gt;systemctl enable zabbix.service&lt;/li&gt;
&lt;li&gt;systemctl start zabbix.service&lt;/li&gt;
&lt;/ol&gt;
</description>
</item>

<item>
<title>Ubuntu: Running Out of Space и количество inodes</title>
<guid isPermaLink="false">43</guid>
<link>https://maxkuznetsov.ru/all/ubuntu-running-out-of-space-i-kolichestvo-inodes/</link>
<pubDate>Sun, 13 Mar 2022 04:25:22 +0300</pubDate>
<author>Максим Кузнецов</author>
<comments>https://maxkuznetsov.ru/all/ubuntu-running-out-of-space-i-kolichestvo-inodes/</comments>
<description>
&lt;p&gt;Одна из причин, почему ваш Ubuntu сервер может сигнализировать, что кончилась память, тогда как её предостаточно — слишком большое количество файлов. Каждый диск в зависимости от размера имеет ограниченное максимальное количество файлов и папок, которые вы можете создать.&lt;/p&gt;
&lt;p&gt;Проверить количество используемых inodes можно командой:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;root@ec2-consumer:~# df -ih
Filesystem     Inodes IUsed IFree IUse% Mounted on
/dev/root        2.5M  2.1M  419K   84% /
devtmpfs         482K   349  482K    1% /dev
tmpfs            484K     5  484K    1% /dev/shm
tmpfs            484K   602  483K    1% /run
tmpfs            484K     4  484K    1% /run/lock
tmpfs            484K    18  484K    1% /sys/fs/cgroup
/dev/loop1         16    16     0  100% /snap/amazon-ssm-agent/4046
/dev/loop5        11K   11K     0  100% /snap/core18/2253
/dev/nvme1n1      25M  462K   25M    2% /var/www
tmpfs            484K    22  484K    1% /run/user/0&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Видим 84% около /dev/root — процент очень большой, это свидетельствует о проблеме. Обычно должно быть не больше 50%, а 80% — это верхний предел.&lt;/p&gt;
&lt;p&gt;Причин может быть несколько, например, у вас есть какой-то скрипт, пишущий много логов или временных файлов в /tmp. В моём случае это была библиотека phrets, которая создаёт много пустых временных файлов в /tmp.&lt;/p&gt;
&lt;p&gt;Если не знаете, где превышаете лимит по количеству файлов, запустите команду (может занять несколько минут):&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;du --inodes -d 3 / | sort -n | tail&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Быстрое решение&lt;/h2&gt;
&lt;p&gt;Просто удалите лишние файлы. Это можно сделать по шаблону regexp:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;find /tmp -type f -mtime +0 -name 'phrets*' -execdir rm -- '{}' \;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Здесь &lt;tt&gt;-mtime +0&lt;/tt&gt; фильтрует все файлы, которые были созданы больше суток назад — я не хотел удалять все файлы, так как не был уверен, что библиотека phrets не использует самые свежие для чего-то нужного (хотя они все и были пустыми). Почитайте про значения параметра mtime, чтобы фильтровать как давно созданные файлы, так и недавно.&lt;/p&gt;
&lt;h2&gt;Долгое решение&lt;/h2&gt;
&lt;p&gt;Нужно увеличить количество inodes. Это крайне редкое и радикальное решение, которое хорошо описано тут — &lt;a href="https://www.ctrl.blog/entry/how-to-all-out-of-inodes.html."&gt;https://www.ctrl.blog/entry/how-to-all-out-of-inodes.html.&lt;/a&gt;&lt;/p&gt;
</description>
</item>

<item>
<title>Как узнать размер бакета на AWS S3</title>
<guid isPermaLink="false">42</guid>
<link>https://maxkuznetsov.ru/all/kak-uznat-razmer-baketa-na-aws-s3/</link>
<pubDate>Tue, 19 Oct 2021 18:00:43 +0300</pubDate>
<author>Максим Кузнецов</author>
<comments>https://maxkuznetsov.ru/all/kak-uznat-razmer-baketa-na-aws-s3/</comments>
<description>
&lt;p&gt;К сожалению, AWS консоль (дэшборд) не даёт возможность узнать количество файлов и занимаемый объём. Это можно сделать через консоль:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;aws s3 ls --summarize --human-readable --recursive s3://&amp;lt;bucket-name&amp;gt;&lt;/code&gt;&lt;/pre&gt;</description>
</item>

<item>
<title>Бесплатный wildcard SSL-сертификат для поддоменов</title>
<guid isPermaLink="false">40</guid>
<link>https://maxkuznetsov.ru/all/nginx-free-wildcard-ssl/</link>
<pubDate>Sun, 02 May 2021 18:05:26 +0300</pubDate>
<author>Максим Кузнецов</author>
<comments>https://maxkuznetsov.ru/all/nginx-free-wildcard-ssl/</comments>
<description>
&lt;p&gt;Для простых веб-приложений достаточно одного домена (example.com). Однако для сложных систем, с разделением на бэкенд и фронтенд части, с лендингами, микросервисами и т. п. нужны поддомены, например: api.example.com, app.example.com, cdn.example.com.&lt;/p&gt;
&lt;p&gt;Вместо того, чтобы выпускать отдельные сертификаты на каждый поддомен, мы можем выпустить один сертификат, покрывающий сразу все кейсы — *.example.com. Такой тип сертификатов называется &lt;i&gt;Wildcard&lt;/i&gt;.&lt;/p&gt;
&lt;p&gt;Как и в предыдущей статье про выдачу &lt;a href="https://maxkuznetsov.ru/all/nginx-free-ssl/"&gt;бесплатного SSL-сертификата&lt;/a&gt;, мы воспользуемся утилитой Certbot, который поможет нам выпустить и установить бесплатные SSL-сертификаты для наших поддоменов через центр Let’s encrypt.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Предполагаю, что вы уже настроили ваш сервер, чтобы поддомены были доступны по http (80 порт). Если вы не знаете, как это сделать, поищите статью по вашим ОС и веб-серверу на Digitalocean. Например, вот статья про &lt;a href="https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-20-04-ru"&gt;установку и настройку Nginx на Ubuntu 20.04&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Заходим на сайт &lt;a href="https://certbot.eff.org/"&gt;Certbot’а&lt;/a&gt; и выбираем ОС нашего сервиса и веб-сервер. Ниже в примере будет Ubuntu 20.04 и Nginx. Дальше выбираем таб Wildcard.&lt;/p&gt;
&lt;p&gt;Предположим, что нам нужны следующие домены: example.com, api.example.com и app.example.com.&lt;br /&gt;
Запускаем по инструкции первые 8 шагов для установки самой утилиты:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;// Удаляем snap, если он был установлен ранее
$ sudo apt-get remove certbot
// Устанавливаем snap
$ sudo snap install core; sudo snap refresh core
// Устанавливаем certbot
$ sudo snap install --classic certbot
$ sudo ln -s /snap/bin/certbot /usr/bin/certbot
$ sudo snap set certbot trust-plugin-with-root=ok

// Проверяем, что certbot установлен
$ certbot --version
&amp;gt; certbot 1.14.0&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Для того, чтобы выпустить сертификаты, мы должны доказать удостоверяющему центру Let’s Encrypt, что мы действительно владеем данными доменами и сабдоменами. Есть два способа это сделать.&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Добавить в DNS специальные txt записи, которые попросит нас добавить Let’s Encrypt, после этого он их проверит, и если записи будут обнаружены, это подтвердит факт владения (или по крайней мере доступа к ним). С чужими доменами так сделать не получится.&lt;/li&gt;
&lt;li&gt;Webroot. Аналогичный способ, но специальные уникальные строки добавляются в файлы, которые потом должны быть доступны через URL, например: &lt;tt&gt;&lt;a href="http://api.example.com/.well-known/acme-challenge/JH1kjoemxaS33d"&gt;http://api.example.com/.well-known/acme-challenge/JH1kjoemxaS33d&lt;/a&gt;&lt;/tt&gt;. Поскольку такой файл сможет добавить только владелец домена, этого тоже достаточно, чтобы Let’s Encrypt удостоверился в факте нашего владения.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Certbot позволяет автоматизировать каждый из способов. Нам не придётся делать это вручную, что критично для будущей поддержки сертификатов — их нужно будет обновлять раз в два месяца. И если доменов и сабдоменов много, то это быстро станет обузой.&lt;/p&gt;
&lt;p&gt;Certbot поддерживает из коробки несколько популярных DNS-регистраторов (те сервисы, где можно купить домен), и тогда можно следовать по инструкции, однако в моём случае регистратором был Godaddy, который не поддерживается. Поэтому я воспользовался вторым способом и он оказался даже проще первого. Кроме того, данный способ надёжнее, так как не зависит, повезёт ли вам с поддержкой вашего регистратора или нет.&lt;/p&gt;
&lt;p&gt;Для верификации доменов по webroot нам нужно настроить каждый домен/поддомен, чтобы он отдавал директорию &lt;tt&gt;&lt;domain&gt;/.well-known/acme-challenge/&lt;/tt&gt;. В эту папку certbot запишет то, что нужно Let’s Encrypt, чтобы мы прошли проверку, поэтому убедитесь, что certbot имеет туда доступ.&lt;/p&gt;
&lt;p&gt;Есть небольшой лайфхак, как упростить работу с этой папкой в конфигах сервера. Для этого мы создадим отдельный файл конфига, который будем подключать для каждого домена/поддомена. Пример для Nginx:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="nginx"&gt;// создаём файл &amp;quot;/etc/nginx/letsencrypt.conf&amp;quot;
location /.well-known/acme-challenge {
    # Вместо `/var/www/acme-challenge` можно указать любую папку.
    # Она может быть недоступна извне, это неважно. Главное, чтобы туда имел доступ ваш текущий юзер.
    alias /var/www/acme-challenge/.well-known/acme-challenge;
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Подключаем этот файл в конфигах доменов и поддоменов. Допустим, у вас есть файл &lt;/tt&gt;/etc/nginx/sites-available/api.example.com&lt;/tt&gt; (с остальными аналогично).&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="nginx"&gt;server {
    server_name api.example.com;
    include letsencrypt.conf;
    ...
}&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Готово. Теперь домен будет все запросы, начинающиеся на &lt;tt&gt;/.well-known/acme-challenge&lt;/tt&gt; будут направлены в папку &lt;tt&gt;/var/www/acme-challenge/.well-known/acme-challenge&lt;/tt&gt;, общую для всех доменов/поддоменов.&lt;/p&gt;
&lt;p&gt;Далее запускаем certbot, чтобы он пообщался с Let’s Encrypt, подтвердил наши домены, выпустил сертификаты и даже настроил наш веб-сервер на их использование.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;certbot run -a webroot -i nginx -w /var/www/acme-challenge -d example.com -d api.example.com -d app.example.com&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Разберём эти конфиги:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;tt&gt;-a webroot&lt;/tt&gt; — это способ аутентификации (подтверждение, что мы владеем доменами)&lt;/li&gt;
&lt;li&gt;&lt;tt&gt;-i nginx&lt;/tt&gt; — один из доступных способов установки сертификатов. В данном случае это nginx, так как именно этот веб-сервер мы используем. Certbot вам задаст несколько вопросов и после этого обновит конфиги. Делает он это очень умно, так что без разницы как эти конфиги у вас написаны. И даже устанавливает редирект http на https.&lt;/li&gt;
&lt;li&gt;&lt;tt&gt;-w /var/www/acme-challenge&lt;/tt&gt; — путь до папки, которая будет использоваться для webroot аутентификации. Обратите внимание, что именно эта папка прописана в &lt;tt&gt;/etc/nginx/letsencrypt.conf&lt;/tt&gt;.&lt;/li&gt;
&lt;li&gt;&lt;tt&gt;-d example.com -d api.example.com -d app.example.com&lt;/tt&gt; — указываем все необходимые поддомены, обязательно первым указываем основной домен (не поддомен), иначе ничего не получится.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Всё, certbot всё сделал сам, даже перезагрузил веб-сервер. Можно зайти через браузер на поддомены и убедиться, что всё работает.&lt;/p&gt;
&lt;p&gt;&lt;hr&gt;&lt;/p&gt;
&lt;p&gt;p.s. если браузер выдаёт ошибку, что такой домен не найден, то проверьте, что файрволл настроен на выдачу https трафика. Например, в Ubuntu 20.04 это делается через UFW:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="bash"&gt;// Проверяем, есть ли HTTPS в активных правилах
$ sudo ufw status
// Смотрим, какие правила могут быть включены
$ sudo ufw app list
// Включаем Nginx https
$ sudo ufw allow 'Nginx HTTPS'
// Проверяем, что правило появилось в активных
$ sudo ufw status&lt;/code&gt;&lt;/pre&gt;</description>
</item>

<item>
<title>Как настроить php-cs-fixer и phpstan на локалке</title>
<guid isPermaLink="false">39</guid>
<link>https://maxkuznetsov.ru/all/code-quality-local/</link>
<pubDate>Tue, 01 Dec 2020 17:34:55 +0300</pubDate>
<author>Максим Кузнецов</author>
<comments>https://maxkuznetsov.ru/all/code-quality-local/</comments>
<description>
&lt;h2&gt;Проблема 1&lt;/h2&gt;
&lt;p&gt;При совместной разработке (от 2х человек) code style становится критичным. Если каждый будет писать в своём стиле, вскоре проект превратится в бардак, где по разному стилю можно будет оценивать возраст кода будто по кольцам на деревьях.&lt;/p&gt;
&lt;p&gt;На собеседованиях я часто слышу от кандидатов, что мы пользуемся проверками, встроенными в IDE. Но это полрешения, так как такие проверки носят рекомендательный, а не обязательный характер. Очень легко не заметить предложение по правке от IDE. А кроме того, приходится синхронизировать настройки IDE между всей командой. А что делать, если у кого-то другой редактор? Результат непредсказуем, а значит и ненадёжен.&lt;/p&gt;
&lt;h2&gt;Проблема 2&lt;/h2&gt;
&lt;p&gt;Чем больше кодовая база проекта, тем проще допустить ошибку или опечатку. Вручную проверка кода перед каждым коммитом возможна, но требует много времени и большой внимательности. Результат опять непредсказуем и ненадёжен.&lt;/p&gt;
&lt;h2&gt;Решение&lt;/h2&gt;
&lt;p&gt;Нужно договориться об общих правилах с командой и автоматизировать процесс проверки кода, сделав её независимой от IDE, платформы, настроения, желания и внимательности человека.&lt;/p&gt;
&lt;p&gt;Такие инструменты уже есть — code style и статические анализаторы кода.&lt;/p&gt;
&lt;h3&gt;Единый стиль кода&lt;/h3&gt;
&lt;p&gt;Поскольку я специализируюсь на Symfony, то для код-стайла выбираю &lt;a href="https://github.com/FriendsOfPHP/PHP-CS-Fixer"&gt;PHP-CS-Fixer&lt;/a&gt;. Он написан при участии создателя Symfony и имеет из коробки поддержку рекомендованного сообществом &lt;a href="https://symfony.com/doc/current/contributing/code/standards.html"&gt;Symfony Code Standards&lt;/a&gt;. Есть ещё &lt;a href="https://github.com/squizlabs/PHP_CodeSniffer"&gt;PHPCodeSniffer&lt;/a&gt;, но он умеет только обнаруживать ошибки, но не исправляет их автоматически, поэтому я рекомендую именно PHP-CS-Fixer.&lt;/p&gt;
&lt;p&gt;Совет: обязательно изучите и следуйте code style и рекомендациям вашего фреймворка. С большой вероятностью в будущих проектах вам встретится именно общепринятый в сообществе фреймворка code style, поэтому будет проще вписаться в новую команду, если вы придёте к этому стилю как можно раньше. К тому же, уже принятый в сообществе стиль поможет избежать ненужных холиваров внутри команды.&lt;/p&gt;
&lt;p&gt;Устанавливаем через composer и настраиваем PHPCSFixer в Symfony-проекте:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;cd /ваш/проект
composer req --dev friendsofphp/php-cs-fixer
# Если используете docker-compose, то устанавливайте в нём
docker-compose exec web composer req --dev friendsofphp/php-cs-fixer&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Создаём в корне проекта файл &lt;tt&gt;.php_cs&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;&amp;lt;?php

$finder = PhpCsFixer\Finder::create()
    -&amp;gt;in([
        __DIR__ . '/src',
        __DIR__ . '/tests'
    ])
;

return PhpCsFixer\Config::create()
    -&amp;gt;setRules([
        '@Symfony' =&amp;gt; true,
        'array_syntax' =&amp;gt; ['syntax' =&amp;gt; 'short'],
        'concat_space' =&amp;gt; ['spacing' =&amp;gt; 'one'],
        'increment_style' =&amp;gt; ['style' =&amp;gt; 'post'],
        'no_extra_blank_lines' =&amp;gt; ['tokens' =&amp;gt; [
            'extra',
            'parenthesis_brace_block',
            'square_brace_block',
            'throw',
            'use',
        ]],
        'no_superfluous_phpdoc_tags' =&amp;gt; false,
        'phpdoc_align' =&amp;gt; false,
        'phpdoc_annotation_without_dot' =&amp;gt; false,
        'trailing_comma_in_multiline_array' =&amp;gt; false,
        'yoda_style' =&amp;gt; false
    ])
    -&amp;gt;setFinder($finder)
;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Теперь вы можете запустить проверку всего проекта (или конкретного пути) с помощью команды&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;# только поиск ошибок
./vendor/bin/php-cs-fixer  fix --dry-run --diff ./
# поиск ошибок и их автоматический фикс
./vendor/bin/php-cs-fixer fix ./&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Ошибки и качество кода&lt;/h3&gt;
&lt;p&gt;Для отлова опечаток и даже поиска более сложных ошибок мы воспользуемся статическим анализатором кода. Прелесть анализаторов в том, что они не выполняют код, а просто читают и анализируют — это очень быстро. Кроме того, каждый из них имеет свои уровни строгости, поэтому можно внедрять их в существующий проект постепенно: фиксим ошибки с минимальным уровнем, коммитим, повышаем уровень, фиксим, коммитим, снова повышаем.&lt;/p&gt;
&lt;p&gt;Самые популярные статичиские анализаторы кода:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/phpstan/phpstan"&gt;PHPStan&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vimeo/psalm"&gt;Psalm&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/phan/phan"&gt;Phan&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Я покажу, как настроить и внедрить PHPStan, а вы можете добавить в проект все три по аналогии. Устанавливаем через composer сам PHPStan и несколько полезных плагинов — PHPUnit, Symfony, Doctrine.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;cd /ваш/проект
composer req --dev phpstan/phpstan phpstan/phpstan-doctrine phpstan/phpstan-phpunit phpstan/phpstan-symfony&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Должен создаться в корне проекта файл &lt;tt&gt;.phpstan.neon.dist&lt;/tt&gt;, редактируем его:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;includes:
    - vendor/phpstan/phpstan-doctrine/extension.neon
    - vendor/phpstan/phpstan-phpunit/extension.neon
    - vendor/phpstan/phpstan-phpunit/rules.neon
    - vendor/phpstan/phpstan-symfony/extension.neon
parameters:
    reportUnmatchedIgnoredErrors: false     #to avoid throwing of errors ... if no errors matching the ignored one is raised
    scanFiles:  #we need this for some twig based functions
        - '%rootDir%/../../../vendor/twig/twig/src/Extension/CoreExtension.php'
    bootstrapFiles:
        - '%rootDir%/../../../vendor/bin/.phpunit/phpunit-X.X.X/vendor/autoload.php'
    ignoreErrors:
        - message: '/^Service &amp;quot;[^&amp;quot;]+&amp;quot; is private.$/'
          path: '%rootDir%/../../../tests/'
    level: 0 
    symfony:
        container_xml_path: '%rootDir%/../../../var/cache/development/App_KernelDevelopmentDebugContainer.xml'&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Внимательно пройдитесь по этим настройкам и укажите корректные пути. Особое внимание:&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;bootstrapFiles — включает путь до текущего phpunit. Если его не используете, то можете закоментить.&lt;/li&gt;
&lt;li&gt;level — уровень строгости проверок &lt;a href="https://phpstan.org/user-guide/rule-levels"&gt;от 0 до 8&lt;/a&gt;. 0 — самый слабый уровень, подходит для первого внедрения.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Теперь вы можете запустить проверку всего проекта (или конкретного пути) с помощью команды&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;./vendor/bin/phpstan analyse src tests&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Советы по внедрению&lt;/h3&gt;
&lt;p&gt;Несколько советов по внедреню PHP-CS-Fixer в существующий проект с большой командой.&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Выберите одного ответственного, кто запустит скрипты и пофиксит весь проект у себя на локалке, а потом создаст ПР / смерджит в develop или master.&lt;/li&gt;
&lt;li&gt;Лучшее время — первое утро нового спринта. Обычно после спринта все ветки смёрджены, и это позволит залить все найденные правки в основную ветку проекта без конфликтов.&lt;/li&gt;
&lt;li&gt;В статических анализаторах используйте для начала минимальный уровень строгости проверок. У каждого анализатора для этого своя школа, читайте документацию.&lt;/li&gt;
&lt;li&gt;Первый фикс — самый критичный момент, так как правок может быть очень много. Все последующие фиксы обычно не создают ощутимых конфликтов и гит их смёрдживает самостоятельно.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Чтобы не запоминать длинные пути до скриптов, мы добавим несколько задач (tasks) в composer.json:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;{
    &amp;quot;require&amp;quot;: { ... },
    &amp;quot;require-dev&amp;quot;: { ... },
    ...
    &amp;quot;scripts&amp;quot;: {
        ...
        &amp;quot;csfix&amp;quot;: &amp;quot;./vendor/bin/php-cs-fixer fix&amp;quot;,
        &amp;quot;csfix-validate&amp;quot; : &amp;quot;./vendor/bin/php-cs-fixer fix --dry-run --diff&amp;quot;,
        &amp;quot;phpstan&amp;quot;: &amp;quot;./vendor/bin/phpstan analyse src tests&amp;quot;,
        &amp;quot;code-quality&amp;quot;: [
            &amp;quot;@phpcsfixer-validate&amp;quot;,
            &amp;quot;@phpstan&amp;quot;
        ]&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Теперь мы можем запускать проверки в упрощённом формате:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;composer csfix
composer phpstan
composer code-quality&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Обязательный запуск проверок перед коммитом&lt;/h2&gt;
&lt;p&gt;Внедрение этих инструментов позволяет удостовериться, что код соответствует принятому в команде стилю и не имеет очевидных опечаток и синтаксических ошибок. Однако это ещё не решает проблему забывчивости программистов и необязательности проверок.&lt;/p&gt;
&lt;p&gt;Воспользуемся Git-хуками (git hooks) — это скрипты, которые мы можем попросить git выполнить при возникновении какого-то события: момент перед созданием коммита, момент после создания коммита, момент добавления сообщения в коммит и т. п.&lt;/p&gt;
&lt;p&gt;Полный список git-хуков вы можете увидеть у себя в репозитории:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;$ ls -al .git/hooks
-rwxr-xr-x   1 mk  staff   478 Jul 13 13:04 applypatch-msg.sample
-rwxr-xr-x   1 mk  staff   896 Jul 13 13:04 commit-msg.sample
-rwxr-xr-x   1 mk  staff  3327 Jul 13 13:04 fsmonitor-watchman.sample
-rwxr-xr-x   1 mk  staff   189 Jul 13 13:04 post-update.sample
-rwxr-xr-x   1 mk  staff   424 Jul 13 13:04 pre-applypatch.sample
-rwxr-xr-x   1 mk  staff   382 Dec  1 14:46 pre-commit
-rwxr-xr-x   1 mk  staff  1638 Jul 13 13:04 pre-commit.sample
-rwxr-xr-x   1 mk  staff   416 Jul 13 13:04 pre-merge-commit.sample
-rwxr-xr-x   1 mk  staff  1348 Jul 13 13:04 pre-push.sample
-rwxr-xr-x   1 mk  staff  4898 Jul 13 13:04 pre-rebase.sample
-rwxr-xr-x   1 mk  staff   544 Jul 13 13:04 pre-receive.sample
-rwxr-xr-x   1 mk  staff  1492 Jul 13 13:04 prepare-commit-msg.sample
-rwxr-xr-x   1 mk  staff  3610 Jul 13 13:04 update.sample&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Создадим скрипт, который будет запускать проверки каждый раз перед попыткой создать коммит.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;vim .git/hooks/pre-commit&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Добавляем внутрь файла:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;#!/bin/sh
export PATH=/usr/local/bin:$PATH
# Если вы используете docker-compose (рекомендуется), то добавьте
export COMPOSE_INTERACTIVE_NO_CLI=1

if [ -t 1 ]; then
    # If we're in a terminal, redirect stdout and stderr to /dev/tty and
    # read stdin from /dev/tty. Allow interactive mode for CaptainHook.
    exec &amp;gt;/dev/tty 2&amp;gt;/dev/tty &amp;lt;/dev/tty
fi

errors=0

composer code-quality
# или, если вы используете docker-compose (рекомендуется), то
docker-compose exec -T web php bin/composer code-quality

if [ &amp;quot;$?&amp;quot; -ne 0 ]; then
    errors=1
fi

if [ &amp;quot;$errors&amp;quot; -eq 1 ]; then
    echo &amp;quot;Errors detected!&amp;quot;
    exit 1
fi&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Теперь можете попробовать сделать ошибку в .php файле и создать коммит. Git должен ругнуться:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://maxkuznetsov.ru/pictures/Screen-Shot-2020-12-01-at-17.04.25.png" width="1344" height="826" alt="" /&gt;
&lt;/div&gt;
&lt;h2&gt;Устанавливаем git-hook автоматически&lt;/h2&gt;
&lt;p&gt;Финальный шаг — добавить git-hook в .git/hooks/ папку при запуске composer. Тогда ни один разработчик не сможет пропустить запуск проверок по невнимательности. Для этого добавим в composer.json такую строку:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;{
    ...,
    &amp;quot;scripts&amp;quot;: {
        &amp;quot;post-autoload-dump&amp;quot;: &amp;quot;mkdir -p .git/hooks &amp;amp;&amp;amp; cp config/pre-commit .git/hooks/pre-commit&amp;quot;,
        ...
    }
}&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Итог&lt;/h2&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Мы добавили в проект PHPCsFixer для код-стайла и PHPStan для поиска ошибок и опечаток.&lt;/li&gt;
&lt;li&gt;Запускаем их при каждой попытке создать коммит.&lt;/li&gt;
&lt;li&gt;И всё это начинает работать автоматически после первого же &lt;tt&gt;composer install&lt;/tt&gt;.&lt;/li&gt;
&lt;/ol&gt;
</description>
</item>

<item>
<title>Deployment: настраиваем пользователей</title>
<guid isPermaLink="false">38</guid>
<link>https://maxkuznetsov.ru/all/deployment-users/</link>
<pubDate>Fri, 27 Nov 2020 04:17:56 +0300</pubDate>
<author>Максим Кузнецов</author>
<comments>https://maxkuznetsov.ru/all/deployment-users/</comments>
<description>
&lt;h3&gt;Проблема&lt;/h3&gt;
&lt;p&gt;Под &lt;tt&gt;root&lt;/tt&gt;-пользователем работать небезопасно, равно как и делать весь проект доступным &lt;tt&gt;www-data&lt;/tt&gt;.&lt;br /&gt;
Кроме того, во многих веб-приложениях пользователи имеют возможность загружать свои файлы. И очень часто с такими файлами возникают конфликты, так как пишутся они под &lt;tt&gt;www-data:www-data&lt;/tt&gt;, а деплоим под другим пользователем. Даже если деплоить под &lt;tt&gt;root&lt;/tt&gt;, то скорее всего права на папку перепишутся и &lt;tt&gt;www-data&lt;/tt&gt; потеряет доступ к нужным файлам.&lt;/p&gt;
&lt;h3&gt;Решение&lt;/h3&gt;
&lt;p&gt;Создать нового пользователя deploy с правами на коннект к серверу по ssh, доступом только к папке с проектом и возможностью изменять файлы в &lt;tt&gt;./public/uploads&lt;/tt&gt; и &lt;tt&gt;./var&lt;/tt&gt;.&lt;/p&gt;
&lt;p&gt;Создаём на сервере пользователя &lt;tt&gt;deploy&lt;/tt&gt; из-под &lt;tt&gt;root&lt;/tt&gt;:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;ssh root@вашсервер
useradd --create-home -s /bin/bash deploy&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Настроим доступ по ssh через ключи&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="bash"&gt;mkdir /home/deploy/.ssh
touch /home/deploy/.ssh/authorized_keys&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Вставьте содержимое одного из публичного ключа своего локального пользователя в файл &lt;tt&gt;authorized_keys&lt;/tt&gt; и сохраните. Вывести публичный ключ локально можно командой: &lt;tt&gt;cat ~/.ssh/id_rsa.pub&lt;/tt&gt; (название ключа &lt;tt&gt;id_rsa.pub&lt;/tt&gt; у вас может отличаться).&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;vim /home/deploy/.ssh/authorized_keys&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Меняем права на более строгие&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;chown -R deploy:deploy /home/deploy
chmod 600 /home/deploy/.ssh/authorized_keys&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Определим пользователя нашего веб-сервера (в примере ниже это &lt;tt&gt;www-data&lt;/tt&gt;)&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;ps axo user,comm | grep -E '[a]pache|[h]ttpd|[_]www|[w]ww-data|[n]ginx' | grep -v root | head -1 | cut -d\  -f1
&amp;gt; www-data&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Добавляем пользователя &lt;tt&gt;deploy&lt;/tt&gt; в группу &lt;tt&gt;www-data&lt;/tt&gt; (группа веб-сервера)&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;usermod -a -G www-data deploy&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Выставляем права на папку проекта&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="bash"&gt;chown -R deploy:deploy /var/www/project
chmod -R 0775 /var/www/project&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Проверим, есть ли setfacl в системе &lt;tt&gt;setfacl -h&lt;/tt&gt; и установим, если его нет. Команда для Ubuntu:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="bash"&gt;sudo apt-get install acl&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Выдадим права на папки с кэшем, логами и загруженными пользовательскими файлами&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="bash"&gt;sudo setfacl -dR -m u:www-data:rwX -m u:deploy:rwX /var/www/project/var /var/www/project/public/uploads
sudo setfacl -R -m u:www-data:rwX -m u:deploy:rwX /var/www/project/var /var/www/project/public/uploads&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Готово.&lt;/p&gt;
&lt;p&gt;Для самого деплоймента я обычно использую &lt;a href="deployer.org"&gt;Deployer&lt;/a&gt; c параметром &lt;tt&gt;writable_mode=acl&lt;/tt&gt;.&lt;/p&gt;
</description>
</item>

<item>
<title>Фавиконки для сайтов</title>
<guid isPermaLink="false">37</guid>
<link>https://maxkuznetsov.ru/all/favicon/</link>
<pubDate>Mon, 23 Nov 2020 16:01:47 +0300</pubDate>
<author>Максим Кузнецов</author>
<comments>https://maxkuznetsov.ru/all/favicon/</comments>
<description>
&lt;p&gt;Такая мелочь, как иконка сайта в табе браузера, не является критичной, но тем не менее влият на внешний вид вашего сайта и на его респектабельность.&lt;/p&gt;
&lt;h3&gt;Проблема&lt;/h3&gt;
&lt;p&gt;Почти у каждого браузера и ОС есть свои любимые размеры и форматы. Чтобы сайт выглядел хорошо на всех устройствах, нужно нарисовать несколько иконок.&lt;/p&gt;
&lt;h3&gt;Решение&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://realfavicongenerator.net/"&gt;https://realfavicongenerator.net/&lt;/a&gt; — создаёт все эти варианты иконки по одному файлу и генерирует HTML.&lt;/p&gt;
&lt;p&gt;Наконец-то, роботы трудятся на благо человека!&lt;/p&gt;
</description>
</item>

<item>
<title>Как прокачать Vim за 1 минуту</title>
<guid isPermaLink="false">35</guid>
<link>https://maxkuznetsov.ru/all/vim-1minute-boost/</link>
<pubDate>Thu, 28 May 2020 19:02:48 +0300</pubDate>
<author>Максим Кузнецов</author>
<comments>https://maxkuznetsov.ru/all/vim-1minute-boost/</comments>
<description>
&lt;p&gt;Можно долго спорить о лучших IDE и редакторах для разработки кода, но если вы работаете в консоли или подключаетесь к серверу по SSH, то удобнее vim ничего нет. Он установлен по умолчанию на большинстве хостингов, так что имеет смысл его изучить и полюбить.&lt;/p&gt;
&lt;p&gt;Настройка vim происходит в файле ~/.vimrc. Его может не быть, это нормально, тогда нужно создать.&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Открываем ~/.vimrc&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;$ vim ~/.vimrc&lt;/code&gt;&lt;/pre&gt;&lt;ol start="2"&gt;
&lt;li&gt;Добавляем в файл следующее содержимое и сохраняем (команда «:wq»):&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;set ttyfast
set showmode
set showcmd
set title
set hidden
set ffs=unix,dos,mac

&amp;quot; Показывать нумерацию строк
set number

&amp;quot; Чтобы не было проблем с swp-файлами, которые создаются во время редактирования
set nobackup
set nowritebackup
set nowb
set noswapfile

&amp;quot; Глубина истории
set undolevels=1000

syntax on
&amp;quot; Цветовая схема
set t_Co=256

&amp;quot; monokai не идёт по умолчанию, но мы его установим чуть позже
colorscheme monokai

&amp;quot; Рисовать вертикальную линию для отображения границы в 120 символов — строки длинее хуже читаются
set colorcolumn=120
highlight ColorColumn ctermbg=238 guibg=#232728

&amp;quot; Настройка табов
set expandtab
set tabstop=4
set shiftwidth=4

&amp;quot; Отображать скрытые символы, табы и висящие пробелы
set list
set listchars=tab:→\ ,trail:·,nbsp:·&lt;/code&gt;&lt;/pre&gt;&lt;ol start="3"&gt;
&lt;li&gt;Я использую тёмную тему monokai. Её нет среди тем по умолчанию, но установить её несложно:&lt;/li&gt;
&lt;/ol&gt;
&lt;p class="remark"&gt;&lt;b style="font-family: monospace;"&gt;ls —l /usr/share/vim/vim*/colors&lt;/b&gt; покажет все предустановленные в системе темы&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;$ mkdir -p ~/.vim/colors
$ curl -o ~/.vim/colors/monokai.vim https://raw.githubusercontent.com/sickill/vim-monokai/master/colors/monokai.vim&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Имя этого файла с темой нужно указать в ~/.vimrc в строке&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;colorscheme &amp;lt;monokai&amp;gt;&lt;/code&gt;&lt;/pre&gt;</description>
</item>

<item>
<title>Как подружить docker, localhost и HTTPS</title>
<guid isPermaLink="false">34</guid>
<link>https://maxkuznetsov.ru/all/docker-localhost-and-https/</link>
<pubDate>Tue, 26 May 2020 22:29:08 +0300</pubDate>
<author>Максим Кузнецов</author>
<comments>https://maxkuznetsov.ru/all/docker-localhost-and-https/</comments>
<description>
&lt;p&gt;Когда разрабатываешь веб-сервис, который крутится на проде по HTTPS, есть большой соблазн не запариваться и работать локально по HTTP. Потому что так проще, да и в целом же всё работает плюс-минус одинаково. Если сервис простой, то скорее всего никаких проблем не всплывёт, но если сервис не монолитный и зависит от внешних сервисов или сам предоставляет АПИ клиентам, то проблемы с CORS неизбежны. И обычно эти проблемы обнаруживаются слишком поздно — либо на стейджинге, либо даже после релиза на продакшен.&lt;/p&gt;
&lt;p&gt;Браузер начинает ругаться, что запросы с HTTPS на HTTP или наоборот не безопасны, сторонние сервисы перестают отвечать, так как по HTTP работали только тестовые стенды, с которыми вы и работали.&lt;/p&gt;
&lt;p&gt;Решение простое — нужно не лениться и максимально приблизить локальную среду к боевой. Благо это задача из серии один раз сделал и используешь для всех последующих проектов.&lt;/p&gt;
&lt;h2&gt;Docker на продакшене&lt;/h2&gt;
&lt;p class="remark"&gt;
Если у вас продакшен без Docker, то вам поможет статья — &lt;a href="https://maxkuznetsov.ru/all/nginx-free-ssl/"&gt;Как получить бесплатный SSL-сертификат и установить его на Nginx&lt;/a&gt;.
&lt;/p&gt;
&lt;p&gt;В этой статье я рассматриваю только локальную сборку через docker-compose, потому что на продакшене, как ни странно, это немного проще. Достаточно выполнить понятную инструкцию &lt;a href="https://certbot.eff.org/lets-encrypt/ubuntuxenial-nginx"&gt;по настройке Certbot&lt;/a&gt; и/или добавить соответствующий  &lt;a href="https://hub.docker.com/r/certbot/certbot/"&gt;докер-образ с Docker Hub&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Docker на localhost&lt;/h2&gt;
&lt;p class="remark"&gt;См. как запустить сайт через docker-compose на примере &lt;a href="https://maxkuznetsov.ru/all/egeya-docker/"&gt;докеризации Эгеи&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;На локалхосте Certbot не подойдёт, так как ему нужно реально существующее доменное имя и доступный через интернет сайт. Поэтому прописать локальный адрес в /etc/hosts не поможет. Решение — выпустить свой самоподписанный сертификат. Да, он не будет приниматься никем, кроме нашей системы, но для локальной разработки этого и не нужно.&lt;/p&gt;
&lt;h3&gt;Создаём доменное имя&lt;/h3&gt;
&lt;p&gt;Во-первых, создадим локально доменное имя, чтобы оно было похожим на боевое, а не обычный и скучный localhost. Например, docker.loc для основного сайта и api.docker.loc для API:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;$ sudo vim /etc/hosts
127.0.0.1       localhost
127.0.0.1       docker.loc
127.0.0.1       api.docker.loc
...&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Если для вашего сервиса необходимо несколько доменов, то пропишите их ниже по аналогии.&lt;/p&gt;
&lt;h3&gt;Выпускаем свой SSL-сертификат&lt;/h3&gt;
&lt;p&gt;Для реальных сайтов SSL-сертификаты выдаются специальными сертификационными центрами за деньги. За это они потом могут подтвердить верность домена по запросу любого браузера. Мы же выпустим свой сертификат и сами его подтвердим.&lt;/p&gt;
&lt;p&gt;SSL-сертификат создаётся одной командой и состоит из двух частей: публичного сертификата и секретного ключа.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="bash"&gt;openssl req -x509 -out docker.loc.crt -keyout docker.loc.key \
  -newkey rsa:2048 -nodes -sha256 \
  -subj '/CN=docker.loc' -extensions EXT -config &amp;lt;( \
   printf &amp;quot;[dn]\nCN=docker.loc\n[req]\ndistinguished_name = dn\n[EXT]\nsubjectAltName=DNS:docker.loc\nkeyUsage=digitalSignature\nextendedKeyUsage=serverAuth&amp;quot;)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Файлы docker.loc.crt и docker.loc.key будут сохранены в той папке, откуда вы запускаете команду.&lt;/p&gt;
&lt;h3&gt;Добавляем сертификаты в ОС&lt;/h3&gt;
&lt;p&gt;Ниже пример, как это сделать на Mac OS X.&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Найдите сертификат docker.loc.crt в Finder (или можно в нужной папке консоли ввести «open .»)&lt;/li&gt;
&lt;li&gt;Дважды кликните по нему, появится окно.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://maxkuznetsov.ru/pictures/Screen-Shot-2020-05-26-at-22.04.11.png" width="1294" height="834" alt="" /&gt;
&lt;/div&gt;
&lt;ol start="3"&gt;
&lt;li&gt;Введите пароль администратора&lt;/li&gt;
&lt;li&gt;Теперь нужно найти этот сертификат в Keychain Access и выбрать Trust Always&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://maxkuznetsov.ru/pictures/Screen-Shot-2020-05-26-at-22.09.28.png" width="1870" height="1386" alt="" /&gt;
&lt;/div&gt;
&lt;h3&gt;Добавляем сертификаты в docker-compose сборку&lt;/h3&gt;
&lt;p&gt;Сертификаты нужно положить в контейнер с веб-сервером. Я приведу пример для Nginx и сборки по типу &lt;a href="https://github.com/pluseg/e2-docker."&gt;https://github.com/pluseg/e2-docker.&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Расположение сертификатов и конфигов&lt;/h3&gt;
&lt;p&gt;В этой сборке папки из app/docker/nginx монтируются прямо в nginx-контейнер. Поэтому удобно расположить файлы так:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;&amp;lt;project root&amp;gt;
- app
--- docker
----- nginx
------- conf.d
--------- docker.loc.conf
------- ssl
--------- docker.loc.crt
--------- docker.loc.key&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Пример docker.loc.conf (проект на PHP + Symfony)&lt;/h3&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="yaml"&gt;# лучше с php соединять через socket
upstream php-upstream { server web:9000; }

# Редиректим HTTP на HTTPS
server {
       listen         80;
       listen    [::]:80;
       server_name    docker.loc;
       return         301 https://$server_name$request_uri;
}

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    root /var/www/html/docs;

    server_name            docker.loc;
    ssl_certificate          /etc/nginx/ssl/docker.loc.crt;
    ssl_certificate_key   /etc/nginx/ssl/docker.loc.key;
    ssl_protocols           TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         EECDH+CHACHA20:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5;

    location / {
    	try_files $uri /index.php$is_args$args;
    }

    location ~ ^/(index|app|app_dev|config)\.php(/|$) {
        fastcgi_pass php-upstream;
        fastcgi_split_path_info ^(.+\.php)(/.*)$;
        include fastcgi_params;
        fastcgi_param  SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param DOCUMENT_ROOT $realpath_root;
        fastcgi_read_timeout 300;
    }

    error_log /var/log/nginx/docker.loc-error.log;
    access_log /var/log/nginx/docker.loc-access.log;
}&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Пример docker-compose.yml&lt;/h3&gt;
&lt;p&gt;Добавляем 443 порт и несколько volume с конфигами и сертификатами.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="yaml"&gt;version: '3.2'
services:
    ...
    nginx:
        image: nginx:latest
        ports:
            - 80:80
            - 443:443
        volumes:
            - ./app/docker/nginx/conf.d:/etc/nginx/conf.d
            - ./app/docker/nginx/ssl:/etc/nginx/ssl
            - ./:/var/www/html
            - ./var/logs/nginx:/var/log/nginx
        command: /bin/bash -c &amp;quot;exec nginx -g 'daemon off;'&amp;quot;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Запускаем и проверяем&lt;/h3&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;docker-compose up -d --build&lt;/code&gt;&lt;/pre&gt;</description>
</item>

<item>
<title>Как установить расширение в Postgres</title>
<guid isPermaLink="false">6</guid>
<link>https://maxkuznetsov.ru/all/postgres-install-extension/</link>
<pubDate>Sat, 02 May 2020 00:05:16 +0300</pubDate>
<author>Максим Кузнецов</author>
<comments>https://maxkuznetsov.ru/all/postgres-install-extension/</comments>
<description>
&lt;p&gt;Переключаемся в юзера Postgres:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;$ su postgres&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Заходим в базу и выполняем следующие команды (на примере установки uuid-ossp):&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="sql"&gt;ALTER USER myuser WITH SUPERUSER;
CREATE EXTENSION IF NOT EXISTS &amp;quot;uuid-ossp&amp;quot;;
ALTER USER myuser WITH NOSUPERUSER;&lt;/code&gt;&lt;/pre&gt;</description>
</item>

<item>
<title>Как быстро изучить Git с нуля</title>
<guid isPermaLink="false">30</guid>
<link>https://maxkuznetsov.ru/all/git-intro/</link>
<pubDate>Tue, 28 Apr 2020 16:22:21 +0300</pubDate>
<author>Максим Кузнецов</author>
<comments>https://maxkuznetsov.ru/all/git-intro/</comments>
<description>
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://maxkuznetsov.ru/pictures/git-intro.png" width="1748" height="692" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Git — это система версионирования документов. Она позволяет пользователям создавать несколько параллельных версий, независимо управлять ими и сливать друг в друга с автоматическим нахождением конфликтных строк.&lt;/p&gt;
&lt;p&gt;Когда документ правит один человек, то никаких проблем не возникает, хотя иногда хочется видеть историю изменений с возможностью вернуться на предыдущую версию. А что, если над документом работают два человека?&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;— Вась, только что залил новую версию отчёта. Добавил одну страницу в третью главу.&lt;br /&gt;
— Ёпт, а я третью главу уже с нуля переписал...&lt;br /&gt;
— ...&lt;br /&gt;
— Ладно, давай подходи к моему компу со своим файлом, счас вместе глянем как это разрулить.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Раньше приходилось либо разбивать такой документ на несколько, чтобы можно было менять каждую часть независимо, либо договариваться, кто что правит, либо слать файл со своими правками, а потом ждать, когда человек пришлёт версию с его внесёнными правками.&lt;/p&gt;
&lt;p&gt;А что, если документ содержит сто страниц и его будут править десять человек?&lt;/p&gt;
&lt;h2&gt;Git&lt;/h2&gt;
&lt;p class="remark"&gt;&lt;b&gt;Первая версия Git&lt;/b&gt; была создана Линусом Торвальдсом — автором операционной системы Linux. Собственно, с помощью Git она и разрабатывалась.&lt;/p&gt;
&lt;p&gt;Первыми с проблемой параллельного редактирования файлов столкнулись программисты, они и разработали решение — Git. Он позволяет хранить файлы в общем хранилище, а также каждому участнику создавать их копию локально. Можно менять файлы, создавать свои версии, откатываться на предыдущие, а потом отправлять финальную версию в хранилище. Гит самостоятельно объединит версии разных людей, либо укажет на конфликт, если, например, оба человека изменили одну и ту же строку. По такому же принципу работает Google Docs и подобные сервисы.&lt;/p&gt;
&lt;h3&gt;Курсы&lt;/h3&gt;
&lt;p&gt;Для изучения Git достаточно изучить&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;[4h] &lt;a href="https://githowto.com/ru"&gt;Githowto&lt;/a&gt; — курс на русском, нужно пройти хотя бы первые 38 уроков, чтобы можно было использовать git в реальной жизни.&lt;/li&gt;
&lt;li&gt;[15m] &lt;a href="https://danielkummer.github.io/git-flow-cheatsheet/index.ru_RU.html"&gt;Шпаргалка по gitflow&lt;/a&gt; — способ организации веток, чтобы в них не запутаться на средних и больших проектах.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;В Githowto сразу же рассказывают, как установить git. Иногда git уже включён в систему по умолчанию, поэтому можно и не устанавливать. Проверить его наличие можно командой в терминале:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;$ git --version
git version 2.24.1 (Apple Git-126)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Если ничего не вывелось, значит git не установлен.&lt;/p&gt;
&lt;h3&gt;Вопросы для самопроверки после курса&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Что такое unstaged, staged и commited состояния у файлов?&lt;/li&gt;
&lt;li&gt;Что такое ветки, как создавать их, переключаться между ними и объединять?&lt;/li&gt;
&lt;li&gt;Как нужно именовать ветки согласно gitflow (master, develop, feature, hotfix, release)?&lt;/li&gt;
&lt;li&gt;В каких случаях git самостоятельно сливает файлы, а в каких создаёт конфликты?&lt;/li&gt;
&lt;li&gt;Как выглядит конфликт и что нужно сделать, чтобы его разрешить?&lt;/li&gt;
&lt;li&gt;Как откатить изменения закоммиченного файла?&lt;/li&gt;
&lt;li&gt;Как откатить изменения закоммиченного и запушенного в удалённый реп файла?&lt;/li&gt;
&lt;li&gt;Что такое git stash и как им пользоваться?&lt;/li&gt;
&lt;li&gt;Что такое репозитории — локальные и удалённые? Как склонировать репозиторий? Как запушить в реп?&lt;/li&gt;
&lt;li&gt;Для каких файлов GIT подходит, а с какими работает плохо: текстовые и бинарные файлы?&lt;/li&gt;
&lt;li&gt;Как исключить файлы из-под контроля Git (.gitignore)?&lt;/li&gt;
&lt;li&gt;Установите алиасы для частых комманд: git ci, git st, git hist, git br, git co.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Дополнительная практика [1h]&lt;/h2&gt;
&lt;p&gt;Для начала создайте папку проекта с двумя файлами в разметке Markdown. Если не работали с markdown, то считайте их обычными текстовыми файлами.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;README.md&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="markdown"&gt;# Знакомство с GIT
Есть отличные курсы:
- http://gitimmersion.com/
- https://githowto.com/ru

А также статья в блоге https://maxkuznetsov.ru/.&lt;/code&gt;&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;CHANGELOG.md&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="markdown"&gt;- Изучен курс GitHowTo
- Ознакомился с Git flow подходом
- Прочитал статью https://maxkuznetsov.ru/all/git-intro и сделал задания&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Задачи&lt;/h3&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Добавьте поддержку Git в этот проект. Сделайте первый коммит, включающий оба этих файла.&lt;/li&gt;
&lt;li&gt;Создайте от мастера новую feature/md-updates-vasya.  &lt;br /&gt;
&lt;i&gt;В реальной жизни master используется только для продакшена, а для основной разработки — ветка develop. Новые правки лучше всегда делать в отдельной feature/* ветке, а потом сливать её в develop или master, если вы работаете без develop.&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;Зарегистрируйтесь в github.com и создайте пустой репозиторий.&lt;/li&gt;
&lt;li&gt;Запушьте локальный репозиторий в созданный удалённый репозиторий.&lt;/li&gt;
&lt;li&gt;Измените первые строки в обоих файлах. Сделайте коммит, включив в него только изменения из CHANGELOG.md.  &lt;br /&gt;
&lt;i&gt;Случай из жизни: мы сделали кучу изменений в файлах, а потом поняли, что надо бы сохранить последнюю рабочую версию, но без последних лишних измнений.&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;Откатите (soft) последний коммит и сделайте новый, но уже с изменениями из обоих файлов. Попробуйте также вариант без отката: «git commit —amend».&lt;/li&gt;
&lt;li&gt;Запушьте ветку в удаленный репозиторий и создайте там Pull Request в master.&lt;/li&gt;
&lt;li&gt;Создайте локально новую папку, куда склонируйте ваш удалённый репозиторий. У вас должно быть на компьютере две папки, смотрящие на один и тот же репозиторий. Этим мы имитируем ситуацию, когда два человека с разных компьютеров работают с одним репом. Назовём эти два локальных репозитория: первый — репозиторий Васи, второй — Пети.&lt;/li&gt;
&lt;li&gt;В новом репо Пети создайте ветку feature/md-updates-petya из master.&lt;/li&gt;
&lt;li&gt;Измените первые строки в обоих файлах (иначе, чем в feature/md-updates-vasya), закоммитьте, запушьте и создайте PullRequest на Github.&lt;/li&gt;
&lt;li&gt;В интерфейсе Github примите первый Pull-Request из ветки Васи, а потом убедитесь, что получили конфликт в Пулл-реквесте от Пети.  &lt;br /&gt;
&lt;i&gt;Так часто бывает в жизни, когда над одним кодом работает больше одного человека. Чем больше команда, тем чаще конфликты.&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;Разрулите конфликт в локальном репозитории в ветке Пети и обновите Пулл-реквест.&lt;/li&gt;
&lt;li&gt;Примите в github.com обновлённый Пулл-реквест от Пети.&lt;/li&gt;
&lt;li&gt;В репозитории Васи переключитесь в master и сделайте git pull. Убедитесь, что ветка feature/md-updates-vasya удалена.&lt;/li&gt;
&lt;li&gt;Оказалось, что вмёрдженная ветка от Пети содержала баги, поэтому нужно её откатить. Откатите её в репозитории Пети с помощью revert и c полной перезаписью публичной истории. Можно делать без Пулл-реквеста в этот раз.&lt;/li&gt;
&lt;li&gt;Посмотрите, как изменилась история в репозитории Васи до git pull и после git pull.&lt;/li&gt;
&lt;li&gt;В репозитории Васи добавьте в .gitignore файл CHANGELOG.md, чтобы Git перестал его отслеживать. (Подсказка: «git rm» с флагом «—cached».) Запушьте в удалённый реп.&lt;/li&gt;
&lt;li&gt;Зайдите в Github и проверьте, что CHANGELOG.md больше нет в репозитории.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;GUI клиенты&lt;/h2&gt;
&lt;p&gt;В реальной жизни работать с гитом через консоль приходится редко. Есть клиенты Git с визуальным интерфейсом, например:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;встроенные в IDE&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.sourcetreeapp.com/"&gt;Sourcetree&lt;/a&gt; — мой личный выбор&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.gitkraken.com/"&gt;Git Kraken&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://tortoisegit.org/"&gt;Git Tortoise&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Но ими проще пользоваться, когда ты понимаешь, что они делают под капотом.&lt;/p&gt;
</description>
</item>

<item>
<title>Запускаем Эгею через Docker и забываем про технические проблемы на 10 лет вперёд</title>
<guid isPermaLink="false">29</guid>
<link>https://maxkuznetsov.ru/all/egeya-docker/</link>
<pubDate>Tue, 28 Apr 2020 01:37:47 +0300</pubDate>
<author>Максим Кузнецов</author>
<comments>https://maxkuznetsov.ru/all/egeya-docker/</comments>
<description>
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://maxkuznetsov.ru/pictures/Screen-Shot-2020-04-28-at-01.29.37.png" width="1205" height="658" alt="" /&gt;
&lt;/div&gt;
&lt;p class="remark"&gt;&lt;b&gt;Про работу с Docker&lt;/b&gt; можно посмотреть &lt;a href="https://maxkuznetsov.ru/all/docker-basic/"&gt;часовое видео&lt;/a&gt;. С Docker вам не нужно заботиться о конфигурациях веб-сервера, базы данных и правах доступа к файлам и папкам. Одна команда — и готово.&lt;/p&gt;
&lt;p&gt;В официальной документации Эгеи есть две ссылки на запуск блога с помощью Докера, но оба способа имеют свои недостатки. По крайней мере один из них устарел и просто не заведётся из коробки, плюс используется старый, медленный ПХП 5, а второй использует веб-сервер caddle, который не столь популярен и хорошо описан, как Nginx или официально поддерживаемый Эгеей Apache.&lt;/p&gt;
&lt;h2&gt;Блог за 5 шагов&lt;/h2&gt;
&lt;p&gt;Вот, что вам нужно, чтобы установить последнюю версию блога.&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Установленные на целевой машине docker и docker-compose — это может быть и локальная машина, и продакшн. А также наличие домена, если поднимаете на продакшене.&lt;/li&gt;
&lt;li&gt;Склонировать репозиторий &lt;a href="https://github.com/pluseg/e2-docker"&gt;https://github.com/pluseg/e2-docker&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;git clone git@github.com:pluseg/e2-docker.git /path/to/blog/folder&lt;/code&gt;&lt;/pre&gt;&lt;ol start="3"&gt;
&lt;li&gt;Создать файл .env&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;cp .env.example .env&lt;/code&gt;&lt;/pre&gt;&lt;ol start="4"&gt;
&lt;li&gt;И указать в нём нужные версии PHP, MySQL, Эгеи (на сегодняшний день они актуальные и их менять и не надо) и домен VIRTUAL_HOST (для локали можно оставить localhost.&lt;/li&gt;
&lt;li&gt;Запускаем всё это хозяйство с помощью docker-compose&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;docker-compose up -d --build&lt;/code&gt;&lt;/pre&gt;&lt;ol start="5"&gt;
&lt;li&gt;Заходим на домен, указанный в VIRTUAL_HOST, и заполняем небольшую форму&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;DB Host: db
   DB User: root
   DB Pass: egeya
   DB Database: &amp;lt;выбрать из списка &amp;quot;egeya&amp;quot;&amp;gt;
   Admin pass: &amp;lt;укажите ваш админский пароль&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;ol start="6"&gt;
&lt;li&gt;Готово.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Дополнительные плюшки&lt;/h2&gt;
&lt;p&gt;В этой сборке я вынес версии всех зависимостей в .env файл, так что теперь обновить версию блога, ПХП или mysql — дело одной минуты. Достаточно обновить их в .env файле и перезапустить контейнеры с флагом пересборки:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;docker-compose up -d --build&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Если понадобится перенести блог в другое место — не проблема. Достаточно перенести папку с блогом куда нужно и запустить ту же самую команду docker-compose.&lt;/p&gt;
&lt;p&gt;Все файлы, которые должны сохраниться между обновлениями сохранятся. Они лежат в папке ./data:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;data/pictures — картинки&lt;/li&gt;
&lt;li&gt;data/user — всё, что касается пользователя, включая shared шаблоны и настройки&lt;/li&gt;
&lt;li&gt;data/theme — ваша кастомная тема, если понадобится. Она добавляется в папку к остальным комплектным темам /themes&lt;/li&gt;
&lt;li&gt;data/mysql — данные из базы&lt;/li&gt;
&lt;/ul&gt;
</description>
</item>

<item>
<title>Удерживаем читателей блога, увеличиваем ретеншн</title>
<guid isPermaLink="false">27</guid>
<link>https://maxkuznetsov.ru/all/adding-email-subscription/</link>
<pubDate>Tue, 14 Apr 2020 23:13:32 +0300</pubDate>
<author>Максим Кузнецов</author>
<comments>https://maxkuznetsov.ru/all/adding-email-subscription/</comments>
<description>
&lt;p&gt;Одна из проблем всех self-hosted блогов, что их приходится самостоятельно раскручивать. Даже если вы хорошо пишете и вас приходят читать, вы не сможете удержать читателей. Они просто не узнают о том, что у вас что-то вышло.&lt;/p&gt;
&lt;p&gt;Есть несколько решений.&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Завести аккаунты в соц.сетях и постить там анонсы статей. Тогда читатели блога смогут узнавать о ваших статьях в удобной платформе и приходить их дочитывать к вам в блог.&lt;/li&gt;
&lt;li&gt;Сделать емейл подписку, чтобы периодически слать подборку новых статей.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Оба способа хороши и стоит использовать их оба. Но в этой статье покажу, как добавить в Эгею блок с подпиской на емейл рассылку и как управлять ею.&lt;/p&gt;
&lt;h2&gt;Выбор сервиса по рассылке емейлов&lt;/h2&gt;
&lt;p&gt;Для начала нам нужно зарегистрироваться в любом сервисе, работающего с емейл-рассылками. При выборе сервиса важно понимать, что емейлы бывают&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;транзакционными — это письма для конкретного пользователя. Например, подтверждение регистрации, письмо о произошедших с его аккаунтом событиях, о новом сообщении.&lt;/li&gt;
&lt;li&gt;маркетинговыми — массовые рассылки одного письма. Например, новостная рассылка сайтов, емейл-курс Максима Ильяхова «Сильный текст». В таких письмах нет ничего персонального, но они и не для этого.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Нам нужно, чтобы сервис умел отправлять именно маркетинговые рассылки. Тогда нашу рассылку никто не заблочит, а мы ещё сможем и аналитику проводить.&lt;/p&gt;
&lt;p class="remark"&gt;
&lt;b&gt;Mailchimp&lt;/b&gt; — один из лидеров рынка емейл рассылок. У них есть тысячи интеграций как с популярными движками, CMS, платформами и соц.сетями, так и с частными сайтами, что нам и подходит.
&lt;/p&gt;
&lt;p&gt;Я выбрал Mailchimp. Для небольшого блога достаточно бесплатного плана: 2000 подписчиков на рассылку, которым можно слать 10 тысяч писем каждый месяц. Кроме того, у него много интеграций, кастомизаций, есть удобный функционал по управлению подписчиками и созданию емейлов в онлайн редакторе. И возможность подписчикам отписываться от рассылки в любой момент — это важно, так как спам никто не любит. Вот моя честная реферральная ссылка — &lt;a href="http://eepurl.com/gZRNX1,"&gt;http://eepurl.com/gZRNX1,&lt;/a&gt; мне будет приятно, если вы зарегистрируетесь по ней.&lt;/p&gt;
&lt;h2&gt;Регистрация в Mailchimp&lt;/h2&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Указываете емейл, юзернейм пароль.&lt;/li&gt;
&lt;li&gt;Вам приходит письмо для подтверждением емейла, проходим по ссылке из него.&lt;/li&gt;
&lt;li&gt;Выбираем бесплатный план (справа).&lt;/li&gt;
&lt;li&gt;Дальше нас спросят имя и фамилию, а затем название бизнеса. Это название будет показываться во всех формах с подпиской/отпиской на вашу рассылку, поэтому укажите что-то вразумительное. Я указал: «Блог Макса Кузнецова»&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://maxkuznetsov.ru/pictures/mailchimp1.png" width="1856" height="1430" alt="" /&gt;
&lt;/div&gt;
&lt;ol start="5"&gt;
&lt;li&gt;Удивительно, но им нужен ещё и адрес с индексом. В принципе, можно указывать что угодно, в формах мы потом это скроем.&lt;/li&gt;
&lt;li&gt;Шаг с загрузкой контактов, которые уже готовы подписаться на нашу рассылку. Скорее всего им придёт письмо с просьбой подтвердить этот факт.&lt;/li&gt;
&lt;li&gt;Ещё пара вопросов и всё готово.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Настройка Mailchimp&lt;/h2&gt;
&lt;p&gt;Нам нужно зайти в Audience, а затем в Settings.&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://maxkuznetsov.ru/pictures/mailchimp2.png" width="2440" height="1548" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Дальше выбираем Signup forms. Здесь нам нужно кастомизировать и перевести все возможные формы на русский (если блог русскоязычный). Для этого зайдём в Form Builder и по очереди будем выбирать шаблоны форм в выпадающем списке. Нам нужны все формы в Subscribe и Unsubscribe разделе. В разделе Build можно увидеть предпросмотр и убрать или добавить лишние поля. В раздале Translate It — указать переводы всех текстов с английского на любой другой. В принципе, можно оставить английский вариант или авто-перевод на русский, но выглядит это не айс, местами хуже гугл-транслейта:&lt;/p&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://maxkuznetsov.ru/pictures/mailchimp3.png.jpg" width="2560" height="2094" alt="" /&gt;
&lt;/div&gt;
&lt;p&gt;Возвращаемся обратно в Signup forms, заходим в Embedded forms и выбираем в верхних полях предустановленный вид, который подходит нам. Нам подойдёт Horizontal — мы её кастомизируем под нас чуть позже. А сейчас копируем весь код из поля «Copy/paste onto your site».&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Обратите внимание на раздел Signup Forms &gt; Subscriber Pop-up. Он позволяет добавить небольшой javascript-код на ваш сайт &lt;a href="https://maxkuznetsov.ru/all/egeya-metrika-n-analytics/"&gt;подобно Яндекс.Метрике или Гугл Аналитике&lt;/a&gt;, а затем управлять видом и структурой формы и условиями её показа прямо из Мейлчимпа. Вполне удобно, но я против назойливых попапов. Кроме того, если пользователь его скроет, то он больше не сможет подписаться на вашу рассылку позже, если передумает. Поэтому я предлагаю разместить статичный блок с подпиской в футере&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Добавляем блок с формой подписки в Эгею&lt;/h2&gt;
&lt;p&gt;Само собой, вы можете вставить код с формой куда угодно и стилизовать его, как вы считаете нужным. Я покажу, как вставить его в Эгее чуть выше футера, чтобы этот блок появлялся на каждой странице, включая внутренние.&lt;/p&gt;
&lt;p&gt;Нам нужно создать новый файл footer-pre.tmpl.php и залить его на сервер с блогом (по ftp или с помощью scp) в папку /путь/до/блога/user/extras/.&lt;/p&gt;
&lt;p&gt;Внутри этого файла помещаем тот код, что мы скопировали в MailChimp, только меняем стили внутри тега style, а также текст внутри тега label и подпись на кнопке Subscribe. У меня получилось так:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="html"&gt;&amp;lt;link href=&amp;quot;//cdn-images.mailchimp.com/embedcode/horizontal-slim-10_7.css&amp;quot; rel=&amp;quot;stylesheet&amp;quot; type=&amp;quot;text/css&amp;quot;&amp;gt;
&amp;lt;style type=&amp;quot;text/css&amp;quot;&amp;gt;
  #mc_embed_signup{
    background:antiquewhite; 
    clear:left; 
    font:14px Helvetica,Arial,sans-serif; 
    width:100%;
    margin: 0 0 40px 0; 
    padding: 20px;
  }
  #mc_embed_signup label {
    font-weight: 400;
    line-height: 24px;
  }
  #mc_embed_signup input[type=submit] {
    text-align: center;
  }
}
&amp;lt;/style&amp;gt;
&amp;lt;div id=&amp;quot;mc_embed_signup&amp;quot;&amp;gt;
&amp;lt;form action=&amp;quot;ВАША ССЫЛКА&amp;quot; method=&amp;quot;post&amp;quot; id=&amp;quot;mc-embedded-subscribe-form&amp;quot; name=&amp;quot;mc-embedded-subscribe-form&amp;quot; class=&amp;quot;validate&amp;quot; target=&amp;quot;_blank&amp;quot; novalidate=&amp;quot;&amp;quot;&amp;gt;
    &amp;lt;div id=&amp;quot;mc_embed_signup_scroll&amp;quot;&amp;gt;
  &amp;lt;label for=&amp;quot;mce-EMAIL&amp;quot;&amp;gt;Оставьте свой емейл, чтобы изредка получать подборку новых статей&amp;lt;/label&amp;gt;
  &amp;lt;input type=&amp;quot;email&amp;quot; value=&amp;quot;&amp;quot; name=&amp;quot;EMAIL&amp;quot; class=&amp;quot;email&amp;quot; id=&amp;quot;mce-EMAIL&amp;quot; placeholder=&amp;quot;Email&amp;quot; required=&amp;quot;&amp;quot;&amp;gt;
    &amp;lt;!-- real people should not fill this in and expect good things - do not remove this or risk form bot signups--&amp;gt;
    &amp;lt;div style=&amp;quot;position: absolute; left: -5000px;&amp;quot; aria-hidden=&amp;quot;true&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;text&amp;quot; name=&amp;quot;b_ed0df51ffed55c354e5eeb476_d4231642b3&amp;quot; tabindex=&amp;quot;-1&amp;quot; value=&amp;quot;&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;div class=&amp;quot;clear&amp;quot;&amp;gt;&amp;lt;input type=&amp;quot;submit&amp;quot; value=&amp;quot;Подписаться&amp;quot; name=&amp;quot;subscribe&amp;quot; id=&amp;quot;mc-embedded-subscribe&amp;quot; class=&amp;quot;button&amp;quot;&amp;gt;&amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Результат&lt;/h2&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://maxkuznetsov.ru/pictures/mailchimp4.png.jpg" width="2560" height="1497" alt="" /&gt;
&lt;/div&gt;
&lt;h2&gt;Что дальше&lt;/h2&gt;
&lt;p&gt;Теперь вам нужно продолжать писать интересные и полезные статьи, а раз в неделю-две-месяц вы сможете через интерфейс Mailchimp отправлять всем подписавшимся письмо с подборкой последних интересных статей. И не расстраивайтесь, если будут отписки, в этом нет ничего страшного. Ваш читатель всё равно вас найдёт.&lt;/p&gt;
&lt;p&gt;Пишите полезно, пишите вкусно.&lt;/p&gt;
</description>
</item>

<item>
<title>Эгея. Как починить комментарии, если у вас Nginx</title>
<guid isPermaLink="false">26</guid>
<link>https://maxkuznetsov.ru/all/egeya-comments-fix/</link>
<pubDate>Tue, 14 Apr 2020 15:17:56 +0300</pubDate>
<author>Максим Кузнецов</author>
<comments>https://maxkuznetsov.ru/all/egeya-comments-fix/</comments>
<description>
&lt;p&gt;Если вы подняли блог на Nginx по документации Эгеи, то вероятнее всего у вас не работает авторизация через кнопки соц.сетей в комментариях. Даже если визуально всё работает, попробуйте авторизоваться там сами. Это критично, если у вас в настройках выбрано «комментировать могут только авторизовавшиеся пользователи», тогда вам даже не смогут написать про эту проблему.&lt;/p&gt;
&lt;h3&gt;Решение&lt;/h3&gt;
&lt;ol start="1"&gt;
&lt;li&gt;Обновляем одну строку в файле Nginx-конфига, соответствующего блогу на Эгее.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;location / {
    log_not_found off;
    # Было:
    # try_files $uri $uri/ /index.php?go=$uri;
    # Стало:
      try_files $uri $uri/ /index.php?go=$uri&amp;amp;$query_string;
  }&lt;/code&gt;&lt;/pre&gt;&lt;ol start="2"&gt;
&lt;li&gt;Перезагружаем веб-сервер.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="bash"&gt;$ nginx -s reload&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;Причина&lt;/h3&gt;
&lt;p&gt;Эгея обрабатывает все запросы путём пробрасывания через фронт-контроллер index.php. При этом первоначальный URI передаётся как GET-параметр «?go=».&lt;/p&gt;
&lt;blockquote&gt;
&lt;div class="e2-text-table"&gt;
&lt;table cellpadding="0" cellspacing="0" border="0"&gt;
&lt;tr&gt;
&lt;td style="text-align: center"&gt;Исходный запрос&lt;/td&gt;
&lt;td style="text-align: center"&gt;Обработанный запрос&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/tags/&lt;/td&gt;
&lt;td&gt;/index.php?go=/tags/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/all/how-web-works/&lt;/td&gt;
&lt;td&gt;/index.php?go=all/how-web-works/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/sign-in-done/vk/&lt;span style="color: red;"&gt;?data={«user»: ...}&lt;/span&gt;&lt;/td&gt;
&lt;td&gt;/index.php?go=sign-in-done/vk/&lt;/td&gt;
&lt;/tr&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;/blockquote&gt;
&lt;p&gt;В последнем случае исходный запрос уже содержит GET-параметры, которые до php не дойдут.&lt;/p&gt;
&lt;p&gt;Подозреваю, что из-за этого же бывали баги с загрузкой и удалением уже прикреплённых в постах картинок.&lt;/p&gt;
&lt;p&gt;ps.: большое спасибо Евгению Степанищеву, что обнаружил этот баг и не поленился написать мне на почту.&lt;/p&gt;
</description>
</item>

<item>
<title>Что такое HTTP, или как браузеры общаются с веб-серверами</title>
<guid isPermaLink="false">25</guid>
<link>https://maxkuznetsov.ru/all/http-basics/</link>
<pubDate>Tue, 14 Apr 2020 04:07:59 +0300</pubDate>
<author>Максим Кузнецов</author>
<comments>https://maxkuznetsov.ru/all/http-basics/</comments>
<description>
&lt;p&gt;В статье про &lt;a href="https://maxkuznetsov.ru/all/how-web-works/"&gt;устройство веба и как происходит серфинг&lt;/a&gt; я упомянул, что браузер отправляет запрос к веб-серверу. Но что представляет из себя запрос? Это куча машиночитаемых квантовых кодов и сингулярных шифров? Программистская магия? Вовсе нет.&lt;/p&gt;
&lt;h2&gt;Что такое HTTP&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;HTTP — это протокол передачи данных для клиент-серверных приложений.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Не нужно пугаться слова «протокол». Это значит лишь то, что разработчики встретились и договорились о возможных форматах запросов и ответов на них. Вокруг нас множество таких протоколов-договорённостей.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;При встрече на протянутую руку принято отвечать рукопожатием. Отсутствие рукопожатия — это тоже ответ, иногда даже более красноречивый, чем само рукопожатие.&lt;/li&gt;
&lt;li&gt;Девушкам же руку не протягивают — это тоже часть протокола. Можно и им руку протянуть, но в большинстве случаев не поймут, а в некоторых странах заставят жениться.&lt;/li&gt;
&lt;li&gt;Электрические розетки — хотя &lt;a href="https://otvet.imgsmail.ru/download/c90b7eb20d34567e912652b26efddca4_i-1583.jpg"&gt;в разных странах они разные&lt;/a&gt;, внутри одной страны они одинаковы.&lt;/li&gt;
&lt;li&gt;Разъёмы для кабелей — USB type B, USB type C, mini USB, micro USB. Производители приняли внегласный протокол и производят кабели и устройства именно таких форматов, иначе при прочих равных пользователи их не поймут и не будут покупать их продукцию (исключение — Apple).&lt;/li&gt;
&lt;li&gt;Правила дорожного движения — знаки, разметка и светофоры помогают пешеходам дойти, а автомобилистам доехать до места назначения без происшествий.&lt;/li&gt;
&lt;li&gt;Формы налоговых деклараций и прочих бюрократических документов.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Любой из протоколов нас ни к чему не обязывает, это не ГОСТ, он лишь рекомендует поступать так или иначе, если мы хотим добиться желаемой цели — понимания от окружающих людей, одобрения от покупателей, сохранения продаж, избежания аварий и штрафов, получения веб-страницы от сервера.&lt;/p&gt;
&lt;p class="remark"&gt;HTTP — это набор некоторых правил, которым должны следовать клиенты и сервера, если они хотят, чтобы их правильно поняли.&lt;/p&gt;
&lt;p&gt;HTTP-клиентами чаще всего являются браузеры — Google Chrome, Mozilla Firefox, Safari, Opera, Yandex Browser и другие. А серверами являются веб-сервера. Вот эта приставка «веб-» и указывает на то, что это не просто какой-то сервер, а сервер, который умеет принимать запросы и отвечать на них по протоколу HTTP. Как он будет устроен внутренне — не определено и не важно. Наиболее популярными в мире веб-серверами являются Nginx, Apache2, но вы можете написать и свой — в некоторых языках это делается крайне легко, см. &lt;a href="https://gobyexample.com/http-servers"&gt;пример на Golang&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Прикидываемся браузером, или делаем HTTP-запрос из терминала&lt;/h2&gt;
&lt;p&gt;Чтобы понять, как браузер общается с сервером, нужно думать как браузер, нужно стать браузером.&lt;/p&gt;
&lt;p&gt;Попробуем обратиться к веб-странице &lt;a href="http://http.maxkuznetsov.ru"&gt;http://http.maxkuznetsov.ru&lt;/a&gt; так, как это делают браузеры под капотом. Для этого отправим запрос из терминала/командной строки с помощью утилиты netcat. Чаще всего она установлена по умолчанию: в Mac OS X — это «nc», в других ОС может быть «ncat» или «netcat». (Или воспользуйтесь онлайн-сервисом &lt;a href="https://reqbin.com/u4178vu3,"&gt;https://reqbin.com/u4178vu3,&lt;/a&gt; в котором слева и справа выберите табы Raw для отображения «голых» запросов и ответов. Но из терминала получится нагляднее.)&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="bash"&gt;nc http.maxkuznetsov.ru 80&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Дальше ничего не произойдёт, терминал подвиснет — это нормально. Команда netcat подключилась к серверу по адресу http.maxkuznetsov.ru к 80-му порту, и сервер ждёт от нас текст запроса.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;i&gt;Порты&lt;/i&gt; — это как номера квартир в доме. Чтобы доставить письмо, почтальону нужно знать не только дом, но и номер квартиры. Причём в некоторых квартирах почтальону ответят, если он в них постучится, а другие — нет, потому что там никто не живёт. А кто-то ответит, что адресат уже давно здесь не живёт и дадут новый адрес почтальону (редирект запроса).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;В компьютерных сетях всё точно также. На одном адресе (IP или доменном имени) могут висеть и ожидать запросов несколько портов одновременно. Чтобы избежать путаницы, сообщество разработчиков договорилось для наиболее популярных серверов выделять одни и те же порты: SSH — 22, FTP — 21, база данных MySQL — 3306, веб-сервера — 80. Это лишь соглашение и рекомендация, можно поднять какой угодно сервер на каком угодно порту, но для клиентов это скорее всего станет неожиданностью.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Когда в браузере мы вбиваем в адресной строке http.maxkuznetsov.ru, браузер подставляет порт 80 незаметно для нас. Вы можете убедиться в этом, вбив в адрес не http.maxkuznetsov.ru, а http.maxkuznetsov.ru:80 — результат не изменится.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Введём в терминале такие строки запроса.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="bash"&gt;$ nc http.maxkuznetsov.ru 80
GET / HTTP/1.1
Host: http.maxkuznetsov.ru&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;После Host нужно ввести две пустые строки: одна строка отступа, вторая содержит тело запроса, но в данном примере оно пустое. Такие правила протокола HTTP. Получив вторую пустую строку, веб-сервер поймёт, что запрос завершён, обработает его и пришлёт ответ, включающий интересующую нас веб-страницу с html.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="bash"&gt;$ nc http.maxkuznetsov.ru 80
GET / HTTP/1.1
Host: http.maxkuznetsov.ru

HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Mon, 13 Apr 2020 20:10:04 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive

&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;title&amp;gt;This is a test page for Max's blog article about http&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;h1&amp;gt;Hello HTTP world!&amp;lt;/h1&amp;gt;
  &amp;lt;form method=&amp;quot;POST&amp;quot; action=&amp;quot;/&amp;quot;&amp;gt;
    &amp;lt;input name=&amp;quot;name&amp;quot; type=&amp;quot;text&amp;quot; placeholder=&amp;quot;Name&amp;quot; /&amp;gt;
    &amp;lt;button type=&amp;quot;submit&amp;quot;&amp;gt;Submit&amp;lt;/button&amp;gt;
  &amp;lt;/form&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;После этого браузер разбирает ответ, убирает техническую информацию и отображает html-страницу в кодировке UTF-8  — так ему сказал сервер в заголовке Content-Type. Если в HTML включены CSS, Javascript, картинки, то браузер запросит их отдельными запросами ровно таким же образом. Если он их уже запрашивал раньше, то возьмёт из локального кэша. Поэтому первый раз страницы грузятся визуально дольше.&lt;/p&gt;
&lt;p&gt;Разберём структуру запроса и ответа более детально.&lt;/p&gt;
&lt;h2&gt;Структура HTTP-запроса&lt;/h2&gt;
&lt;p&gt;Каждый запрос имеет один и тот же формат:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;метод /путь протокол
Host: &amp;lt;site.ru&amp;gt;
заголовок1: значение
заголовок2: значение
заголовокN: значение

&amp;lt;тело_запроса_в_одну_строку или пустая_строка_если_тело_пустое&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;протокол&lt;/h3&gt;
&lt;p&gt;Указывает, какая конкретная версия протокола HTTP будет использоваться. Чаще всего 1.0 и 1.1, но могут встречаться устаревшая 0.9 или новая 2.0. Однако, веб-сервер может не поддерживать указанную версию и вернуть ошибку. На практике подавляющее количество веб-серверов поддерживают 1.0 и 1.1.&lt;/p&gt;
&lt;h3&gt;/путь&lt;/h3&gt;
&lt;p&gt;Относительный путь (без доменного имени) до документа. В нашем примере указан корень /, но путь может быть любым: /index.php, /catalog/food/milk. Под документом понимаются не только файлы с расширением .html, но и любые другие файлы, например картинки, .css, .js.&lt;/p&gt;
&lt;h3&gt;метод&lt;/h3&gt;
&lt;p&gt;Определяет, что веб-сервер должен сделать с документом, найденным по указанному «/пути».&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;GET — вернуть документ — GET /messages/1.&lt;/li&gt;
&lt;li&gt;HEAD — вернуть только заголовки без самой страницы. Это подходит для случая, когда мы хотим проверить, что ссылка на документ валидная. Пример: HEAD /messages/1&lt;/li&gt;
&lt;li&gt;POST — отправить данные для создания документа — POST /messages (и детали нового сообщения).&lt;/li&gt;
&lt;li&gt;PUT — заменить документ&lt;/li&gt;
&lt;li&gt;PATCH — частично обновить документ&lt;/li&gt;
&lt;li&gt;DELETE — удалить документ&lt;/li&gt;
&lt;li&gt;TRACE, CONNECT — технические методы, которые можно пропустить.&lt;/li&gt;
&lt;li&gt;OPTIONS — просьба к веб-серверу вернуть разрешённые настройки для запросов к указанному документу. Ответ включает в себя в том числе список разрешённых методов.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;На практике примерно 80% запросов приходится на GET, 15% — на POST и 5% — на все остальные методы.&lt;/p&gt;
&lt;h3&gt;Заголовки&lt;/h3&gt;
&lt;p&gt;Они опциональны (в нашем примере их не было вовсе) и подсказывают веб-серверу, как именно нужно обработать запрос. Например, что клиент отправляет запрос в виде текста с кодировкой utf-8, а ожидает получить json в кодировке cp1251.&lt;/p&gt;
&lt;p&gt;Наиболее частые на практике заголовки:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Accept — в каком формате ожидаем ответ: обычный текст, html, xml, json, что угодно ещё.&lt;/li&gt;
&lt;li&gt;Accept-Charset — кодировка тела запроса: utf8, cp1251, koi8.&lt;/li&gt;
&lt;li&gt;Authorization — данные для авторизации между запросами. Здесь чаще всего передаются токены API. Авторизация между запросами будет рассмотрена ниже.&lt;/li&gt;
&lt;li&gt;Accept-Language — список языков, которые нас бы устроили. Например: «Accept-Language: ru».&lt;/li&gt;
&lt;li&gt;Cache-Control — настройки кэширования страниц&lt;/li&gt;
&lt;li&gt;Cookie — известные браузеру куки. В них сохраняются идентификаторы сессий и пользовательские предпочтения.&lt;/li&gt;
&lt;li&gt;Referrer — с какой страницы был сделан текущий запрос. Полезно для аналитики сайта и для возвращения юзера на первоначальную страницу после регистрации, например.&lt;/li&gt;
&lt;li&gt;User-Agent — тип клиента (чаще всего тип вашего браузера). Пример: «Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.163 Safari/537.36». Это поле часто используется на сервере, чтобы отслеживать количество запросов с одного устройства и блокировать их при превышении лимита. Однако это не панацея, ведь после блокировки злоумышленник может поменять User-Agent на любой другой.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Тело&lt;/h3&gt;
&lt;p&gt;Для GET-запросов тело не имеет смысла, так как всё, что нужно — это путь в стартовой строке и заголовки. Но что происходит, когда мы хотим отправить информацию на сервер? Логин-пароль, форма обратной связи, форма создания поста? Для этого используется POST-запрос.&lt;/p&gt;
&lt;p&gt;Каждый элемент формы имеет аттрибут name. В нашем примере страница &lt;a href="http://http.maxkuznetsov.ru"&gt;http://http.maxkuznetsov.ru&lt;/a&gt; содержит форму с единственным тегом input, который имеет name=«name». Именно это имя и введённое пользователем в инпут значение будут отправлены на сервер. В консоли такой браузерный запрос будет выглядеть так:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="http"&gt;$ nc http.maxkuznetsov.ru 80
POST / HTTP/1.1
Host: http.maxkuznetsov.ru
Content-Type: application/x-www-form-urlencoded
Content-Length: 8

name=Max&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Обратите внимание, что POST запрос очень похож на GET, мы даже обращаемся к тому же документу «/». Однако есть и отличия:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;вместо второй пустой строки в конце запроса содержатся данные: «name=Max»&lt;/li&gt;
&lt;li&gt;эти данные могут быть в разном формате, поэтому мы должны явно указать веб-серверу, что это данные из формы — application/x-www-form-urlencoded&lt;/li&gt;
&lt;li&gt;также мы сообщаем серверу, что в теле запроса содержится ровно 8 символов — «Content-Length: 8». Это техническое поле, которое браузер выполняет на лету, а нам приходится считать самим.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;В ответ придёт знакомая страница, но с другим h1 заголовком — php-скрипт, обрабатывающий страницу, подставил вместо «http world» введённое нами имя.&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class="http"&gt;HTTP/1.1 200 OK
Server: nginx/1.10.3 (Ubuntu)
Date: Mon, 13 Apr 2020 22:26:19 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: keep-alive
Content-Encoding: gzip

&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
  &amp;lt;title&amp;gt;This is a test page for Max's blog article about http&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
  &amp;lt;h1&amp;gt;Hello Max&amp;lt;/h1&amp;gt;
  &amp;lt;form method=&amp;quot;POST&amp;quot; action=&amp;quot;/&amp;quot;&amp;gt;
    &amp;lt;input name=&amp;quot;name&amp;quot; type=&amp;quot;text&amp;quot; placeholder=&amp;quot;Name&amp;quot; /&amp;gt;
    &amp;lt;button type=&amp;quot;submit&amp;quot;&amp;gt;Submit&amp;lt;/button&amp;gt;
  &amp;lt;/form&amp;gt;
&amp;lt;/body&amp;gt; 
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;h2&gt;Структура HTTP-ответа на основе примера выше&lt;/h2&gt;
&lt;p&gt;Можно заметить, что структура ответа похожа на структуру запроса. Но есть несколько нюансов. Первая строка ответа выглядит иначе:&lt;/p&gt;
&lt;pre class="e2-text-code"&gt;&lt;code class=""&gt;HTTP/1.1 200 OK
протокол статус пояснение&lt;/code&gt;&lt;/pre&gt;&lt;h3&gt;протокол&lt;/h3&gt;
&lt;p&gt;Значение поля то же самое, что и в запросе. Но может отличаться от версии, что запросил браузер, если веб-сервер её не понимает.&lt;/p&gt;
&lt;h3&gt;статус и пояснение&lt;/h3&gt;
&lt;p&gt;HTTP-статус из трёх цифр и короткая поясняющая фраза. Все фразы стандартизированы и чётко соответствуют статусу.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://ru.wikipedia.org/wiki/%D0%A1%D0%BF%D0%B8%D1%81%D0%BE%D0%BA_%D0%BA%D0%BE%D0%B4%D0%BE%D0%B2_%D1%81%D0%BE%D1%81%D1%82%D0%BE%D1%8F%D0%BD%D0%B8%D1%8F_HTTP#%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BA%D0%BE%D0%B4%D0%BE%D0%B2"&gt;Статусов больше сотни&lt;/a&gt;, но не все их них используются браузерами. Некоторые предусмотрены на далёкое будущее, а некоторые слишком специфичны.&lt;/p&gt;
&lt;p&gt;Первая цифра статуса указывает на класс:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1xx — информационные — технические статусы, вероятнее всего вы их не увидите в реальной жизни&lt;/li&gt;
&lt;li&gt;2xx — успешно обработанные запросы с детализацией. Наиболее частые: 200 — всё ок, 201 — документ был создан, 204 — запрос завершился успешно, но ответ содержит заголовки и пустое тело. На практике реальные API не парятся и в 95% случаев возвращают код 200, а детали успешной операции отсылаются в теле.&lt;/li&gt;
&lt;li&gt;3xx — перенаправление — запрос следует выполнить по другому адресу, который передаётся в заголовке Location. Частые: 301 — документ перенесён навсегда, 302 — документ перенесён временно. Они довольно критичны для поисковых ботов, которые индексируют ваш сайт. 301 говорит боту, чтобы он запомнил новый адрес страницы, а прежний забыл.&lt;/li&gt;
&lt;li&gt;4xx — ошибка клиента — запрос содержит не все или некорректные данные (400), требуется аутентификация (401) или не хватает прав на выполнение операции (403), запрошенной страницы не существует (404) или http метод запроса для этой страницы запрещён (405).&lt;/li&gt;
&lt;li&gt;5xx — ошибка на сервере — сервер не справился или произошла непредвиденная ошибка (500), ошибка обработки запроса вышестоящим сервером, например php-fpm не отвечает nginx’у (502), сервер временно не отвечает по техническим причинам (503), сервер не дождался ответа и запрос отвалился по таймауту (504). Например, стандартное ограничение на время выполнения php-скрипта — 30 секунд. Если скрипт делает запрос к стороннему ресурсу, который под нагрузкой, то nginx покажет 504ю ошибку.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;При этом даже неуспешный статус не запрещает серверу вернуть веб-страницу, которую браузер отобразит как ни в чём не бывало. Попробуйте зайти на несуществующую страницу моего блога: &lt;a href="https://maxkuznetsov.ru/non-existed-page."&gt;https://maxkuznetsov.ru/non-existed-page.&lt;/a&gt; Сервер вернёт 404 вместо 200, но мы всё равно можем показать пользователю что-то полезное.&lt;/p&gt;
&lt;h3&gt;заголовки&lt;/h3&gt;
&lt;p&gt;Заголовки сервера выполняют ту же роль, что и заголовки запроса. Есть общие заголовки, как Cache-Control, но есть и свои уникальные.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Allow — определяет список разрешённых http методов для запросов&lt;/li&gt;
&lt;li&gt;Location — адрес для перенаправления.&lt;/li&gt;
&lt;li&gt;WWW-Authenticate — информация про метод аутентификации, запрос должен послать соответствующую информацию в хедере Authorization.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;тело&lt;/h3&gt;
&lt;p&gt;Тело ответа также отделяется от группы заголовков пустой строкой. При этом в теле может передаваться что угодно — текст, html, json, xml, картинки и прочие файлы. Все они отдаются браузеру в одинаковом формате, но с отличающемся заголовком Content-Type, который и поясняет браузеру, как отобразить контент пользователю: как html-страницу, как картинку, показать встроенный в браузер PDF-просмотрщик или начать скачивание файла.&lt;/p&gt;
&lt;h2&gt;Про аутентификацию и авторизацию&lt;/h2&gt;
&lt;p&gt;Если посмотреть на структуру HTTP запросов и ответов становится понятно, что каждый запрос для веб-сервера является изолированным и не сохраняет состояния. То есть если вы сделаете два одинаковых запроса с одного браузера, веб-сервер обработает их так, будто они были присланы разными пользователями.&lt;/p&gt;
&lt;p&gt;В жизни это ограничение обходят двумя путями:&lt;/p&gt;
&lt;ol start="1"&gt;
&lt;li&gt;хранят уникальный идентификатор сессии в куках (cookies), которые браузер по требованию сервера сохраняет локально и затем прикрепляет к каждому запросу в виде заголовка «Cookie». Cервер при каждом запросе разбирает заголовок с куками и по сохранённому там идентификатору «узнаёт» пользователя.&lt;/li&gt;
&lt;li&gt;через заголовок Authorization браузер посылает серверу в каждом запросе токен (форматы могут быть разными), по которому сервер определяет пользователя аналогично сессиям. Этот способ чаще всего используется в API.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Поскольку http протокол передаёт все данные в незащищённом виде, то ни один из этих способов не является безопасным, а идентификатор сессии или токен могут быть легко перехвачены злоумышленником. В качестве решения проблемы следует использовать более безопасного брата HTTP — HTTPS.&lt;/p&gt;
&lt;h2&gt;Что важно понимать про HTTP&lt;/h2&gt;
&lt;ol start="1"&gt;
&lt;li&gt;HTTP — это протокол общения клиент-серверных приложений в вебе. Набор правил, который помогает клиентам (прежде всего браузерам) и веб-серверам понимать друг друга.&lt;/li&gt;
&lt;li&gt;HTTP — это про формат общения, а не про управление сервером HTTP-командами. Клиент может отправить что угодно: удали страницу сайта, создай нового пользователя, выдай список всех пользователей — но сервер не обязан их выполнять, он лишь обязан ответить в формате HTTP, чтобы клиент его понял. То есть благодаря HTTP сервер поймёт, что клиент хочет, а потом уже решит, как это обработать и вернёт результат. Может быть удалит страницу, а может и нет.&lt;/li&gt;
&lt;li&gt;В HTTP общение всегда начинает клиент. А веб-сервер висит и ждёт. Сейчас есть способы инициировать запрос с сервера, но изначально протокол для этого не предназначен.&lt;/li&gt;
&lt;li&gt;HTTP-протокол не имеет шифрования, поэтому передавать персональные данные и прочие приватные данные через него не безопасно. В таком случае нужно использовать HTTPS.&lt;/li&gt;
&lt;li&gt;Простой способ изучить заголовки запроса и ответа — открыть консоль браузера на нужной странице и обновить её. В разделе Network/Сеть отобразятся все запросы с этой страницы, включая запросы на картинки и статические файлы.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="e2-text-picture"&gt;
&lt;img src="https://maxkuznetsov.ru/pictures/http1.png" width="756" height="557" alt="" /&gt;
&lt;/div&gt;
&lt;h2&gt;Несколько слов о наступающем будущем — HTTP/2&lt;/h2&gt;
&lt;p&gt;Первая версия протокола HTTP была принята 20 лет назад. После этого 10 лет была тишина, пока Google не стал разрабатывать свой протокол SPDY поверх HTTP, что дало ускорение работе над HTTP/2. После серёзных подвижек Google отказался от разработки SPDY в пользу HTTP/2.&lt;/p&gt;
&lt;p&gt;Вторая версия протокола отличается от первой чуть меньше, чем полностью.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Протокол уже стал бинарен, а значит человеко нечитаемый.&lt;/li&gt;
&lt;li&gt;Несмотря на возможность работать без шифрования, все современные браузеры будут поддерживать именно вариант с шифрованием.&lt;/li&gt;
&lt;li&gt;В протоколе предусмотрена возможность push-сообщений, инициированных сервером.&lt;/li&gt;
&lt;li&gt;Протокол позволяет мультиплексирование — отправку нескольких запросов внутри одного соединения.&lt;/li&gt;
&lt;li&gt;Умеет сжимать данные заголовков.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Новый протокол безопаснее и в несколько раз быстрее. Самое приятное, что последние версии современных браузеров понимают http2, и половина наиболее крупных и популярных сайтов уже готовы к переходу на него. С приходом http2 веб станет ещё более интерактивным, а приложения приблизятся к десктопным.&lt;/p&gt;
</description>
</item>


</channel>
</rss>