Please purchase the course to watch this video.

Full Course
Networking in Go often involves higher-level protocols like HTTP, which is essential for creating web servers and clients that communicate with APIs. Utilizing the net/http
package from the Go standard library simplifies the process of sending HTTP requests and handling responses. This lesson demonstrates how to send GET and POST requests to a service like HTTP Bin, illustrating the use of context for managing requests, setting query parameters, and adding custom headers. Additionally, it highlights best practices such as creating a custom HTTP client, handling errors through status codes, and configuring form data. Effective management of HTTP requests is crucial for creating robust command-line applications and understanding response formats like JSON bolsters data handling. As HTTP interactions evolve, familiarity with encoding and decoding techniques will further enhance effective API communication.
No links available for this lesson.
While the net
package provides low-level networking capabilities, most real-world applications interact with higher-level protocols, particularly HTTP. Go's standard library includes the net/http
package, which makes it easy to work with HTTP for both client and server applications.
In this lesson, we'll focus on using the net/http
package to make HTTP requests as a client, which is a common requirement for CLI applications that interact with web services or APIs.
Making HTTP Requests with Go
Let's start by creating a simple program that makes a GET request to HTTPBin, a service designed for testing HTTP requests:
package main
import (
"context"
"fmt"
"io"
"log"
"net/http"
"os"
"os/signal"
"time"
)
func main() {
// Create a context that cancels on interrupt signal
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()
// Create a new HTTP request
req, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://httpbin.org/get", nil)
if err != nil {
log.Fatal("Failed to create request:", err)
}
// Send the request using the default HTTP client
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal("Failed to send request:", err)
}
defer resp.Body.Close()
// Print the status code
fmt.Println("Status:", resp.StatusCode)
// Copy the response body to stdout (up to 1MB)
_, err = io.CopyN(os.Stdout, resp.Body, 1024*1024)
if err != nil && err != io.EOF {
log.Fatal("Failed to read response:", err)
}
}
This code:
- Creates a context that cancels when the user presses Ctrl+C
- Creates an HTTP GET request to httpbin.org/get
- Sends the request using the default HTTP client
- Prints the status code of the response
- Copies up to 1MB of the response body to stdout
When we run this code, we'll see the JSON response from HTTPBin, which includes details about our request.
Creating a Custom HTTP Client
While using http.DefaultClient
is convenient, it's generally better to create a custom client with appropriate timeouts:
// Create a custom HTTP client with timeout
client := &http.Client{
Timeout: 10 * time.Second,
}
// Use the custom client instead of DefaultClient
resp, err := client.Do(req)
This prevents your application from hanging indefinitely if a server doesn't respond.
Adding Query Parameters
To add query parameters to a request, we can manipulate the request's URL:
// Get the current query values (if any)
q := req.URL.Query()
// Add query parameters
q.Add("name", "John")
q.Add("age", "20")
q.Add("name", "Sally") // Adding another value for "name"
// Update the request's URL with the new query string
req.URL.RawQuery = q.Encode()
These query parameters will appear in the URL as ?name=John&age=20&name=Sally
.
Setting Custom Headers
To add custom headers to a request:
// Add a custom header
req.Header.Add("X-User-ID", "foobar")
You can add multiple values for the same header by calling Add
multiple times. To set a header (replacing any existing values), use Set
instead:
req.Header.Set("X-User-ID", "foobar")
Making Different Types of Requests
The net/http
package supports all standard HTTP methods. To make a POST request, we change the method and update the URL:
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://httpbin.org/post", nil)
Sending Data with POST Requests
Unlike GET requests, POST requests typically include a body. There are several ways to send data with a POST request:
- Sending raw data:
data := strings.NewReader("foobar")
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://httpbin.org/post", data)
- Sending form data:
// Create form values
values := url.Values{}
values.Add("username", "Thanos")
// Create a string reader with the encoded values
data := strings.NewReader(values.Encode())
// Create the request
req, err := http.NewRequestWithContext(ctx, http.MethodPost, "https://httpbin.org/post", data)
// Set the content type header
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
- Using the PostForm convenience method:
values := url.Values{}
values.Add("username", "Thanos")
resp, err := http.DefaultClient.PostForm("https://httpbin.org/post", values)
Understanding HTTP Status Codes
HTTP responses include a status code that indicates the result of the request. These codes are grouped into ranges:
- 1xx (Informational): The request was received and is being processed
- 2xx (Success): The request was successfully received, understood, and accepted
- 3xx (Redirection): Further action needs to be taken to complete the request
- 4xx (Client Error): The request contains bad syntax or cannot be fulfilled
- 5xx (Server Error): The server failed to fulfill a valid request
To handle different status codes appropriately:
resp, err := client.Do(req)
if err != nil {
log.Fatal("Failed to send request:", err)
}
defer resp.Body.Close()
switch {
case resp.StatusCode >= 500:
fmt.Println("Server error, might retry later")
case resp.StatusCode >= 400:
fmt.Println("Client error, request was invalid")
case resp.StatusCode >= 300:
fmt.Println("Redirection occurred")
case resp.StatusCode >= 200:
fmt.Println("Request successful")
}
Best Practices for HTTP Clients
-
Always set timeouts:
client := &http.Client{ Timeout: 10 * time.Second, }
-
Use context for cancellation:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() req := req.WithContext(ctx)
-
Always close response bodies:
defer resp.Body.Close()
-
Handle redirects if needed:
client := &http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { if len(via) >= 10 { return errors.New("too many redirects") } return nil }, }
-
Limit response body size to prevent memory issues:
io.CopyN(dst, resp.Body, maxSize)
Summary
In this lesson, we've explored:
- Creating and sending HTTP requests with Go's
net/http
package - Setting up custom HTTP clients with timeouts
- Adding query parameters and custom headers to requests
- Making different types of requests (GET, POST)
- Sending data with POST requests
- Understanding and handling HTTP status codes
The net/http
package provides a robust foundation for HTTP communication in Go applications. In the next lesson, we'll look at encoding and decoding data formats, particularly JSON, which is commonly used in HTTP APIs.