Возвращайте настоящие данные каталога

GolangBeginner
Практиковаться сейчас

Введение

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

Вам уже должны быть знакомы функции, структуры, срезы, карты и модули Go, а также основные пути в терминале. HTTP, JSON и тесты обработчиков объясняются здесь. Браузерная среда содержит Go и готовую страницу. Этот модуль запускается независимо: файлы из другой лабораторной работы не нужны. Отведите около 55 минут с учётом чтения и обычных ошибок; это оценка автора.

Выберите один предмет, сохранив работу списка

На этом шаге сохраните работающую коллекцию из одной карточки и добавьте настоящие сведения о предмете. Начните во вкладке Workspace. Её оболочка открывает /home/labex/project/lost-found; следующая команда cd восстановит этот каталог, если вы вернётесь позже.

Go установлен в /usr/local/go/bin. export добавляет этот каталог в путь поиска команд этой оболочки, сохраняя существующий $PATH. Это действует в текущем сеансе терминала; повторите в новой оболочке. Глобальные настройки оболочки не меняются.

cd /home/labex/project/lost-found
export PATH=/usr/local/go/bin:$PATH

curl отправляет запрос. -i включает заголовки ответа, а одинарные кавычки сохраняют URL целиком. GET запрашивает чтение ресурса. /api/items обозначает коллекцию; 127.0.0.1:8080 — сервис на этой же машине. Сначала используйте обычный пример:

curl -i 'http://127.0.0.1:8080/api/items'

Ожидайте 200 OK, заголовок Content-Type: application/json и массив с LF-101, синим зонтом в библиотеке. Статус HTTP описывает результат запроса; тело содержит данные. JSON использует квадратные скобки для массивов и фигурные — для объектов. Один 200 не доказывает полноту коллекции. Переключитесь в Lost and Found, посмотрите её одну карточку, затем вернитесь в Workspace.

cat показывает файл без редактирования. Прочитайте исходные записи, входную структуру и свой небольшой начальный обработчик:

cat items.json
cat main.go
cat handler.go

Записей шесть. Item объединяет ID, Name, Category и Location; экспортируемые поля с прописной буквы могут кодироваться. Теги вида `json:"id"` выбирают строчные ключи JSON. main.go загружает коллекцию, обслуживает подготовленную страницу и передаёт свой срез предметов в catalogHandler. Сохраните main.go, go.mod, items.json, serve.py и web/; редактируйте только handler.go и дополнительные файлы Go или тесты.

Перед изменением кода предскажите разницу запросов LF-101 и LF-103. Оба названия — Blue umbrella, но места — Library desk и Cafe counter. Какое поле позволяет выбрать ровно одну запись? Теперь запросите второй ID:

curl -i 'http://127.0.0.1:8080/api/items/LF-103'

Начальная версия по-прежнему возвращает массив с первой записью. Ответ получен, но сведения неверны. Ваша задача — вернуть объект, выбранный по ID, сохранив доступность текущего ответа списка. Не решайте задачу возвратом первого объекта для каждого пути.

Обработчик — код Go, вызываемый для каждого запроса. r *http.Request указывает на входящий запрос. w http.ResponseWriter — место назначения: Header задаёт заголовки, операции записи отправляют тело. http.Handler описывает это поведение; http.HandlerFunc приспосабливает функцию к нему. Возвращённая анонимная функция запоминает переданные items. Писать разборщик HTTP не нужно.

Перед редактированием сохраните копию. cp копирует файл; расширение .backup намеренно отличается от .go, которое создало бы повторные определения при компиляции пакета Go.

cp handler.go handler.go.backup

Откройте настоящий файл в терминальном редакторе. LC_ALL=C.UTF-8 в области этой команды заставляет nano интерпретировать текст как UTF-8, включая буквы с диакритикой. Это влияет только на этот вызов редактора, не на глобальные настройки оболочки:

LC_ALL=C.UTF-8 nano handler.go

В nano Ctrl означает удерживать Control при нажатии указанной клавиши. Перемещайтесь стрелками. Нажмите Ctrl+W, введите catalogHandler, затем Enter, чтобы найти функцию; Ctrl+C отменяет незавершённый поиск. Редактируйте обычным вводом, Backspace и Delete. Ctrl+K вырезает текущую строку; используйте только на строке, которую намерены заменить. Подсказки клавиш внизу используют ^ для Control. Вы редактируете существующий обработчик, а не создаёте вторую программу.

Реализуйте следующие решения внутри возвращённой функции, после существующего заголовка типа содержимого. Работайте с одной веткой за раз:

Сначала прочитайте r.URL.Path. Если он равен /api/items, сохраните кодирование начального массива items[:1] и немедленно вернитесь. Это сохраняет обычный список с одной карточкой, пока вы работаете над сведениями.

Иначе распознайте префикс /api/items/. Добавьте импорт стандартной библиотеки strings рядом с существующими импортами. strings.HasPrefix(path, prefix) проверяет префикс, а strings.TrimPrefix(path, prefix) удаляет его. Сохраните оставшийся ID в локальной переменной.

Затем обойдите переданный срез через for _, item := range items. Сравните item.ID с запрошенным ID, а не с названием. Для совпадения закодируйте этот объект item и немедленно вернитесь. json.NewEncoder(w).Encode(value) записывает выбранное значение Go как JSON: срез становится массивом, одна структура — объектом. Цикл должен изучать каждую запись, не только items[0].

После этого, если ни один ID не совпал или путь вне этих маршрутов, задайте 404 перед записью ошибки JSON. w.WriteHeader(http.StatusNotFound) задаёт статус. Небольшое совместимое с JSON значение ошибки — map[string]string{"error": "item not found"}; строка error должна быть непустой. Закодируйте эту карту. Не записывайте ещё один список после ошибки.

Первая запись ответа обычно фиксирует 200, поэтому задавайте другой статус до кодирования. Encode возвращает error: nil означает отсутствие сбоя. В этом примере все значения — строки, совместимые с JSON. Отключившийся клиент всё ещё может вызвать сбой записи; отправка второго ответа после начала тела не восстановит соединение. Ранние возвраты также не дают дописать два ответа.

Сохраните через Ctrl+O, подтвердите существующее имя handler.go клавишей Enter, затем выйдите через Ctrl+X. Изучите сохранённый файл:

cat handler.go

Если открыли не тот файл, выйдите без сохранения: Ctrl+X, затем N при вопросе о сохранении. Выполните pwd, чтобы проверить своё местоположение, восстановите первый cd и откройте правильное имя. Если запрос сохранения показывает нежелательное имя, Ctrl+C отменяет его. Не перезаписывайте исходные данные ради исправления ошибки редактирования.

gofmt -w форматирует указанный файл Go на месте. Подготовленный serve.py собирает ваш код, прежде чем заменить только зарегистрированный сервер этой рабочей среды. Он не предоставляет реализацию обработчика. При ошибке сборки предыдущий рабочий процесс сохраняется, поэтому старый ответ браузера не доказывает, что новый код скомпилировался.

gofmt -w handler.go
python3 serve.py

Ожидайте Lost and Found ready on port 8080. Если форматирование или компиляция не удались, прочитайте имя файла и номер строки. Снова откройте nano handler.go, найдите соседнее выражение через Ctrl+W, исправьте только этот участок, сохраните, выйдите и повторите обе команды. Сохраните уже работающую логику; не восстанавливайте начальную копию поверх последующего прогресса. При No such file восстановите первый cd. Диагностика сервиса находится в .work/server.log, её можно прочитать через cat .work/server.log; опечатка не является причиной сбрасывать VM.

curl -i 'http://127.0.0.1:8080/api/items/LF-103'

Ожидайте один объект, у которого id — LF-103, а location — Cafe counter, не массив и не библиотечный зонт. Проверьте, что обычный список по-прежнему возвращает свой массив-образец:

curl -i 'http://127.0.0.1:8080/api/items'

Вы добавили выбор, сохранив формат коллекции работающей страницы. Следующий шаг намеренно расширяет список до всех записей.

Верните полную коллекцию

На этом шаге превратите список из одной записи в полный каталог только для чтения, сохранив написанные вами сведения. Начните в Workspace, в /home/labex/project/lost-found.

Перед редактированием прочитайте выражение среза в ветке коллекции. items[:1] выбирает префикс до индекса один, скрывая остальные пять записей. Предскажите ответ, если кодировщик получит сами items. В отличие от жёстко заданного массива из шести предметов, этот аргумент работает и для другого переданного среза.

LC_ALL=C.UTF-8 nano handler.go

Найдите items[:1] через Ctrl+W и Enter. Измените только кодируемое значение коллекции на полный входной срез. Сохраните заголовок JSON, цикл сведений, ошибку отсутствующего ID и ранние возвраты. Сохраните под тем же именем через Ctrl+O и Enter, затем Ctrl+X.

Пустая коллекция должна кодироваться как [], никогда не null. Пустой срез, отличный от nil, кодируется как []; срез nil кодировался бы как null. Независимый пустой ввод этого модуля — []Item{}. Не заменяйте пустой результат ошибкой отсутствующего предмета: список и сведения имеют разный смысл. Порядок массива и ключей объекта свободный, но название, категория и место каждого ID должны оставаться вместе.

gofmt -w handler.go
python3 serve.py

Запросите коллекцию, затем оба зонта по отдельным ID:

curl -i 'http://127.0.0.1:8080/api/items'
curl -i 'http://127.0.0.1:8080/api/items/LF-101'
curl -i 'http://127.0.0.1:8080/api/items/LF-103'

Ожидайте все шесть полных записей в массиве. LF-101 по-прежнему относится к Library desk, а LF-103 — к Cafe counter, хотя оба названия — Blue umbrella. Если сведения переместились, изучите сравнение ID и значение цикла, которое кодируете.

Следующий запрос намеренно использует отсутствующий ID. Ожидайте HTTP 404 и объект JSON с непустой строкой error, а не пустой список. curl обычно всё равно завершается успешно при полученном ответе с ошибкой HTTP: получение ответа отличается от невозможности подключиться.

curl -i 'http://127.0.0.1:8080/api/items/no-such-id'

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

Напишите тест, различающий предметы по идентификаторам

На этом шаге спроектируйте свой целевой тест обработчика без запуска второго настоящего сервера, затем используйте реальную страницу. Начните в Workspace в том же каталоге.

Сначала решите, что должен выявлять тест: обработчик может вернуть первое совпавшее название вместо запрошенного ID. Выберите две собственные небольшие записи с одинаковым названием, но разными ID и местами, вне коллекции веб-страницы. Запросите второй ID. Запишите, какие статус, ID и место должны вернуться. Этот независимый набор данных заставит фиксированный ответ работающего сервиса не пройти проверку и сделает тест полезным за пределами шести подготовленных записей.

Go находит тесты в файлах с окончанием _test.go. Имя тестовой функции начинается с Test, она принимает t *testing.T. Создайте файл теста в nano:

LC_ALL=C.UTF-8 nano handler_test.go

Начните с package main. Импортируйте encoding/json, net/http/httptest и testing. Добавьте функцию вроде func TestSecondIdentity(t *testing.T) { ... }, заменив точки операторами теста. Тело функции состоит из четырёх частей:

Сначала создайте свой набор из двух записей []Item, используя экспортируемые имена полей из main.go. Передайте его в catalogHandler, не загружая и не меняя items.json.

Затем создайте запрос сведений о второй записи. httptest.NewRequest("GET", path, nil) строит запрос; последний аргумент означает отсутствие тела. httptest.NewRecorder() возвращает рекордер ответа. Вызов ServeHTTP(recorder, request) вашего обработчика запускает настоящий обработчик и фиксирует заголовки, статус и тело.

Далее объявите var got Item. Декодируйте через json.Unmarshal(recorder.Body.Bytes(), &got). &got — адрес, куда будут записаны поля. Сначала проверьте возвращённый error: неверный JSON нельзя принять за корректную пустую структуру. Например, if err != nil { t.Fatal(err) } останавливает тест с понятной ошибкой.

После этого проверьте ожидаемые статус и идентичность предмета. recorder.Code содержит статус, а got.ID и got.Location — декодированные поля. Сравните их со второй записью тестового набора. Используйте if и t.Fatalf, чтобы завершить тест ошибкой с фактическими значениями. %d показывает число, %+v — поля структуры. Не проверяйте лишь наличие какого-то JSON.

Согласованно используйте выбранные ID и места в запросе и проверках. Держите тестовые данные, выполнение и проверки в одной тестовой функции, чтобы рассуждение легко прослеживалось. Сохраните через Ctrl+O и Enter, затем Ctrl+X. Среда не предоставляет за вас проходящий тест.

go test -v . запускает тесты этого пакета: -v показывает имена тестов, . выбирает текущий пакет.

gofmt -w handler_test.go
go test -v .

Ожидайте имя своего теста и затем PASS. При сбое должны выводиться реальные статус или предмет. Если тест не проходит, решите, неверна ли ожидаемая связь в тестовом наборе или обработчик; исправьте только эту часть в nano и повторите тест. Ошибка компилятора указывает файл и строку для изучения. Пересоберите через python3 serve.py после любого исправления обработчика, чтобы браузер также использовал текущий код. Сохраните хотя бы один проходящий тест обработчика Go в этой рабочей среде; итоговая проверка запускает ваши тесты изолированно и проверяет API с другими переданными входными записями.

Переключитесь на Lost and Found и обновите страницу. Ожидайте 6 items found. Откройте View item у LF-103, прочитайте его номер и место получения, затем выберите Back to collection. Два зонта должны оставаться различимыми. Этот модуль предоставляет просмотр коллекции и отдельных предметов; следующая пошаговая лабораторная работа добавляет элементы поиска вместе с соответствующим поведением фильтрации API.

Настоящая страница каталога со всеми исходными записями

Найдите шесть карточек и их отдельные номера. Страница использует ваш работающий API и не содержит резервной коллекции. Вернитесь в Workspace, если нужно исправление. Теперь у вас есть пригодный каталог и тест, для которого вы выбрали данные и проверки, выявляющие настоящую ошибку идентичности предметов.

Резюме

Вы закодировали записи Go в JSON, выбрали ответы списка и отдельных предметов по пути, сохранили связь одинаковых названий с их собственными ID и вернули настоящий статус отсутствующего предмета. Также вы написали изолированный тест с httptest и использовали работающую веб-страницу. Поиск и более строгая обработка ошибок станут отдельной задачей следующей пошаговой лабораторной работы.