programming

18 Building Cross-Origin Resource Sharing (CORS) Middleware Using the httprouter Library in Golang

Cross-Origin Resource Sharing (CORS) is a security mechanism that allows or restricts HTTP requests from different domains. In web application development, it is often necessary to interact with APIs that do not originate from the same domain as the application. Therefore, CORS middleware is required to control and grant access from different origins, ensuring that requests from various sources can be safely accepted and processed by the server.

In this article, we will discuss how to create CORS middleware in Go using the httprouter library. This article will also cover unit testing for this middleware to ensure that it is easy to understand, even for beginners in web development with Go.

Step 1: Setting Up the Golang Project

The first step is to set up a Golang project and add the necessary dependencies. Here, we will use httprouter to handle HTTP routing.

Step 1.1: Install the httprouter Library

To get started, ensure that Golang is installed. Then, create a project folder and navigate to it:

mkdir cors-middleware
cd cors-middleware

Create a go.mod file by running the following command:

go mod init cors-middleware

Install the httprouter dependency:

go get github.com/julienschmidt/httprouter

Once the dependencies are installed, we can start writing the code.

Step 1.2: Setting Up Routing in Main

Create a main.go file with the following code:

package main

import (
	"log"
	"net/http"
	"github.com/julienschmidt/httprouter"
)

// Define CORS middleware function
func CORSMiddleware(next httprouter.Handle) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		// Allow all origins, methods, and headers by default
		w.Header().Set("Access-Control-Allow-Origin", "*")
		w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
		w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
		w.Header().Set("Access-Control-Allow-Credentials", "true")

		// If it's an OPTIONS request, respond with 200 immediately
		if r.Method == http.MethodOptions {
			w.WriteHeader(http.StatusOK)
			return
		}

		// Call the next handler
		next(w, r, ps)
	}
}

func main() {
	router := httprouter.New()

	// Apply middleware to a specific route
	router.GET("/", CORSMiddleware(func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		w.Write([]byte("Hello, world!"))
	}))

	log.Fatal(http.ListenAndServe(":8080", router))
}

Explanation:

  1. Middleware CORSMiddleware: This middleware function adds CORS headers to HTTP responses and checks for the HTTP OPTIONS method to allow preflight requests.
  2. Handler Route: The middleware is applied to the GET handler at the root (/). If an OPTIONS request is received, the server responds with a 200 status code.
  3. Running the Server: At the end of the code, the server runs on port 8080 using httprouter.

Step 2: Adding Unit Tests for CORS Middleware

Next, we will add unit tests to verify that our middleware functions correctly. We will use the net/http/httptest package to test the middleware’s response.

Step 2.1: Creating a Unit Test File

Create a new file named main_test.go to write unit tests.

package main

import (
	"net/http"
	"net/http/httptest"
	"testing"
)

// TestCORSHeader ensures that CORS headers are correctly set
func TestCORSHeader(t *testing.T) {
	handler := CORSMiddleware(func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		w.Write([]byte("Test"))
	})

	req, _ := http.NewRequest("GET", "/", nil)
	rr := httptest.NewRecorder()

	// Call the handler
	handler(rr, req, nil)

	// Check the CORS headers
	if rr.Header().Get("Access-Control-Allow-Origin") != "*" {
		t.Errorf("Expected Access-Control-Allow-Origin header to be '*', got %s", rr.Header().Get("Access-Control-Allow-Origin"))
	}
	if rr.Header().Get("Access-Control-Allow-Methods") != "GET, POST, PUT, DELETE, OPTIONS" {
		t.Errorf("Expected Access-Control-Allow-Methods header to be 'GET, POST, PUT, DELETE, OPTIONS', got %s", rr.Header().Get("Access-Control-Allow-Methods"))
	}
	if rr.Header().Get("Access-Control-Allow-Headers") != "Content-Type, Authorization" {
		t.Errorf("Expected Access-Control-Allow-Headers header to be 'Content-Type, Authorization', got %s", rr.Header().Get("Access-Control-Allow-Headers"))
	}
}

// TestOPTIONSRequest checks that OPTIONS requests are handled correctly
func TestOPTIONSRequest(t *testing.T) {
	handler := CORSMiddleware(func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		w.Write([]byte("Test"))
	})

	req, _ := http.NewRequest("OPTIONS", "/", nil)
	rr := httptest.NewRecorder()

	// Call the handler
	handler(rr, req, nil)

	// Check status code for OPTIONS request
	if rr.Code != http.StatusOK {
		t.Errorf("Expected status 200 OK, got %d", rr.Code)
	}
}

Explanation of Unit Tests:

  • TestCORSHeader: Tests whether CORS headers are correctly set (Access-Control-Allow-Origin, Access-Control-Allow-Methods, and Access-Control-Allow-Headers).
  • TestOPTIONSRequest: Ensures that OPTIONS requests receive a 200 status code response.

To run the unit tests, use the following command:

go test

Step 3: Running the Application

Now, you can run the application using:

go run main.go

Visit http://localhost:8080 to see the application in action. You will see the response “Hello, world!” and can verify that CORS is working using browser developer tools to check the headers.

Conclusion

Building CORS middleware in Go using httprouter is straightforward and provides full flexibility in controlling how applications interact with different origins. With unit testing, we ensure that our middleware functions correctly and behaves as expected. You can extend this middleware further to include additional rules and features based on your application’s needs.

For more insights, check out these resources:

comments powered by Disqus