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:
- Middleware
CORSMiddleware
: This middleware function adds CORS headers to HTTP responses and checks for the HTTP OPTIONS method to allow preflight requests. - 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. - 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
, andAccess-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: