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:
1mkdir cors-middleware
2cd cors-middlewareCreate a go.mod file by running the following command:
1go mod init cors-middlewareInstall the httprouter dependency:
1go get github.com/julienschmidt/httprouterOnce 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:
1package main
2
3import (
4 "log"
5 "net/http"
6 "github.com/julienschmidt/httprouter"
7)
8
9// Define CORS middleware function
10func CORSMiddleware(next httprouter.Handle) httprouter.Handle {
11 return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
12 // Allow all origins, methods, and headers by default
13 w.Header().Set("Access-Control-Allow-Origin", "*")
14 w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
15 w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
16 w.Header().Set("Access-Control-Allow-Credentials", "true")
17
18 // If it's an OPTIONS request, respond with 200 immediately
19 if r.Method == http.MethodOptions {
20 w.WriteHeader(http.StatusOK)
21 return
22 }
23
24 // Call the next handler
25 next(w, r, ps)
26 }
27}
28
29func main() {
30 router := httprouter.New()
31
32 // Apply middleware to a specific route
33 router.GET("/", CORSMiddleware(func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
34 w.Write([]byte("Hello, world!"))
35 }))
36
37 log.Fatal(http.ListenAndServe(":8080", router))
38}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.
1func TestCORS(t *testing.T) {
2 type args struct {
3 method string
4 }
5 tests := []struct {
6 name string
7 args args
8 wantStatusCode int
9 wantNextCalled bool
10 wantCORSHeadersSet bool
11 }{
12 {
13 name: "OPTIONS request should return 200 without calling next",
14 args: args{
15 method: http.MethodOptions,
16 },
17 wantStatusCode: http.StatusOK,
18 wantNextCalled: false,
19 wantCORSHeadersSet: true,
20 },
21 {
22 name: "GET request should call next and return 200",
23 args: args{
24 method: http.MethodGet,
25 },
26 wantStatusCode: http.StatusOK,
27 wantNextCalled: true,
28 wantCORSHeadersSet: true,
29 },
30 {
31 name: "POST request should call next and return 200",
32 args: args{
33 method: http.MethodPost,
34 },
35 wantStatusCode: http.StatusOK,
36 wantNextCalled: true,
37 wantCORSHeadersSet: true,
38 },
39 }
40 for _, tt := range tests {
41 t.Run(tt.name, func(t *testing.T) {
42 recorder := httptest.NewRecorder()
43 request := httptest.NewRequest(tt.args.method, "/", nil)
44 params := httprouter.Params{}
45
46 nextCalled := false
47 next := func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
48 nextCalled = true
49 w.WriteHeader(http.StatusOK)
50 }
51
52 handler := CORS(next)
53 handler(recorder, request, params)
54
55 // Status code
56 if recorder.Code != tt.wantStatusCode {
57 t.Errorf("got status code %v, want %v", recorder.Code, tt.wantStatusCode)
58 }
59
60 // Next handler
61 if nextCalled != tt.wantNextCalled {
62 t.Errorf("next handler call = %v, want %v", nextCalled, tt.wantNextCalled)
63 }
64
65 // CORS headers
66 if tt.wantCORSHeadersSet {
67 headers := recorder.Header()
68 if headers.Get("Access-Control-Allow-Origin") != "*" {
69 t.Error("Access-Control-Allow-Origin header not set to *")
70 }
71 if headers.Get("Access-Control-Allow-Methods") == "" {
72 t.Error("Access-Control-Allow-Methods header missing")
73 }
74 if headers.Get("Access-Control-Allow-Headers") == "" {
75 t.Error("Access-Control-Allow-Headers header missing")
76 }
77 if headers.Get("Access-Control-Allow-Credentials") != "true" {
78 t.Error("Access-Control-Allow-Credentials header not set to true")
79 }
80 }
81 })
82 }
83}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:
1go testStep 3: Running the Application
Now, you can run the application using:
1go run main.goVisit 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: