Back to Home

Go Middleware - Part 4

  • February 24, 2019 |
  • 4 min read

In this fourth part of the Go middleware tutorial series, we'll discuss passing custom state along the request chain. This provides the flexibility to pass data from one middleware handler to another one somewhere else in the chain.

Part 4 - Custom State Middleware Example

We want to be able to pass custom data from one middleware function to another, but without breaking encapsulation or modularity. We also want to be very explicit and type-safe about the data that is being passed so it is easy to maintain.

The code for this tutorial can be found here: https://github.com/benjohns1/go-middleware-example/blob/master/customstate/main.go

Step 1 - Simple Server

We'll create a simple server that uses the recursive middleware approach from part 2 with a few modifications:

main.go:

This code should be familiar but for a couple slight differences:

  1. In main() we're passing in a function literal (i.e. anonymous or lambda function) to the businessLogic function that randomly returns true or false. This is just for brevity and is not all that relevant.
  2. In businessLogic() we're explicitly setting a success or failure HTTP status code, depending on whether the decider function returns true or false.

Wouldn't it be nice to be able to log that status code in our logger middleware? But take a look at the http.ResponseWriter interface; there's no way to retrieve its value after its been set!

Step 2 - Implement http.ResponseWriter

Here's an example of where Go's interfaces really shine. We can define a new struct type to contain the data that we need to keep track of, and as long as we implement the http.ResponseWriter interface, our struct can be passed through any middleware handler without issue (even through handlers that don't know or care about our custom data):

main.go (partial):

The logResponseWriter type stores the original http.ResponseWriter sent to the logger function and passes through all of the required interface function calls. It extends the standard functionality by storing the status code in an easily-accessible parameter when the WriteHeader interface method is called.

The logger function creates our custom struct and passes it into the next middleware handler in the chain (which is fine because the struct implements the http.ResponseWriter interface). It then accesses the status parameter for logging after the subsequent handlers have run. Notice that we didn't touch a single line of our business logic to implement this, because we leveraged the http.ResponseWriter interface's existing WriteHeader function.

Step 3 - Custom Data

You might notice that this only works well for the status code in particular (or for any data that is passed to an existing http.ResponseWriter interface method). So how do we pass additional custom data?

In order to do this, both middleware handlers need to be aware of some sort of interface for getting/setting the data. In this example we'll modify our business logic so that it is aware of the logResponseWriter struct, but you can imagine decoupling this further using additional interfaces.

main.go (partial):

We've added a new field "extra" to store our custom string data. The logger function logs this string after the request is complete.

The businessLogic function does a bit of work to check if the custom interface is implemented. It uses a type assertion to check whether a logResponseWriter was passed in as the http.ResponseWriter, and if so it has type-safe access to the "extra" field.

You can extend this approach to pass any type of custom data up and down the middleware chain. This has a lot of benefit over using, say, a context to store values on the response writer object, because it clearly defines type-safe fields in an explicit struct, making the code much more readable and less error-prone. However, keep in mind that this approach is still more complicated than simply passing parameters into functions, so only use it when needed.

In the final part 5 of this series, we'll look at a slightly more realistic and structured example of a JSON API that uses the middleware approaches we've discussed.

Final Server Code

main.go:

Go Middleware - Part 3

Go Middleware - Part 5

Related Posts

The essential design concepts I use when developing an evolvable, distributed system.

Read More

How can we continuously integrate small changes while practicing acceptance test-driven development?

Read More

TDD and Testing Behavior

January 24, 2024

The importance of testing behavior when using test-driven development

Read More

When is it appropriate to use centralized orchestration versus event-driven choreography?

Read More

When defining a business problem and planning its solution, keep the two conversations separate...

Read More

Modern message brokers provide many important benefits to a distributed system...

Read More

Printable cheat sheets to help remember some of Uncle Bob's valuable contributions to the industry

Read More

Why Terraform?

December 25, 2019

Terraform leads the way in the infrastructure-as-code world...

Read More

I was looking for a quick and easy way to put together a personal static site and...

Read More

A few weeks ago, I decided to try Svelte's Sapper framework to handle the front-end of a simple app...

Read More

After years of consulting, I find myself continually coming back to three basic principles of system design...

Read More

In this fifth and final part of the Go middleware tutorial series, we'll use what we've learned to create a more structured API example...

Read More

Go Middleware - Part 3

February 15, 2019

In this third part of the Go middleware tutorial series, we'll quickly look at a common variant on the recursive middleware implementation from part 2.

Read More

Go Middleware - Part 2

February 9, 2019

In this second part of the Go middleware tutorial series, we'll cover a recursive approach that provides a couple benefits beyond the simple loop chain example from part 1.

Read More

Go Middleware - Part 1

February 6, 2019

This is the first in a series of simple tutorials explaining the usage of HTTP middleware in Go.

Read More

How do we manage the architectural complexity that inevitably arises from using cloud services?

Read More

This Old Blog

January 20, 2019

I've decided to resurrect this old blog to publish some nuggets about software architecture and development, and perhaps...

Read More

Drupal 6 Theme Info Error

September 14, 2011

Recently one of my client sites had an issue where the custom theme info was corrupted...

Read More

Here's a slight modification to the handy Google Bookmarks Bookmarklet...

Read More

While building a Drupal site for one of my clients, I was having a heck of a time integrating...

Read More