Завершение сервера и сопровождение
Последний шаг серии касается жизненного цикла процесса. «Инвентарь» уже показывает оборудование, принимает формы, обнаруживает конфликт версии и проводит движение в транзакции. Теперь добавим штатную остановку и разберём рабочие границы сервера. Это не урок публикации: проект остаётся локальным стендом на 127.0.0.1:4493. Никакие удалённые ресурсы, зависимости или учебные программы при подготовке не запускались.
У процесса есть несколько независимых видов состояния. Карточки и журнал сохраняются в SQLite. Сессии живут в карте памяти и исчезают после остановки. DOM браузера хранит последнее представление и может устареть. Исходники определяют поведение, но не являются резервной копией пользовательского файла базы. Перед сопровождением полезно перечислить эти различия: слово «сохранили проект» само по себе не уточняет, что именно переживёт перезапуск.
Фазы HTTP-запроса
У http.Server заданы ReadHeaderTimeout, ReadTimeout, WriteTimeout и IdleTimeout. Первый ограничивает ожидание заголовков, следующий — чтение обращения, третий — работу выдачи, четвёртый — простой соединения между запросами. Их значения выбраны для небольшого учебного интерфейса, а не получены из нагрузочных измерений. Не переносите числа автоматически на загрузку больших файлов или долгие операции: новый сценарий меняет договор допустимого времени.
Таймаут сервера не означает, что любая предметная работа гарантированно прекратилась в тот же момент. Важно передавать контекст в SQL и разумно ограничивать отдельные внешние операции. Наши обработчики уже используют контекст запроса, а SQLite получает предел ожидания занятости. При добавлении нового сервиса нужно повторить анализ его отмены. Один общий таймер вокруг процесса не определяет судьбу всех внутренних действий.
stop,done:=signal.NotifyContext(context.Background(),os.Interrupt,syscall.SIGTERM);defer done()
shutdownDone:=make(chan struct{})
go func(){<-stop.Done();ctx,cancel:=context.WithTimeout(context.Background(),5*time.Second);defer cancel();if err:=server.Shutdown(ctx);err!=nil{log.Print(err);_ = server.Close()};close(shutdownDone)}()
if err:=server.ListenAndServe();err==http.ErrServerClosed{<-shutdownDone}else if err!=nil{log.Print(err)}
Фрагмент завершения находится в main.go состояния lesson-14. Полный файл включает обработчики и настройки сервера предыдущих глав. signal.NotifyContext связывает сигнал остановки с контекстом, а отдельная горутина ждёт его отмены. Затем Shutdown перестаёт принимать новые соединения и даёт существующей работе ограниченное время. В нашем варианте ожидание составляет пять секунд; это выбранная граница учебного завершения.
Обычная остановка и авария
ListenAndServe при штатном Shutdown возвращает http.ErrServerClosed. Код не считает его неожиданной неисправностью. Если любое возвращённое значение отправлять в log.Fatal, обычное завершение будет выглядеть как авария и может обходить отложенную очистку. Здесь ошибка прослушивания и прекращение работы сервера рассматриваются раздельно. Такое отличие полезно для человека, который читает журнал: сигнал завершения не должен маскироваться под поломку порта.
Shutdown не является гарантией восстановления при отключении питания. Он действует, когда процесс получил возможность выполнить код завершения. Аварийное прекращение требует устойчивости самого хранилища и стратегии резервных копий. Транзакции SQLite охраняют согласованность связанных записей, однако резервная копия нужна для других случаев: повреждения диска, ошибочного удаления, неправильной миграции или нежелательной предметной операции, которая была успешно подтверждена.
Главная функция ждёт закрытия канала shutdownDone, чтобы не выйти сразу после возвращения ListenAndServe. Если ограниченное ожидание завершилось ошибкой, соединения закрываются принудительно. Только затем main доходит до отложенного закрытия базы. Закрытие не удаляет inventory.sqlite. Новый запуск должен открыть тот же файл и прочитать прежние карточки. Карта сессий при этом создаётся заново, поэтому браузер получает анонимное основание. Страница, оставшаяся в открытой вкладке, может содержать старый CSRF и старую версию карточки. Эти отказы после перезапуска объясняются разными слоями состояния, а не одной неопределённой ошибкой «кэш браузера».
Путь данных и резервная копия
В учебной версии файл базы выбирается относительно рабочей папки. Для постоянного использования нужно вынести путь в конфигурацию и проверить, что все экземпляры открывают ожидаемое место. Не следует оставлять выбор каталога случайной особенностью способа запуска. Также нельзя бездумно копировать активную SQLite-базу как обычный текстовый файл: согласованность копии зависит от режима журнала и состояния записи. Следует выбрать поддерживаемый механизм резервирования базы отдельно.
Архив курса содержит исходники, README, шаблоны и конфигурацию зависимостей. Он не содержит пользовательскую базу, скачанный htmx, карту сессий или готовый бинарный файл. Это делает границу воспроизведения понятной: читатель выбирает состояние, получает зависимости и создаёт собственные данные. Наличие ZIP не подтверждает, что примеры компилировались или запускались автором. В материалах прямо указан example_execution: not_run.
Начальные данные создаются через INSERT OR IGNORE. Это удобно для независимых учебных папок, но не является процедурой восстановления настоящего склада. Если пользователь удалил карточку напрямую, повторный запуск может вернуть фиксированную начальную строку. В зрелом приложении заполнение демонстрационного набора следует отделить от обычного старта, а миграции выполнять с конкретной версией. Курс не скрывает эту границу за словом «инициализация».
Что завершено и что требует продолжения
Согласованный результат серии — серверная страница с прогрессивной интерактивностью. Обычные формы и GET-ссылки имеют полноценный ответ. Htmx получает общий panel, а каталог и поиск читают одну SQL-модель. Ошибки формы сохраняют доступный ввод, версия предотвращает тихое перезаписывание устаревшей карточки, движение связывает изменение количества с записью журнала. Эти свойства определяются исходниками и описаны через ожидаемые сценарии самостоятельного прохождения.
При этом журнал не является полным аудитом, потому что обычное редактирование ещё позволяет прямо менять остаток. Настоящий вход не реализован: роли выбираются демонстрационно. Сессии не имеют общего хранилища для нескольких процессов и полной фоновой очистки. Русский поиск не получил полноценной Unicode-нормализации. Перечень ограничений помогает выбирать следующий предметный шаг, а не объявлять приложение готовым для любой корпоративной задачи из-за наличия таблицы и кнопок.
Если развивать учёт дальше, разумно сначала закрыть прямую корректировку количества отдельным разрешением и журналировать её основание. Затем отделить товары от остатков по местам, если один SKU хранится на нескольких складах. После этого выбрать настоящую идентификацию сотрудников и понятную процедуру резервирования. Такой порядок следует из текущего предметного договора. Он отличается от добавления случайных библиотек только потому, что они популярны в веб-разработке.
Для самостоятельной остановки читатель может завершить процесс привычным сигналом терминала и затем открыть ту же папку снова. Ожидается завершение прослушивания, сохранённая база и новая сессионная карта. Эти действия при подготовке не выполнялись и не являются отчётом о тестах. Готовый курс даёт последовательность, исходники и причины поведения; проверка конкретной среды остаётся отдельной разрешённой работой после локального чтения материалов.
Исходники урока
Оглавление серии · Скачать исходники. Полное самостоятельное состояние находится в lesson-14. Учебные приложения, зависимости, база и проверки при подготовке не запускались; описанный результат ожидается по исходникам. Материалы пока находятся в локальной редакционной версии.
Первичные источники: документация 1.