Question
I want to parse the response from an HTTP request in Go, but I am having trouble accessing the body as a string.
Here is my code:
package main
import (
"fmt"
"io/ioutil"
"net/http"
"reflect"
)
func main() {
resp, err := http.Get("http://google.hu/")
if err != nil {
// handle error
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
// handle error
return
}
ioutil.WriteFile("dump", body, 0600)
for i := 0; i < len(body); i++ {
fmt.Println(body[i]) // This prints uint8 values as numbers
}
fmt.Println(reflect.TypeOf(body))
fmt.Println("done")
}
ioutil.WriteFile correctly writes the response to a file, but when I print the body in a loop, I only get numbers because each element appears to be a uint8.
How can I access the HTTP response body as a string in Go?
Short Answer
By the end of this page, you will understand why an HTTP response body is read as a []byte in Go, how to convert it into a string, and when to work with bytes versus strings. You will also learn the most common patterns used in real Go code when reading HTTP responses.
Concept
In Go, the body of an HTTP response is usually read as raw bytes, not directly as text.
When you call:
body, err := ioutil.ReadAll(resp.Body)
body has the type:
a[]byte
A []byte is a slice of bytes. Each byte is a number from 0 to 255, which is why this code:
fmt.Println(body[i])
prints numbers.
To view the entire response as text, you convert the byte slice into a string:
text := string(body)
fmt.Println(text)
This works because Go strings are sequences of bytes too, but they are intended for textual data.
Why this matters:
- HTTP responses are transmitted as bytes.
- Some responses are text, such as HTML, JSON, XML, or plain text.
- Some responses are binary, such as images, ZIP files, or PDFs.
- Go gives you the raw bytes first so you can decide how to interpret them.
If the response is textual, converting []byte to string is the normal approach. If the response is binary, you should keep it as bytes.
Mental Model
Think of []byte as a box of raw Lego pieces and string as the finished word or sentence made from those pieces.
[]byte= raw data, one byte at a timestring= human-readable text built from those bytes
When you loop through body[i], you are inspecting each individual Lego piece, so you see numbers.
When you do string(body), you assemble those pieces into readable text.
Another way to think about it:
resp.Bodyis a stream of incoming dataioutil.ReadAllreads the stream into memory as bytesstring(body)tells Go: "Interpret these bytes as text"
Syntax and Examples
The basic pattern is:
resp, err := http.Get("https://example.com")
if err != nil {
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
text := string(body)
fmt.Println(text)
Example: print the response body as a string
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
resp, err := http.Get("https://example.com")
if err != nil {
fmt.Println("request error:", err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("read error:", err)
return
}
fmt.Println(string(body))
}
Example: print bytes versus string
package main
import "fmt"
func main() {
data := []byte("Hello")
fmt.Println(data) // [72 101 108 108 111]
fmt.Println(data[0]) // 72
fmt.Println(string(data)) // Hello
}
Explanation:
datais a byte slice.data[0]is the first byte, which is the ASCII/UTF-8 value forH.string(data)converts the whole byte slice into readable text.
Modern note
In newer Go code, io.ReadAll is preferred over ioutil.ReadAll:
body, err := io.ReadAll(resp.Body)
The idea is exactly the same.
Step by Step Execution
Consider this example:
package main
import (
"fmt"
)
func main() {
body := []byte("Go")
fmt.Println(body)
fmt.Println(body[0])
fmt.Println(body[1])
fmt.Println(string(body))
}
Step by step:
-
body := []byte("Go")- Go creates a byte slice containing the UTF-8 bytes for
Gando. - Internally, this is roughly
[71 111].
- Go creates a byte slice containing the UTF-8 bytes for
-
fmt.Println(body)- Prints the slice values.
- Output:
[71 111]
-
fmt.Println(body[0])- Prints the first byte.
- Output:
71
-
fmt.Println(body[1])- Prints the second byte.
- Output:
111
-
fmt.Println(string(body))- Converts the whole byte slice to a string.
- Output:
Go
Now apply the same idea to an HTTP response:
body, err := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
You first read the body as bytes, then convert those bytes into text.
Real World Use Cases
Converting an HTTP response to a string is common when the response contains text-based formats.
HTML pages
html := string(body)
fmt.Println(html)
Used for:
- scraping pages
- checking page contents
- debugging server responses
JSON APIs
jsonText := string(body)
fmt.Println(jsonText)
Useful for:
- logging API responses during development
- inspecting returned JSON before unmarshaling
Plain text services
Some endpoints return plain text such as status messages or generated tokens.
message := string(body)
fmt.Println(message)
Error responses
If an API returns status 400 or 500, the body may still contain a useful text explanation.
if resp.StatusCode >= 400 {
fmt.Println("server error:", string(body))
}
Command-line utilities
Go scripts and CLI tools often fetch URLs and print the text output directly to the terminal.
Real Codebase Usage
In real Go projects, developers usually do more than just convert the response body to a string.
1. Check the status code first
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("request failed: %s - %s", resp.Status, string(body))
}
This is a common validation pattern.
2. Read once, then reuse the data
resp.Body is a stream. Once read, it is consumed.
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
text := string(body)
fmt.Println(text)
Developers often store the body in a variable, then:
- log it
- parse it
- validate it
3. Unmarshal JSON directly from bytes
In production code, if the body is JSON, developers usually keep it as []byte and unmarshal it.
var result map[string]interface{}
err = json.Unmarshal(body, &result)
The string conversion may still be useful for debugging.
4. Use guard clauses for errors
Go code commonly exits early when errors happen:
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
This keeps the code simple and readable.
5. Avoid reading huge responses into memory unnecessarily
For small responses, ReadAll is fine. For very large responses, developers may stream or decode incrementally instead of converting everything to a string at once.
Common Mistakes
Mistake 1: Printing each byte and expecting text
Broken example:
for i := 0; i < len(body); i++ {
fmt.Println(body[i])
}
Why it happens:
body[i]is a single byte- a byte is a number, not a full string
Fix:
fmt.Println(string(body))
Mistake 2: Forgetting to handle the read error
Broken example:
body, err := ioutil.ReadAll(resp.Body)
fmt.Println(string(body))
If err is not checked, you may print incomplete or invalid data.
Fix:
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println("read error:", err)
return
}
fmt.Println(string(body))
Mistake 3: Reading the body twice
Broken example:
body1, _ := ioutil.ReadAll(resp.Body)
body2, _ := ioutil.ReadAll(resp.Body)
fmt.Println(string(body1))
fmt.Println(string(body2))
Why it fails:
- the second read gets nothing because the stream is already consumed
Fix:
body, _ := ioutil.ReadAll(resp.Body)
text := string(body)
fmt.Println(text)
Mistake 4: Treating all responses as text
Broken assumption:
fmt.Println(string(body))
This is only meaningful if the response actually contains text.
For binary files like images or PDFs, keep the data as bytes.
Mistake 5: Ignoring character encoding issues
Most modern web responses use UTF-8, which works well with string(body). But if a response uses a different encoding, the text may look wrong after conversion.
In that case, the bytes are still valid, but you may need decoding logic for that specific encoding.
Comparisons
| Concept | What it is | Best use |
|---|---|---|
[]byte | Raw bytes | HTTP bodies, files, binary data, JSON decoding |
string | Text | Printing, searching, displaying, text processing |
body[i] | One byte from the slice | Inspecting individual bytes |
string(body) | Converts all bytes into text | Viewing a text response |
[]byte vs string
data := []byte("Hello")
text := string(data)
- Use
[]bytewhen working with raw data. - Use
stringwhen working with human-readable text.
ioutil.ReadAll vs io.ReadAll
| Function | Status | Notes |
|---|---|---|
ioutil.ReadAll | Older style | Works, but ioutil has been deprecated in newer Go versions |
io.ReadAll | Preferred | Modern replacement |
Printing bytes one by one vs printing the whole string
| Approach | Output style |
|---|---|
fmt.Println(body[i]) | Numeric byte values |
fmt.Println(string(body)) | Human-readable text |
If your goal is to inspect text, convert the full slice to a string.
Cheat Sheet
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
text := string(body)
fmt.Println(text)
Key facts
resp.Bodyis a stream.ioutil.ReadAll(resp.Body)returns[]byte.body[i]is a single byte, so it prints as a number.string(body)converts the full byte slice to text.- Read the body only once.
- Call
defer resp.Body.Close()after a successful request.
Modern equivalent
body, err := io.ReadAll(resp.Body)
Useful pattern
resp, err := http.Get(url)
if err != nil {
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return
}
fmt.Println(string(body))
When not to use string(body)
Avoid converting to string if the response is binary, such as:
- images
- ZIP files
- PDFs
- audio/video data
FAQ
How do I convert an HTTP response body to a string in Go?
Read the body into a []byte, then convert it with string(body).
Why does body[i] print numbers instead of characters?
Because body[i] is a single byte of type uint8. Go prints its numeric value.
Is []byte the same as string in Go?
No. They are different types. A []byte holds raw bytes, while a string represents text.
Can I print the whole HTTP response body directly?
Yes:
fmt.Println(string(body))
Should I use ioutil.ReadAll or io.ReadAll?
In newer Go versions, prefer io.ReadAll. Older examples often use ioutil.ReadAll.
Can I read resp.Body multiple times?
Not directly. It is a stream and is consumed when read. Store the result if you need to reuse it.
What if the response is JSON?
You can print it as a string for debugging, but in real code you often unmarshal the []byte into a Go struct or map.
Why does writing to a file work even though printing does not?
Writing bytes to a file preserves the raw data exactly. Printing one byte at a time shows numeric byte values rather than the full text.
Mini Project
Description
Build a small Go program that fetches a web page and prints both the raw byte length and the text content. This demonstrates the difference between []byte and string, which is one of the most common beginner questions when working with HTTP in Go.
Goal
Create a program that sends an HTTP request, reads the response body, converts it to a string, and prints the result safely.
Requirements
- Make an HTTP GET request to a public URL.
- Read the response body into a byte slice.
- Print the number of bytes received.
- Convert the byte slice to a string and print the text.
- Handle request and read errors properly.
Keep learning
Related questions
Are Go Maps Passed by Value or Reference?
Learn Go map parameter and return semantics: maps are passed by value, while copies share underlying data. Know when map pointers are useful.
Authentication for Go REST APIs: Sessions, Tokens, and Middleware
Learn practical authentication patterns for Go REST APIs, including password hashing, server sessions, bearer tokens, middleware, and security rules.
Automatic Build Versioning in Go: Embed Incrementing Build Numbers
Learn how to add automatic build versioning in Go using linker flags, build metadata, CI counters, and Git-based version values.