Как реализовать HTTP сервер на Go, который выполняет параллельные запросы к нескольким системам и агрегирует ответы в JSON
Что проверяют
Это задача на конкурентность и надёжность, а не на синтаксис: умеете ли вы запустить запросы параллельно, ограничить их по времени, собрать ошибки и не оставить висящих горутин. Следом обычно спрашивают, что вернуть клиенту, если одна система из трёх не ответила.
Как отвечать
Проговорите решение по слоям, потом пишите код:
- сервер:
http.ServeMuxс шаблонами вида"GET /aggregate"(с Go 1.22), уhttp.ServerвыставленыReadHeaderTimeoutи остальные таймауты, есть корректное завершение черезShutdown; - контекст: берёте
r.Context()и оборачиваете вcontext.WithTimeout, например на две секунды. Клиент ушёл или время вышло, и все исходящие запросы отменяются; - исходящие запросы: один общий
http.Clientс таймаутом, а неhttp.DefaultClient, у которого таймаута нет. Запрос создаётся черезhttp.NewRequestWithContext, проверяетсяresp.StatusCode, тело читается черезio.LimitReaderи обязательно закрывается; - параллелизм:
errgroup.WithContextизgolang.org/x/sync/errgroup. Первая ошибка отменяет контекст для остальных, аg.SetLimit(n)ограничивает число одновременных запросов, если систем много; - сбор результатов: заранее создайте срез длины
len(sources), и каждая горутина пишет в свой индекс. Мьютекс не нужен, порядок в ответе стабильный; - ответ:
w.Header().Set("Content-Type", "application/json"), затемjson.NewEncoder(w).Encode(result). У ответа своя структура с полями, а не сырая строка чужого тела.
Про частичный отказ скажите явно. Если данные одной системы необязательны, горутина не возвращает ошибку в группу, а кладёт её в поле ответа, например "errors": {"billing": "timeout"}, и клиент получает 200 с тем, что есть. Если обязательны, отдаёте 502 или 504.
Где спотыкаются
Печатают ошибку в горутине и возвращают nil. Клиент получает неполный JSON и не знает об этом.
Забывают таймауты. Одна зависшая система держит горутины и соединения, и под нагрузкой сервис заканчивается.
Не закрывают resp.Body или берут устаревший ioutil.ReadAll вместо io.ReadAll с ограничением размера. Соединения не возвращаются в пул, а огромный ответ одной системы съедает память.