Как получить тело HTTP-ответа в виде строки в Go?
Чтение тела HTTP-ответа в строку с помощью io.ReadAll
В Go тело HTTP-ответа представлено интерфейсом io.ReadCloser (объединяет io.Reader и io.Closer). Чтобы получить его содержимое в виде строки, нужно прочитать все байты из ридера, а затем преобразовать их в строку. Самый простой и распространённый способ — использовать функцию io.ReadAll (доступна с Go 1.16) или ioutil.ReadAll (устарела, но всё ещё работает).
package main
import (
"fmt"
"io"
"net/http"
"log"
)
func main() {
// 1. Выполняем GET-запрос
resp, err := http.Get("https://jsonplaceholder.typicode.com/posts/1")
if err != nil {
log.Fatalf("Ошибка запроса: %v", err)
}
// 2. ОБЯЗАТЕЛЬНО закрываем тело ответа, чтобы не было утечки ресурсов
defer resp.Body.Close()
// 3. Читаем все байты из тела ответа
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatalf("Ошибка чтения тела: %v", err)
}
// 4. Преобразуем []byte в string
bodyString := string(bodyBytes)
// 5. Используем строку
fmt.Println("Статус:", resp.Status)
fmt.Println("Тело (первые 200 символов):")
fmt.Println(bodyString[:200])
}
Почему это работает?
io.ReadAllчитает все данные изresp.Bodyдо EOF и возвращает[]byte.- Преобразование
[]byteвstring— это просто приведение типа, которое копирует память (для маленьких ответов это не критично). - Обязательно закрывайте
resp.Bodyчерезdefer, иначе соединение может не вернуться в пул и произойдёт утечка.
Альтернативный способ: bytes.Buffer
Если вы используете старую версию Go (до 1.16), где нет io.ReadAll, можно использовать bytes.Buffer:
import (
"bytes"
"net/http"
)
func readBody(resp *http.Response) (string, error) {
buf := new(bytes.Buffer)
_, err := buf.ReadFrom(resp.Body)
if err != nil {
return "", err
}
return buf.String(), nil
}
Важные моменты
- Проверяйте статус-код: даже при успешном запросе
http.Getможет вернуть 4xx или 5xx. Всегда проверяйтеresp.StatusCode. - Ограничивайте размер: для больших ответов (например, файлы) не читайте всё в память — используйте
io.Copyс файлом или потоковую обработку. - Для JSON: после получения строки можно сразу декодировать через
json.Unmarshal(но эффективнее читать напрямую изresp.Bodyчерезjson.Decoder).
Полный пример с проверкой статуса
func fetchString(url string) (string, error) {
resp, err := http.Get(url)
if err != nil {
return "", fmt.Errorf("ошибка запроса: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("неожиданный статус: %s", resp.Status)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", fmt.Errorf("ошибка чтения: %w", err)
}
return string(body), nil
}
Этот паттерн — стандарт в Go для получения ответа в виде строки. Он простой, безопасный и хорошо документирован.
<<Использование ioutil.ReadFile (устаревший, но всё ещё рабочий способ)
До появления io.ReadAll в Go 1.16 стандартной практикой было использование ioutil.ReadAll из пакета io/ioutil. Хотя этот пакет объявлен устаревшим (deprecated), код всё ещё компилируется и работает. Если вы поддерживаете старый проект или не можете обновить Go, можно использовать его:
import (
"io/ioutil"
"net/http"
)
resp, _ := http.Get("https://example.com")
defer resp.Body.Close()
bodyBytes, _ := ioutil.ReadAll(resp.Body)
bodyString := string(bodyBytes)
Однако я рекомендую сразу привыкать к io.ReadAll, так как ioutil будет удалён в будущих версиях Go.
Прямое декодирование JSON (если ответ — JSON)
Если вы знаете, что ответ — это JSON, и хотите сразу получить структуру, не читая в строку, используйте json.NewDecoder:
import "encoding/json"
type Post struct {
UserID int `json:"userId"`
ID int `json:"id"`
Title string `json:"title"`
Body string `json:"body"`
}
func fetchPost(url string) (*Post, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var post Post
err = json.NewDecoder(resp.Body).Decode(&post)
if err != nil {
return nil, err
}
return &post, nil
}
Этот подход эффективнее, так как не создаёт промежуточную строку и работает с потоком данных напрямую. Но если вам нужна именно строка (например, для отладки или логирования), то первый вариант с io.ReadAll предпочтительнее.