HTTP clients
| http.Get | |
| POST JSON | |
| Timeouts save mornings | |
| Read and close the body |
http.Get
http.Get fetches a URL in a single call and hands back a response. The call can fail on DNS errors and refused connections, so check the error first. A 200 status means the body is worth reading. Short scripts live happily on Get alone.
package main
import (
"fmt"
"io"
"net/http"
)
func main() {
resp, err := http.Get("https://example.com")
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(resp.Status)
fmt.Println(len(body), "bytes")
}
The deferred Close runs when main returns and releases the connection. ReadAll collects the body for small answers. Big downloads deserve streaming straight to a file instead.
POST JSON
http.Post sends a body with a content type in one call. Marshal the payload first, then wrap the bytes in a reader. Servers usually answer with a status plus a JSON echo of what they stored. Decode that answer the same way the JSON lesson taught.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
)
type Signup struct {
Login string `json:"login"`
Age int `json:"age"`
}
func main() {
payload, err := json.Marshal(Signup{Login: "ada", Age: 36})
if err != nil {
fmt.Println(err)
return
}
resp, err := http.Post("https://example.com/signup", "application/json", bytes.NewReader(payload))
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
fmt.Println(resp.Status)
fmt.Println(string(body))
}
The blank identifier skips the read error here to keep the example short. Real code checks it like every other error. Requests with headers or custom methods need a Client and a Request object instead.
Timeouts save mornings
A client without a timeout can hang forever on a slow server. Set Timeout on http.Client so every request has a deadline. Ten seconds suits most interactive calls. Batch jobs may allow more, but they still need a limit.
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
client := &http.Client{Timeout: 10 * time.Second}
resp, err := client.Get("https://example.com")
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close()
fmt.Println(resp.Status)
}
Reuse one client for the whole program instead of building a new one per request. A shared client also reuses connections behind the scenes. Fine-grained control comes from contexts, covered in the next lesson.
Read and close the body
Every response body must be read and closed, or connections leak. Close even when you ignore the contents, and check the status code before parsing. A helper keeps this pattern in one place. Callers get either parsed data or a clear error.
package main
import (
"fmt"
"io"
"net/http"
)
func fetchText(url string) (string, error) {
resp, err := http.Get(url)
if err != nil {
return "", err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return "", fmt.Errorf("bad status: %s", resp.Status)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(body), nil
}
func main() {
text, err := fetchText("https://example.com")
if err != nil {
fmt.Println(err)
return
}
fmt.Println(len(text), "bytes")
}
Next: Context
Article author: Arthur Isaev