Middlewares In Golang
About & Context & Examples &
In the simplest terms: Middlewares are the intermediators or a layer of abstraction between the sender and the receiver of any request. According to IBM’s definition [^1] [^1]: https://www.ibm.com/in-en/topics/middleware
Middleware is software that enables one or more kinds of communication or connectivity between two or more applications or application components in a distributed network.
Middlewares are functions that take a first order function and returns another first order function of the same type. For the math geeks, middlewares can be called an identity morphism. That is, for each object of a category, the domain and co-domain (or range) of a morphism must be the same.
For example: In real life, an interpreter who translates source text/speech from one language to another is a middle-man. In software world, we’d call it as a middleware.
HTTP Middlewares:
HTTP middlewares are the middlewares where the type of function that we deal with is a httpHandler or an abstraction for a request.
User --Req--> Endpoint --Req--> Middleware --Req--> Business Logic |
---|
Sample Request Lifecycle With Middleware |
With golang, we get a minimal yet very powerful builtin http
library.
We had talked about middleware accepting a certain type of input and returning
the same type of object. For Http middlewares, http.Handler
is the type that
we deal with. http.Handler is a simple interface that has only one method
ServeHttp
that must be present with the object to be called a http.Handler
type.
1
2
3
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
Interface definition of http.Handler
Pre-Requisite For Examples
Let’s define a sample golang application with an endpoint to be used in the further examples.
1
A. Relay Middleware
Let’s get started defining a simple middleware that does nothing. It relays the request got to it without modifying any attributes of the request or doing any processing on the request params.
1
2
3
4
5
func RelayMiddleware(originalHandler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
originalHandler.ServeHTTP(w, r)
})
}
Relay Middleware Example
The Usage of the relay-middleware is very simple. You can wrap the endpoint handler function with the Call to the RelayMiddleware.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main
import "net/http"
func RelayMiddleware(originalHandler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
originalHandler.ServeHTTP(w, r)
})
}
func greet(w http.ResponseWriter, r *http.Request) {
_, _ = w.Write([]byte("hello world")) // ignoring the error
}
func main() {
greetHandler := http.HandlerFunc(greet)
http.Handle("/greet", greetHandler)
http.Handle("/greet-with-middleware", RelayMiddleware(greetHandler))
panic(http.ListenAndServe(":8069", nil))
}
B. Logging Middleware
These come under the category of middlewares that are stateless with respect to the request-response model. That is, they do not modify either incoming request or the outgoing response. These might just sniff in some information or store the meta-data around it
1
2
3
4
5
6
7
8
9
10
11
func LoggingMiddleware(originalHandler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
inTime := time.Now()
log.Printf("got a new request @%v\n", inTime)
originalHandler.ServeHTTP(w, r)
outTime := time.Now()
log.Printf("request got @%v was served by %v\n", inTime, outTime)
})
}
The usage is similar as that of the Relay Middleware
Endpoints vs Middleware
Endpoint | Middleware |
---|---|
Can be called by users | Cannot be called directly by users |
Used by end-users | Used by developers to intercept and modify requests |
Represents a specific URL or URI | Operates on requests and responses |
Handles incoming requests | Sits between the client and the server, modifying requests |
Processes client requests | Performs pre- and post-processing tasks on requests and responses |
Returns a response to the client | Can be chained together for request processing |
Part of the server application | Part of the request processing pipeline |
Executes the main logic of an application | Can be reusable across multiple endpoints |
Typically represented by a URL route | Can be applied globally or selectively to specific routes |
Benefits of using middleware
- core business logic is separated from other things.
- grouping more than one endpoint