Productivity is the result of a commitment to excellence, intelligent planning, and focused effort.

HTTP/HTTPS server with Go 1.22, with nested routing, middleware and more

Cover Image for HTTP/HTTPS server with Go 1.22, with nested routing, middleware and more
George Crisan
George Crisan
Posted on:

Intro

In my effort to learn Go 1.22 I've tried to see which of my skills learned in express/node.js can be transferred to GO.

This project includes a TLS HTTPS server option or a basic HTTP server option, using environment variables. See details in the README.md https://github.com/GeorgeCrisan/go-web-server/blob/master/README.md file.

The full project source code can be found in this public GIT repository if you want to get your hands dirty. https://github.com/GeorgeCrisan/go-web-server

The server is set with the latest version of Go which allows you to define the CRUD verbs on the endpoint.

	// Set the route functionality
	router.HandleFunc("GET /user/{userId}/token/{token}", userHandler)

You can see how to set nested or versioned routing.

	// Allows to point certain routes to other routers
	proxyRouter := http.NewServeMux()
	proxyRouter.Handle("/api/v1/", http.StripPrefix("/api/v1", router))

Middleware functions

The example below shows how to set middleware functions and how to chain them to prevent ugly function invocation nesting.

// Logger middleware
func RequestLogger(next http.Handler) http.HandlerFunc {
	return func(res http.ResponseWriter, req *http.Request) {
		log.Printf("Log info method: %s, path : %s, info: %s ", req.Method, req.URL.Path, req.UserAgent())

		next.ServeHTTP(res, req)
	}
}

// Mock dumb validator as middleware proof of concept
func RequestValidator(next http.Handler) http.HandlerFunc {
	return func(res http.ResponseWriter, req *http.Request) {

		token := "1234"

		if !strings.Contains(req.URL.Path, token) {
			log.Printf("Token is required !!!")
			http.Error(res, "Unauthorised so fuck off", http.StatusUnauthorized)
			// Early return
			return
		}

		log.Printf("Security Token found, move on")

		next.ServeHTTP(res, req)
	}
}

This is an example of a middleware chaining function.


// Types
// type Middleware func(next http.Handler) http.HandlerFunc
type Middleware func(http.Handler) http.HandlerFunc

// Chain middleware util function
func ChainMiddleware(middlwareList ...Middleware) Middleware {
	return func(next http.Handler) http.HandlerFunc {
		// iterate from all middleware, invoke each middleware and pass object to next middleware
		for i := len(middlwareList) - 1; i >= 0; i-- {
			next = middlwareList[i](next)
		}

		return next.ServeHTTP
	}
}

and this is how to glue the middleware together to the route handler.

	// Define the middleware we want to use for this router
	midlewareChained := ChainMiddleware(
		RequestLogger,
		RequestValidator,
	)

	// Set the route functionality
	router.HandleFunc("GET /user/{userId}/token/{token}", userHandler)

    // Chain middleware to the router
	server := http.Server{
		Addr:    httpServer.address,
		Handler: midlewareChained(router),
	}

This was a very interesting experience for me. Go promotes simplicity and clean code and I am loving it. If you want to discuss this project or if you have any questions email me at georgerdp@gmail.com or find me on linked in https://www.linkedin.com/in/george-crisan26/.