Reimplementing the Gin Web Framework from Scratch in Go

Leapcell: The Best of Serverless Web Hosting Implementing an HTTP Router Similar to Gin Using the net Package in Go 1. Introduction In modern web development, an efficient and flexible routing system is one of the core components of building web applications. The Go programming language is highly favored in the field of web development due to its high performance, simplicity, and powerful standard library. The net/http package in Go is the implementation of an HTTP server in the standard library. Although it is powerful, it is relatively low-level. If you want to handle routing like in lightweight web frameworks such as Gin, we can implement a simplified router ourselves. This article will provide a detailed introduction on how to use the net package in Go to implement an HTTP server similar to Gin. At the same time, it will delve into HTTP-related knowledge, common routing implementation methods, and how to implement middleware based on this. 2. Review of HTTP Basics 2.1 HTTP Requests and Responses HTTP (Hypertext Transfer Protocol) is a protocol used for transferring hypertext, and it is the foundation of web applications. An HTTP request usually consists of the following parts: Request Line: It includes the request method (such as GET, POST, PUT, DELETE, etc.), the requested URI (Uniform Resource Identifier), and the HTTP version. Request Headers: They contain additional information about the request, such as User-Agent, Content-Type, etc. Request Body: It contains the data of the request, which is usually used in POST or PUT requests. An HTTP response also consists of several parts: Status Line: It includes the HTTP version, the status code (for example, 200 indicates success, 404 indicates not found, 500 indicates an internal server error, etc.), and the status message. Response Headers: They contain additional information about the response, such as Content-Type, Content-Length, etc. Response Body: It contains the data of the response, such as an HTML page, JSON data, etc. 2.2 HTTP Methods Common HTTP methods include: GET: It is used to retrieve resources. POST: It is used to submit data to the server, usually for creating new resources. PUT: It is used to update resources. DELETE: It is used to delete resources. Different HTTP methods have different semantics, and requests need to be handled according to different methods when designing a routing system. 3. Common Routing Implementation Methods 3.1 Static Routing Static routing is the simplest routing method, which maps a fixed URL path to a specific handler function. For example, mapping the /about path to the handler function that displays the about page. 3.2 Dynamic Routing Dynamic routing allows parameters to be included in the URL path, and these parameters can be retrieved in the handler function. For example, :id in /users/:id is a parameter that can be used to retrieve information about a specific user. 3.3 Regular Expression Routing Regular expression routing allows the use of regular expressions to match URL paths. This method is more flexible and can handle complex routing rules. For example, using regular expressions to match all URL paths that end with .html. 4. Design Ideas To implement the routing function, we need to: Parse the HTTP request path and method. Store the handler functions for different paths and methods. Parse dynamic routing parameters. Handle 404 errors. We will use a map structure to store the routing rules. Each path corresponds to different HTTP methods, which can efficiently match requests. Specifically, we will use a nested map. The key of the outer map is the HTTP method, the key of the inner map is the URL path, and the value is the corresponding handler function. 5. Code Implementation package main import ( "fmt" "net/http" "strings" ) // Router struct is used to store routing rules type Router struct { routes map[string]map[string]http.HandlerFunc } // NewRouter creates a new router instance func NewRouter() *Router { return &Router{ routes: make(map[string]map[string]http.HandlerFunc), } } // Handle method is used to register routes func (r *Router) Handle(method, path string, handler http.HandlerFunc) { if _, ok := r.routes[method];!ok { r.routes[method] = make(map[string]http.HandlerFunc) } r.routes[method][path] = handler } // ServeHTTP method is used to parse HTTP requests and call the corresponding handler functions func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) { methodRoutes, ok := r.routes[req.Method] if!ok { http.NotFound(w, req) return } handler, ok := methodRoutes[req.URL.Path] if!ok { // Handle dynamic routing for route, h := range methodRoutes { if params := matchDynamicRoute(route, req.URL.Path); pa

Mar 30, 2025 - 12:09
 0
Reimplementing the Gin Web Framework from Scratch in Go

Image description

Leapcell: The Best of Serverless Web Hosting

Implementing an HTTP Router Similar to Gin Using the net Package in Go

1. Introduction

In modern web development, an efficient and flexible routing system is one of the core components of building web applications. The Go programming language is highly favored in the field of web development due to its high performance, simplicity, and powerful standard library. The net/http package in Go is the implementation of an HTTP server in the standard library. Although it is powerful, it is relatively low-level. If you want to handle routing like in lightweight web frameworks such as Gin, we can implement a simplified router ourselves. This article will provide a detailed introduction on how to use the net package in Go to implement an HTTP server similar to Gin. At the same time, it will delve into HTTP-related knowledge, common routing implementation methods, and how to implement middleware based on this.

2. Review of HTTP Basics

2.1 HTTP Requests and Responses

HTTP (Hypertext Transfer Protocol) is a protocol used for transferring hypertext, and it is the foundation of web applications. An HTTP request usually consists of the following parts:

  • Request Line: It includes the request method (such as GET, POST, PUT, DELETE, etc.), the requested URI (Uniform Resource Identifier), and the HTTP version.
  • Request Headers: They contain additional information about the request, such as User-Agent, Content-Type, etc.
  • Request Body: It contains the data of the request, which is usually used in POST or PUT requests.

An HTTP response also consists of several parts:

  • Status Line: It includes the HTTP version, the status code (for example, 200 indicates success, 404 indicates not found, 500 indicates an internal server error, etc.), and the status message.
  • Response Headers: They contain additional information about the response, such as Content-Type, Content-Length, etc.
  • Response Body: It contains the data of the response, such as an HTML page, JSON data, etc.

2.2 HTTP Methods

Common HTTP methods include:

  • GET: It is used to retrieve resources.
  • POST: It is used to submit data to the server, usually for creating new resources.
  • PUT: It is used to update resources.
  • DELETE: It is used to delete resources.

Different HTTP methods have different semantics, and requests need to be handled according to different methods when designing a routing system.

3. Common Routing Implementation Methods

3.1 Static Routing

Static routing is the simplest routing method, which maps a fixed URL path to a specific handler function. For example, mapping the /about path to the handler function that displays the about page.

3.2 Dynamic Routing

Dynamic routing allows parameters to be included in the URL path, and these parameters can be retrieved in the handler function. For example, :id in /users/:id is a parameter that can be used to retrieve information about a specific user.

3.3 Regular Expression Routing

Regular expression routing allows the use of regular expressions to match URL paths. This method is more flexible and can handle complex routing rules. For example, using regular expressions to match all URL paths that end with .html.

4. Design Ideas

To implement the routing function, we need to:

  • Parse the HTTP request path and method.
  • Store the handler functions for different paths and methods.
  • Parse dynamic routing parameters.
  • Handle 404 errors.

We will use a map structure to store the routing rules. Each path corresponds to different HTTP methods, which can efficiently match requests. Specifically, we will use a nested map. The key of the outer map is the HTTP method, the key of the inner map is the URL path, and the value is the corresponding handler function.

5. Code Implementation

package main

import (
    "fmt"
    "net/http"
    "strings"
)

// Router struct is used to store routing rules
type Router struct {
    routes map[string]map[string]http.HandlerFunc
}

// NewRouter creates a new router instance
func NewRouter() *Router {
    return &Router{
        routes: make(map[string]map[string]http.HandlerFunc),
    }
}

// Handle method is used to register routes
func (r *Router) Handle(method, path string, handler http.HandlerFunc) {
    if _, ok := r.routes[method];!ok {
        r.routes[method] = make(map[string]http.HandlerFunc)
    }
    r.routes[method][path] = handler
}

// ServeHTTP method is used to parse HTTP requests and call the corresponding handler functions
func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    methodRoutes, ok := r.routes[req.Method]
    if!ok {
        http.NotFound(w, req)
        return
    }

    handler, ok := methodRoutes[req.URL.Path]
    if!ok {
        // Handle dynamic routing
        for route, h := range methodRoutes {
            if params := matchDynamicRoute(route, req.URL.Path); params != nil {
                req.URL.Query().Set("params", strings.Join(params, ","))
                h(w, req)
                return
            }
        }
        http.NotFound(w, req)
        return
    }

    handler(w, req)
}

// matchDynamicRoute function is used to match dynamic routes
func matchDynamicRoute(route, path string) []string {
    routeParts := strings.Split(route, "/")
    pathParts := strings.Split(path, "/")

    if len(routeParts) != len(pathParts) {
        return nil
    }

    var params []string
    for i, part := range routeParts {
        if strings.HasPrefix(part, ":") {
            params = append(params, pathParts[i])
        } else if part != pathParts[i] {
            return nil
        }
    }

    return params
}

func main() {
    router := NewRouter()

    // Register static route
    router.Handle("GET", "/", func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "Hello, world!")
    })

    // Register dynamic route
    router.Handle("GET", "/hello/:name", func(w http.ResponseWriter, req *http.Request) {
        params := req.URL.Query().Get("params")
        name := strings.Split(params, ",")[0]
        fmt.Fprintf(w, "Hello, %s!", name)
    })

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

Code Explanation

  • Router struct: It is used to store routing rules and uses a nested map to store the handler functions corresponding to different HTTP methods and URL paths.
  • NewRouter function: It is used to create a new router instance.
  • Handle method: It is used to register routes, storing the HTTP method, URL path, and handler function in the Router struct.
  • ServeHTTP method: It is used to parse HTTP requests. First, it checks whether the requested HTTP method exists, and then it checks whether the URL path matches. If there is no matching static route, it will try to match the dynamic route.
  • matchDynamicRoute function: It is used to match dynamic routes and check whether the parameters in the URL path match.

6. Running and Testing

Save the code as main.go and run it:

go run main.go

Then visit:

  • http://localhost:8080/ returns "Hello, world!"
  • http://localhost:8080/hello/Go returns "Hello, Go!"
  • Visiting other paths will return 404 Not Found

7. Implementing Middleware

Middleware is a function that is executed before or after handling a request. It can be used for logging, authentication, error handling, etc. Implementing middleware in our router is very simple. We just need to define a function type that receives an http.HandlerFunc and returns a new http.HandlerFunc.

// Middleware is a middleware function type
type Middleware func(http.HandlerFunc) http.HandlerFunc

// Logger is a simple logging middleware
func Logger(next http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, req *http.Request) {
        fmt.Printf("Received %s request for %s\n", req.Method, req.URL.Path)
        next(w, req)
    }
}

func main() {
    router := NewRouter()

    // Register static route and apply middleware
    router.Handle("GET", "/", Logger(func(w http.ResponseWriter, req *http.Request) {
        fmt.Fprintf(w, "Hello, world!")
    }))

    // Register dynamic route and apply middleware
    router.Handle("GET", "/hello/:name", Logger(func(w http.ResponseWriter, req *http.Request) {
        params := req.URL.Query().Get("params")
        name := strings.Split(params, ",")[0]
        fmt.Fprintf(w, "Hello, %s!", name)
    }))

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

Code Explanation

  • Middleware type: It defines a middleware function type that receives an http.HandlerFunc and returns a new http.HandlerFunc.
  • Logger function: It is a simple logging middleware that will print the request method and URL path before handling the request.
  • In the main function, we apply the Logger middleware to the handler function of each route.

8. Summary

This tutorial shows how to use the net/http package in the Go language to implement a simple web router. At the same time, it introduces HTTP-related knowledge, common routing implementation methods, and how to implement middleware based on this. You can expand the functionality on this basis, such as:

  • Supporting more HTTP methods, such as PUT, DELETE, etc.
  • Adding more complex middleware functions, such as authentication, rate limiting, etc.
  • Implementing more advanced parameter parsing, such as regular expression routing.

In this way, we can not only master the underlying working principles of HTTP servers but also customize our own web frameworks and enjoy the high performance and flexibility of the Go language.

Leapcell: The Best of Serverless Web Hosting

Finally, I would like to recommend the best platform for deploying Go services: Leapcell

Image description