Module: Building Web Services

Net HTTP

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 method ServeHTTP(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 a http.ResponseWriter (to write the response) and a http.Request (containing request information).
  • fmt.Fprintf(w, "Hello, World!"): Writes "Hello, World!" to the response writer.
  • http.HandleFunc("/", helloHandler): Registers helloHandler to handle requests to the root path ("/").
  • http.ListenAndServe(":8080", nil): Starts the server listening on port 8080. The nil argument means it uses the default ServeMux.

To run this:

  1. Save the code as main.go.
  2. Open a terminal and navigate to the directory where you saved the file.
  3. Run go run main.go.
  4. 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 new ServeMux.
  • mux.HandleFunc("/", homeHandler): Registers homeHandler for the root path.
  • mux.HandleFunc("/about", aboutHandler): Registers aboutHandler for the /about path.
  • http.ListenAndServe(":8080", mux): Starts the server, using the mux to 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.MethodGet and http.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 a FileSystem that serves files from the "static" directory.
  • http.FileServer(...): Creates an HTTP handler that serves files from the FileSystem.
  • http.StripPrefix("/static/", fs): Removes the "/static/" prefix from the URL path before passing it to the FileServer. 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