Go Programming: Building Web Services - Net HTTP
This document outlines the use of the net/http package in Go for building web services. It covers fundamental concepts, common patterns, and practical examples.
1. Introduction to net/http
The net/http package provides the core functionality for building HTTP clients and servers in Go. It's a powerful and flexible package that allows you to create everything from simple APIs to complex web applications.
Key Components:
http.Request: Represents an incoming HTTP request. Contains information like headers, method, URL, and body.http.ResponseWriter: Represents the outgoing HTTP response. Used to write headers and the response body.http.Handler: An interface with a single methodServeHTTP(w http.ResponseWriter, r *http.Request). This is the core of handling requests.http.HandleFunc(pattern string, handler func(http.ResponseWriter, *http.Request)): Registers a handler function for a specific URL pattern.http.ListenAndServe(addr string, handler http.Handler): Starts an HTTP server listening on the specified address and using the provided handler.http.NewServeMux(): Creates a new request multiplexer (router). Allows you to map different URL patterns to different handlers.
2. A Simple "Hello, World!" Server
Let's start with a basic example:
package main
import (
"fmt"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}
func main() {
http.HandleFunc("/", helloHandler)
fmt.Println("Server listening on :8080")
http.ListenAndServe(":8080", nil) // nil uses the default ServeMux
}
Explanation:
helloHandler: This function is our handler. It takes ahttp.ResponseWriter(to write the response) and ahttp.Request(containing request information).fmt.Fprintf(w, "Hello, World!"): Writes "Hello, World!" to the response writer.http.HandleFunc("/", helloHandler): RegistershelloHandlerto handle requests to the root path ("/").http.ListenAndServe(":8080", nil): Starts the server listening on port 8080. Thenilargument means it uses the defaultServeMux.
To run this:
- Save the code as
main.go. - Open a terminal and navigate to the directory where you saved the file.
- Run
go run main.go. - Open your web browser and go to
http://localhost:8080. You should see "Hello, World!".
3. Using ServeMux for Routing
For more complex applications, you'll want to use a ServeMux to route requests to different handlers based on the URL path.
package main
import (
"fmt"
"net/http"
)
func homeHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome to the Home Page!")
}
func aboutHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "About Us Page")
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", homeHandler)
mux.HandleFunc("/about", aboutHandler)
fmt.Println("Server listening on :8080")
http.ListenAndServe(":8080", mux)
}
Explanation:
mux := http.NewServeMux(): Creates a newServeMux.mux.HandleFunc("/", homeHandler): RegistershomeHandlerfor the root path.mux.HandleFunc("/about", aboutHandler): RegistersaboutHandlerfor the/aboutpath.http.ListenAndServe(":8080", mux): Starts the server, using themuxto handle routing.
Now, visiting http://localhost:8080 will show "Welcome to the Home Page!", and http://localhost:8080/about will show "About Us Page".
4. Handling Different HTTP Methods
You can handle different HTTP methods (GET, POST, PUT, DELETE, etc.) within the same handler function by checking r.Method.
package main
import (
"fmt"
"net/http"
)
func formHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
fmt.Fprintf(w, "<form method='POST' action='/form'>Name: <input type='text' name='name'><button>Submit</button></form>")
} else if r.Method == http.MethodPost {
name := r.FormValue("name")
fmt.Fprintf(w, "Hello, %s!", name)
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
func main() {
http.HandleFunc("/form", formHandler)
fmt.Println("Server listening on :8080")
http.ListenAndServe(":8080", nil)
}
Explanation:
r.Method: Contains the HTTP method used in the request.http.MethodGetandhttp.MethodPost: Constants representing the GET and POST methods.r.FormValue("name"): Retrieves the value of the "name" field from the form data.http.Error(w, "Method not allowed", http.StatusMethodNotAllowed): Sends an error response with a 405 status code.
5. Serving Static Files
The http.FileServer function makes it easy to serve static files (HTML, CSS, JavaScript, images, etc.).
package main
import (
"fmt"
"net/http"
"log"
)
func main() {
fs := http.FileServer(http.Dir("static")) // "static" is the directory containing your static files
http.Handle("/static/", http.StripPrefix("/static/", fs))
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World! Check out <a href='/static/index.html'>static files</a>")
})
fmt.Println("Server listening on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}
Explanation:
http.Dir("static"): Creates aFileSystemthat serves files from the "static" directory.http.FileServer(...): Creates an HTTP handler that serves files from theFileSystem.http.StripPrefix("/static/", fs): Removes the "/static/" prefix from the URL path before passing it to theFileServer. This allows you to access files in the "static" directory using URLs like/static/index.html.http.Handle("/static/", ...): Registers the handler for the "/static/" path.
Create a directory named static in the same directory as your Go file and add an index.html file to it. For example:
<!-- static/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>Static File Example</title>
</head>
<body>
<h1>This is a static HTML file!</h1>
</body>
</html>
6. Handling JSON Requests and Responses
Go's encoding/json package is used to marshal (encode) Go data structures into JSON and unmarshal (decode) JSON data into Go data structures.
package main
import (
"encoding/json"
"fmt"
"net/http"
)
type Person struct {
Name string `json:"name"` // `json:"name"` specifies the JSON key
Age int `json:"age"`
}
func personHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
person := Person{Name: "Alice", Age: 30}
w.Header().Set("Content-Type", "application/json") // Set the Content-Type header
json.NewEncoder(w).Encode(person)
} else if r.Method == http.MethodPost {
var person Person
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&person)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
fmt.Fprintf(w