Веб-версияОткрыть в Telegram
LL E S S O N S

L E S S O N S

@MiyamotoLessons · группа · Технологии · в индексе с 2026-07-11
1 078участников
15 272сообщений в индексе
R
Фотография
нажмите — покажем
# Как backend перестаёт быть хрупким: что я понял про импорт данных, тесты, CI, Git и реальные проверки системы Чем дольше я разбираюсь в backend-разработке, тем сильнее понимаю простую вещь: почти никогда проблема не в “одной плохой строчке кода”. Проблема обычно в том, что система выглядит простой только издалека. Снаружи всё кажется понятным: * есть внешний API, * есть функция, которая забирает данные, * есть база данных, * есть endpoint, * есть тесты, * есть CI, * есть Git. Но как только начинаешь разбираться глубже, выясняется, что за каждым из этих слов скрывается не “инструмент”, а целый слой логики и рисков. Например: * импорт данных — это не “скачать JSON”, а не пустить мусор в базу; * тесты — это не “написать пару assert”, а поднять отдельную безопасную среду; * CI — это не “зелёная галочка”, а воспроизводимая проверка в чужом окружении; * Git — это не “ну тут ветки и коммиты”, а реальная история проекта, которую очень легко испортить неправильной базой ветки. Последнее время я как раз разбирал такие вещи: безопасный ingest, идемпотентность, management commands, Django TestCase, test runner, сигналы, shell-проверки, CI-среду и Git после squash merge. И чем глубже я туда заходил, тем сильнее backend переставал быть для меня “набором файлов”. Он начал выглядеть как система доверия: * можно ли доверять входным данным, * можно ли доверять повторному запуску, * можно ли доверять тестам, * можно ли доверять CI, * можно ли доверять истории веток, * и можно ли вообще доверять тому, что проект не развалится от следующей маленькой правки. Ниже — то, как я теперь на это смотрю.
R
## 1. Импорт данных — это не “получить и сохранить”, а фильтр между внешним хаосом и твоей системой Когда смотришь на импорт в самом первом приближении, всё кажется почти смешно простым: 1. сделать HTTP-запрос, 2. получить JSON, 3. пробежаться по данным, 4. сохранить их в базу. На бумаге идеально. В жизни — нет. Потому что внешний API живёт не по твоим правилам. Он может: * зависнуть, * ответить слишком долго, * прислать битый JSON, * вернуть пустые поля, * изменить формат, * дублировать записи, * дать неполную информацию. Поэтому ingest — это не “удобная функция, которая качает новости, товары, посты или ещё что угодно”. Ingest — это пограничный слой между внешним миром и твоей внутренней системой. И этот слой должен отвечать не на вопрос “как бы мне всё это побыстрее записать”, а на вопрос: > что из пришедшего вообще имеет право попасть внутрь моей системы? Вот здесь и начинается взрослая backend-логика. --- ## 2. Первая защита: сеть не должна иметь права зависнуть навсегда Очень много раннего кода выглядит так: response = requests.get(url, headers=headers, params=params) data = response.json() С виду всё нормально. Но проблема в том, что такой код слишком сильно верит сети. Если сеть подвиснет, если удалённый сервер начнёт отвечать очень медленно, если соединение будет устанавливаться бесконечно долго — твой процесс просто повиснет. Это не “редкий случай”. Это нормальная реальность продовых интеграций. Поэтому запросы нужно делать не наивно, а с контролем: import logging import requests from requests.exceptions import RequestException logger = logging.getLogger(__name__) DEFAULT_TIMEOUT = (3.05, 20) def get_json(url, *, headers=None, params=None): try: response = requests.get( url, headers=headers, params=params, timeout=DEFAULT_TIMEOUT, ) response.raise_for_status() return response.json() except RequestException as exc: logger.warning("External A
R
Это текущий объект команды. ### *args Это все позиционные аргументы, собранные в кортеж. Если очень просто: *args = “собери лишние позиционные аргументы в одну пачку”. ### **opts Это все именованные аргументы, собранные в словарь. То есть **opts в команде — это обычно словарь CLI-опций. Если ты запускаешь: python manage.py ingest_items --mode top --pages 2 то внутри opts будет что-то вроде: { "mode": "top", "pages": 2, } ### Почему тут имя opts, а не kwargs Потому что по смыслу это именно **options**, а не просто абстрактные keyword arguments. --- ## 10. Как посмотреть, какие параметры команда вообще принимает Вот одна из самых полезных команд в таких вещах: python manage.py ingest_items --help ### Что делает эта команда Она не запускает ingest по-настоящему. Она просто показывает: * какие есть аргументы, * какие значения допустимы, * какие значения по умолчанию. Это очень важная часть дисциплины командного интерфейса: если команда не умеет сама себя объяснить через --help, она плохо спроектирована. --- ## 11. Тесты: когда backend начинает сам защищать себя Теперь к одной из самых важных тем. До тех пор пока тестов нет, любой “успешный” запуск проекта — это просто удачное стечение обстоятельств. С тестами появляется новый уровень доверия. Особенно когда тестируются не случайные мелочи, а реальные инварианты. Например: * запись без URL не должна сохраняться; * запись без title не должна сохраняться; * запись без даты не должна сохраняться; * повторная обработка одного URL не должна создавать дубль. И тут появляется строка, которую многие недооценивают: from django.test import TestCase --- ## 12. Что такое TestCase в Django по сути TestCase — это не просто класс, где лежат assertEqual и assertTrue. Это специальный тестовый режим Django. Когда ты наследуешь тестовый класс от TestCase, Django понимает: * это не просто Python-класс, * это класс, который должен запускаться в тестовой среде проекта. И дальше включается целая инф
R
Запускает Django shell и сразу выполняет одну команду. #### from app.models import Source Импорт модели. #### Source.objects.filter(...).count() Через ORM отфильтровывает нужные записи и считает их. ### Почему это круто Потому что ты задаёшь проекту очень точный вопрос и получаешь конкретный ответ. Не “мне кажется, что fallback-сущностей мало”. А: * вот точное количество, * вот точная выборка, * вот точная проблема или её отсутствие. --- ## 21. CI: почему зелёная галочка может быть просто красивой иллюзией Если тестов ноль — зелёный CI почти ничего не значит. Но даже если тесты уже есть, CI всё ещё может падать не из-за кода, а из-за окружения. Допустим, в workflow есть: - name: Create .env file run: | echo "DATABASE_URL=${{ secrets.DATABASE_URL }}" >> .env Если этот DATABASE_URL указывает на PostgreSQL, а в CI никакой PostgreSQL не поднят, то тесты свалятся не потому, что твой ingest плохой, а потому, что среда была собрана неправильно. ### Как это диагностировать Нужно различать: * логические ошибки приложения, * ошибки окружения. Если локально тесты проходят, а CI валится именно на подключении к БД, это сильный сигнал, что проблема не в бизнес-логике. ### Временный здравый вариант Если тесты на текущем этапе не завязаны на PostgreSQL-специфику, то в CI можно использовать SQLite: - name: Create .env file run: | echo "SECRET_KEY=test-secret" >> .env echo "DEBUG=False" >> .env echo "DATABASE_URL=sqlite:///db.sqlite3" >> .env echo "REDIS_URL=redis://127.0.0.1:6379/1" >> .env Это не “упрощение ради лености”, а честная настройка среды под тот класс тестов, который ты сейчас реально запускаешь. --- ## 22. Git: почему после Squash and merge новые PR могут стать грязными Это отдельная тема, но она тоже очень важна для реальной разработки. Допустим: * у тебя была feature-ветка; * ты смержил её через Squash and merge; * потом создал новую ветку не от свежего master, а от старой feature-ветки. И внезапно новый PR показывае
R
Аудио
gentle serotonin in low resolution.mp3 · 7.8 МБ · нажмите — покажем
R
Фотография
нажмите — покажем
# Почему shell-команды — один из самых честных способов проверить систему
R
Аудио
Lonown, Riserayss - Worry (Slowed).mp3 · 9.9 МБ · нажмите — покажем
🎧 Нажми, чтобы найти песню
R
Одна из самых неприятных иллюзий в разработке — ощущение, что ты “и так примерно понимаешь, что происходит”. Код написан. Endpoint отвечает. Логи вроде спокойные. Тесты зелёные. В браузере что-то отображается. Кажется, что система жива и под контролем. Но backend очень часто врёт именно в таких состояниях. Он может: * отвечать, но отдавать не те данные; * не падать, но quietly копить мусор; * выглядеть рабочим, но дублировать записи; * проходить тесты, но иметь плохое содержимое базы; * казаться стабильным, хотя часть логики давно работает не так, как ты думаешь. И вот в этот момент shell-команды становятся не просто “удобным инструментом”, а почти хирургическим способом честно посмотреть внутрь системы. Потому что shell — это место, где ты задаёшь системе прямой вопрос и получаешь прямой ответ. Не “что кажется по коду”. Не “что вероятно происходит”. Не “что рисует интерфейс”. А: * сколько записей реально в базе; * есть ли дубли; * сколько объектов без нормальной связи; * как часто сработал fallback; * сколько групп проблемных данных; * какие именно поля чаще всего пустые; * соответствует ли фактическое состояние той модели, которую ты держишь в голове. И в этом его сила. --- ## 1. Что вообще такое shell-команда в контексте backend Когда говорят “пойти в shell”, часто имеют в виду одно из двух: ### Вариант 1. Обычный Python shell Ты запускаешь просто: python И попадаешь в обычный интерпретатор Python. Это полезно, но он ничего не знает о твоём приложении. --- ### Вариант 2. Django shell Ты запускаешь: python manage.py shell И попадаешь в Python-среду, где уже загружен Django-проект: * прочитан settings.py, * зарегистрированы приложения, * инициализирован ORM, * доступны модели, * доступна конфигурация базы, * можно делать запросы к данным так, как их видит само приложение. Вот это уже очень мощно. Если пойти ещё дальше, можно запускать не интерактивный shell, а одну конкретную команду: python manage.py shell -c "from app.models import Entry;
R
Тогда возникает более сильный тип запроса — не просто выбрать записи, а сгруппировать их и посчитать группы. Пример: python manage.py shell -c "from catalog.models import Source; from django.db.models import Count; qs = Source.objects.exclude(external_id__isnull=True).values('external_id').annotate(c=Count('id')).filter(c__gt=1).order_by('-c'); print(list(qs[:20]))" Это уже очень насыщенная команда. Разберём её медленно. --- ## 9. Разбор сложной диагностической shell-команды Вот она ещё раз: python manage.py shell -c "from catalog.models import Source; from django.db.models import Count; qs = Source.objects.exclude(external_id__isnull=True).values('external_id').annotate(c=Count('id')).filter(c__gt=1).order_by('-c'); print(list(qs[:20]))" ### Шаг 1. Импортируем модель и агрегатную функцию from catalog.models import Source from django.db.models import Count Count — это агрегатная функция ORM, аналог SQL COUNT(...). --- ### Шаг 2. Убираем строки, где external_id пустой Source.objects.exclude(external_id__isnull=True) То есть нас интересуют только те записи, где внешний идентификатор задан. Почему это важно: если оставить NULL, они начнут шуметь и мешать нормальному анализу дублей. --- ### Шаг 3. Сводим данные к одному полю .values('external_id') Теперь ORM начинает мыслить не “объектами Source”, а “группами по external_id”. Это принципиально важный переход. --- ### Шаг 4. Добавляем количество строк в каждой группе .annotate(c=Count('id')) Это означает: > для каждой группы external_id посчитай, сколько строк в неё попало, и назови это число c. Результат уже будет типа: [ {'external_id': 'abc', 'c': 1}, {'external_id': 'xyz', 'c': 3}, ] --- ### Шаг 5. Оставляем только реальные дубли .filter(c__gt=1) c__gt=1 означает: > количество больше 1. То есть теперь остаются только те группы, где внешний идентификатор встречается более одного раза. --- ### Шаг 6. Сортируем самые проблемные сверху .order_by('-c') Минус означает убывание.
R
## 18. Как строить хороший shell-вопрос: правильная последовательность Очень полезный шаблон мышления такой: ### Шаг 1. Сначала общий масштаб python manage.py shell -c "from catalog.models import Entry; print(Entry.objects.count())" ### Шаг 2. Потом проблемное подмножество python manage.py shell -c "from catalog.models import Entry; print(Entry.objects.filter(source__name='Unknown source').count())" ### Шаг 3. Потом структура проблемы python manage.py shell -c "from catalog.models import Source; from django.db.models import Count; qs=Source.objects.values('external_id').annotate(c=Count('id')).filter(c__gt=1); print(qs.count())" ### Шаг 4. Потом примеры python manage.py shell -c "from catalog.models import Source; from django.db.models import Count; qs=Source.objects.values('external_id').annotate(c=Count('id')).filter(c__gt=1).order_by('-c'); print(list(qs[:10]))" Это хорошая логика: * сначала масштаб, * потом срез, * потом структура, * потом конкретные примеры. --- ## 19. Что делает shell-команду действительно хорошей Хорошая shell-команда: * отвечает на один конкретный вопрос; * не делает побочных эффектов; * печатает достаточно, но не слишком много; * легко повторяется; * даёт результат, который можно сравнить “до/после”. Плохая shell-команда: * печатает полбазы; * делает update/delete без необходимости; * смешивает несколько вопросов сразу; * даёт вывод, который потом невозможно интерпретировать. --- ## 20. Практический набор команд, который полезно иметь почти в любом backend-проекте Ниже не “магические” команды, а типовые диагностические вопросы. ### Общее количество записей python manage.py shell -c "from catalog.models import Entry; print(Entry.objects.count())" ### Сколько записей без внешнего id python manage.py shell -c "from catalog.models import Entry; print(Entry.objects.filter(external_id__isnull=True).count())" ### Сколько записей без изображения python manage.py shell -c "from catalog.models import Entry; print(Entry.objec
R
GIF
нажмите — покажем
# Почему разработку фронтенда стоит начинать не с интерфейса, а с доказуемого baseline Когда открываешь существующий frontend-проект, первое желание почти всегда одинаковое: запустить приложение, поправить компоненты, подключить API, добавить страницы и наконец увидеть результат в браузере. Но чем сложнее проект, тем опаснее начинать именно так. Перед изменением функциональности нужно ответить на более фундаментальные вопросы: * в правильной ли директории я нахожусь; * какой Git-репозиторий управляет этими файлами; * не принадлежит ли frontend родительскому репозиторию; * какие локальные файлы могут случайно попасть в коммит; * какая версия Node.js используется; * какой пакетный менеджер считается каноническим; * может ли dev-сервер незаметно сменить порт; * не попадут ли секреты в клиентскую сборку; * можно ли повторить текущее окружение на другой машине; * действительно ли проект проходит lint, build и runtime-проверку. Это и есть задача baseline: не улучшать продукт, а сначала доказать текущее состояние системы. Baseline — не уборка ради порядка. Это контрольная точка, относительно которой любые последующие изменения можно сравнивать объективно.
R
Аудио
Merunes - Montagem Tenta · 4.7 МБ · нажмите — покажем
🎧 Нажми, чтобы найти песню ⭐ Telegram Premium берут здесь
R
--- # 1. Baseline — это не список файлов, а система доказательств Представим, что у нас есть frontend в каталоге: /Users/alex/work/atlas/frontend На первый взгляд всё выглядит нормально: frontend/ ├── src/ ├── public/ ├── package.json ├── package-lock.json ├── vite.config.ts ├── .env └── node_modules/ Но визуального осмотра недостаточно. Каталог может быть: * символической ссылкой на другое место; * частью большого родительского Git-репозитория; * уже существующим отдельным репозиторием; * копией другого frontend; * рабочей директорией с неожиданными локальными изменениями; * набором файлов без воспроизводимого окружения. Поэтому baseline строится не вокруг утверждений вроде «кажется, всё нормально», а вокруг проверяемых фактов. Например: pwd pwd -P Обе команды показывают текущий каталог, но делают это немного по-разному. ## pwd pwd означает print working directory — вывести текущую рабочую директорию. pwd Результат: /Users/alex/work/atlas/frontend Это путь, под которым shell воспринимает текущий каталог. ## pwd -P pwd -P Флаг -P просит показать физический путь, разрешив символические ссылки. Допустим: /Users/alex/work/atlas/frontend совпадает с обычным pwd. Значит, вероятнее всего, мы действительно находимся в ожидаемом физическом каталоге. Но возможен и другой результат: /Volumes/external-disk/projects/frontend Тогда выясняется, что путь /Users/alex/work/atlas/frontend — только ссылка на другую директорию. Это не обязательно ошибка, но такое состояние нельзя игнорировать. Все дальнейшие действия с Git, удалением файлов и сборкой будут происходить в физическом каталоге, а не в том месте, которое мы мысленно считали проектом. Проверить сам факт символической ссылки можно отдельно: test -L /Users/alex/work/atlas/frontend \ && echo "FRONTEND_IS_SYMLINK" \ || echo "FRONTEND_IS_NOT_SYMLINK" Здесь: * test -L путь проверяет, является ли объект символической ссылкой; * && выполняет следующую команду, если проверка успешна; * || выполняет
R
> какое правило игнора должно применяться к нему? Нормальный результат: git ls-files .env ничего не выводит, а: git check-ignore -v .env показывает правило. Только вместе эти проверки дают уверенность. --- # 5. Почему переменные VITE_* нельзя считать секретами Frontend-сборка принципиально отличается от backend-конфигурации. На сервере секрет может оставаться только в окружении процесса. Например: DATABASE_PASSWORD=... PRIVATE_API_KEY=... SESSION_SIGNING_SECRET=... Клиентский frontend работает иначе. Браузер должен получить JavaScript-код. Если значение встроено в этот код во время сборки, пользователь может его увидеть: * в загруженных JavaScript-файлах; * в DevTools; * через поиск по bundle; * в Network; * через исходные карты, если они опубликованы. В Vite переменные с префиксом: VITE_ предназначены для клиентского кода. Например: VITE_API_URL=https://api.example.com/v1 Это нормально, потому что адрес публичного API не является секретом. Но такое значение недопустимо: VITE_DATABASE_PASSWORD=super-secret-password Префикс не защищает переменную. Он, наоборот, делает её доступной frontend-сборке. Поэтому правило простое: > всё, что начинается с VITE_, нужно считать публичной информацией. --- ## Как проверять env-файлы, не раскрывая значения Не всегда безопасно выполнять: cat .env Особенно если вывод потом попадёт: * в чат; * в лог; * в скриншот; * в CI; * в технический отчёт. Можно вывести только имена переменных: awk -F= '/^[A-Za-z_][A-Za-z0-9_]*=/{print $1}' .env Разберём команду. ### awk Инструмент для обработки текстовых строк по правилам. ### -F= Задаёт символ = как разделитель полей. Строка: VITE_API_URL=https://api.example.com/v1 разбивается на: поле 1: VITE_API_URL поле 2: https://api.example.com/v1 ### Регулярное выражение /^[A-Za-z_][A-Za-z0-9_]*=/ Оно выбирает строки, похожие на корректные объявления переменных: * первый символ — буква или _; * далее — буквы, цифры или _; * затем =. ### {print $1} Печатает т
R
При корректном strictPort второй процесс завершится ошибкой: Port 5173 is already in use Он не должен стартовать на 5174. После проверки первый процесс нужно остановить: Ctrl+C И убедиться, что порт свободен: lsof -nP -iTCP:5173 -sTCP:LISTEN Разберём параметры. ### lsof Показывает открытые файлы и сетевые соединения процессов. В Unix сетевой сокет тоже рассматривается как открытый ресурс. ### -nP * -n не разрешает IP-адреса в имена; * -P не превращает порты в имена сервисов. Вывод получается быстрее и точнее. ### -iTCP:5173 Ищет TCP-соединения, относящиеся к порту 5173. ### -sTCP:LISTEN Оставляет только процессы, которые слушают порт. Пустой вывод после остановки означает, что dev-сервер больше не работает. --- # 11. Runtime smoke — это не полноценное тестирование продукта После lint и build приложение нужно запустить. Но runtime smoke не должен превращаться в большой функциональный аудит. Его задача — проверить минимальный жизненный цикл: * Vite стартует; * страница отображается; * нет blank page; * нет fatal exception; * нет error overlay; * запрос формируется по правильному адресу; * приложение контролируемо обрабатывает ошибку API. Очень важное различие: ## Ошибка внешнего API Если API временно недоступен, но интерфейс показывает нормальное состояние ошибки: Не удалось загрузить данные и приложение не падает, это не обязательно blocker baseline. Frontend доказал, что: * запускается; * обрабатывает отказ; * формирует ожидаемый запрос; * остаётся работоспособным. ## Ошибка самого frontend Blocker: * пустая белая страница; * необработанное исключение; * неправильный URL; * бесконечный crash loop; * dev-server стартует не на ожидаемом порту; * приложение требует изменить backend, чтобы вообще загрузиться. Baseline проверяет устойчивость оболочки, а не гарантирует доступность всех внешних систем. --- # 12. Staging — это проект будущего коммита Многие воспринимают: git add . как технический шаг перед git commit. Но staging area
R
GIF
нажмите — покажем
# Что на самом деле происходит, когда я открываю localhost:3000 Я долго воспринимал адреса вроде: http://localhost:3000 как одну неделимую техническую конструкцию: вставил её в браузер — открылся сайт. Но когда начинаешь разбирать происходящее по слоям, выясняется, что за этой короткой строкой стоит целая система: процессы операционной системы, IP-адреса, DNS, порты, сокеты, TCP-соединения, HTTP-запросы и файловые дескрипторы. Самое интересное, что каждый из этих уровней решает отдельную задачу. Поняв их, начинаешь гораздо быстрее диагностировать ситуации вроде «сервер не запускается», «порт занят», «браузер не подключается» или «команда показывает какой-то неизвестный процесс». Ниже я разберу весь путь — от строки в адресной строке браузера до конкретного процесса, который возвращает страницу.
R
--- ## 1. Сервер — это прежде всего запущенный процесс Когда я запускаю приложение командой вроде: npm run dev операционная система не воспринимает это как абстрактное «запустить сайт». Она создаёт один или несколько процессов. Упрощённо цепочка может выглядеть так: Terminal └── npm └── node └── development server ### Что такое процесс Процесс — это запущенный экземпляр программы. Например, файл node на диске — это программа. Когда я запускаю её, операционная система создаёт процесс и выделяет ему: * память; * процессорное время; * набор открытых ресурсов; * переменные окружения; * текущую рабочую директорию; * уникальный идентификатор. Этот идентификатор называется: PID — Process ID Например: node 28471 Здесь: * node — имя исполняемой программы; * 28471 — PID конкретного запущенного процесса. Если запустить одну и ту же программу дважды, появятся два разных процесса с разными PID: node 28471 node 29103 Операционная система различает их именно по PID. --- ## 2. Почему имя процесса не всегда совпадает с названием инструмента Если я запустил frontend-сервер, это ещё не означает, что в системном списке процессов будет написано название используемого фреймворка. Например, инструмент разработки может выполняться внутри Node.js. Поэтому система покажет: node а не: my-dev-server Это важный практический момент. Увидеть процесс node — недостаточно, чтобы сделать вывод: > Это именно тот сервер, который мне нужен. Это может быть: * другой frontend-проект; * локальный скрипт; * редактор кода; * фоновый инструмент; * совершенно другое Node.js-приложение. Поэтому при диагностике лучше искать не только процесс по имени, а конкретный ресурс, которым он владеет, — например, сетевой порт. --- # 3. Что означает адрес http://localhost:3000/ Разберём URL по частям: http://localhost:3000/ Он содержит четыре главных элемента: http:// протокол localhost имя хоста 3000 порт / путь Каждая часть отвеча
R
Address already in use или: Port 3000 is already in use Причина проста: система должна однозначно понимать, какому процессу передавать входящее соединение. Если два независимых процесса заявляют: > Всё, что приходит на 127.0.0.1:3000, отдавай мне, операционная система не может выполнить оба требования одновременно. Существуют продвинутые исключения вроде SO_REUSEPORT, балансировки и нескольких процессов одного сервера, но для обычной локальной разработки действует правило: > один listening endpoint — один владелец. --- # 12. Важное различие: 127.0.0.1, 0.0.0.0 и конкретный сетевой IP Сервер может привязаться не только к порту, но и к определённому адресу. ## Только локальный компьютер 127.0.0.1:3000 Сервер доступен только с той же машины. Другой компьютер в локальной сети к нему обычно не подключится. ## Все IPv4-интерфейсы 0.0.0.0:3000 Это не адрес, по которому браузер обычно ходит напрямую. Он означает: > слушать порт 3000 на всех доступных IPv4-интерфейсах. Например: * loopback; * Wi-Fi; * Ethernet; * VPN-интерфейс. Если firewall разрешает соединение, сервер может стать доступен другим устройствам в сети. ## Конкретный сетевой интерфейс 192.168.1.25:3000 Сервер слушает только на конкретном локальном IP. --- ## IPv6-эквиваленты ::1 локальный IPv6 loopback :: все IPv6-интерфейсы Поэтому вывод: TCP [::1]:3000 (LISTEN) не означает, что сервер доступен из интернета. ::1 — локальный адрес, аналог 127.0.0.1. А вот: TCP *:3000 (LISTEN) или привязка к 0.0.0.0 требует большего внимания: процесс может принимать соединения не только от локального браузера. --- # 13. Что такое TCP TCP — транспортный протокол, который создаёт надёжное соединение между двумя программами. Он обеспечивает: * установление соединения; * сохранение порядка данных; * обнаружение потерянных сегментов; * повторную передачу; * контроль целостности; * управление потоком данных. Когда браузер открывает: http://localhost:3000/ он сначала устанавливае
R
Это не догадка по названию программы. Это информация из таблиц ядра о владельце конкретного сокета. --- # 25. Как проверить, кто слушает порт Одна из классических диагностических команд: lsof -nP -iTCP:3000 -sTCP:LISTEN Она отвечает на конкретный вопрос: > Есть ли процесс, который сейчас слушает TCP-порт 3000, и если да, кто именно? Разберём её: lsof показать открытые ресурсы процессов; -n не преобразовывать IP в имена; -P не преобразовывать порты в названия сервисов; -iTCP:3000 оставить сетевые TCP-ресурсы, связанные с портом 3000; -sTCP:LISTEN оставить только серверные сокеты в состоянии ожидания. Пример результата: COMMAND PID USER FD TYPE NAME node 28471 alex 21u IPv6 TCP [::1]:3000 (LISTEN) Это означает: * программа запущена через Node.js; * PID процесса — 28471; * процесс принадлежит пользователю alex; * он использует файловый дескриптор 21; * сокет работает через IPv6; * слушает локальный IPv6-адрес ::1; * порт — 3000; * состояние — LISTEN. --- # 26. Почему поиск по имени процесса менее надёжен Можно попробовать: ps aux | grep node Но это отвечает на другой вопрос: > Какие процессы содержат слово node? А нас обычно интересует: > Кто владеет портом 3000? Проблемы поиска по имени: * Node.js-процессов может быть много; * сервер может иметь другое имя; * строка может относиться к редактору; * grep может найти собственный процесс; * имя не показывает, какой порт занят. Проверка по порту точнее, потому что диагностируется сам конфликтующий ресурс. --- # 27. Как узнать больше о найденном процессе Если найден PID: 28471 можно выполнить: ps -p 28471 -o pid,ppid,user,command Эта команда показывает: * pid — идентификатор процесса; * ppid — идентификатор родительского процесса; * user — владельца; * command — полную команду запуска. Пример: PID PPID USER COMMAND 28471 28390 alex node ./node_modules/.../server.js Можно также посмотреть рабочую директорию процесса: lsof -a -p 28471 -d cwd Здес
R
localhost — имя текущего компьютера. 127.0.0.1 и ::1 — его loopback-адреса. IP определяет сетевой интерфейс. Порт определяет конкретную программу. Процесс создаёт сокет. Сокет привязывается к адресу и порту. LISTEN означает готовность принимать подключения. TCP создаёт надёжный канал между браузером и сервером. HTTP определяет формат их разговора. Файловый дескриптор связывает сокет с процессом внутри операционной системы. А такие инструменты, как lsof, ps, nc и curl, позволяют проверять каждый уровень отдельно. Именно это понимание меняет подход к диагностике. Вместо бессистемного перебора исправлений можно определить, на каком уровне оборвалась цепочка, и проверять только то, что действительно связано с проблемой.

Открытая публичная лента из поискового индекса ChatCrawler — «Google по публичному Telegram»; обновляется по мере обхода площадки. Время — UTC.

Только публичный контент, официальный API Telegram. О проекте · Вопросы · Чего мы не делаем · Убрать страницу из выдачи · Каталог · Поиск · Как мы считаем