Meta trips

Tour de wrap middlewares


In the last post I wrote about the motivation and the design of the go-on/wrap middleware micro framework.

Now I will do a quick walk through some selected middlewares.

Since anybody using middleware might not want to be victim of lots of API changes, I made two different repositories.

The blessed middleware that is battle tested and that should not change its API, is located at github.com/go-on/wrap-contrib.

For new contributions and middleware that has not been settled yet, there is github.com/go-on/wrap-contrib-testing.

They are both far from complete, but have interesting solutions here and there that give you ideas of what is possible and I eagerly wait for your contributions :-)

In this post, I will show you some middleware of the blessed repository.

IfNoneMatch, IfMatch and ETag

This middlewares are related to each other. ETag creates an etag as md5 hash of the content that is written further down the stack. But only if the status is not set or 200. In every other case the request will be served as if ETag was not there.

It is most useful in the combination with IfNoneMatch and IfMatch.

The If-None-Match header is set by the browser, if it already had requested the ressource and got an etag for it. It then first sends a test request to the server with the etag set in the If-None-Match header, which means, that the ressource should only be returned, if the etag did change. Otherwise the status code 304 (Not modified) should be sent and the browser shows the cached content.

While it means that the server still has to do something in order to get (or calculate in our case) the etag, it still results in a faster experience, because the response body does not have to be send over the wire again.

Here is some example code:

stack := wrap.New(
	IfNoneMatch,
	ETag,
	String("Hello World"),
)

A bit more involved is the If-Match header. This header is not send by the browser as part of caching, but more for AJAX calls and other REST clients.

It is a way for the client to say “hey, just do this update / removal, if the ressource did not change in the meantime”.

Accordingly we need 2 http.Handlers: one to create the etag and the second for the action that should be done if the etag matches.

It looks like this:

getETag := wrap.New(
	ETag,
	String("Hello World"),
)

stack := wrap.New(
	IfMatch(getETag),
	String("Hello World matched"),
)

// ... serve the stack

Please keep in mind, that both, IfNoneMatch and IfMatch do not affect the request if the according header has not been set by the client.

Guard and First

Guard and First are quite similar. Guard interrupts the stack run, if its handler did something with the ResponseWriter:

stack := wrap.New(
	Guard(String("forbidden")),
	String("hu?"),
)
	

First does the same, but for a number of handlers that are all tried until the first one returns something. It can be used as some kind of fallback stack (e.g. fallback for routing).

stack := wrap.New(
	First(
		String("won!"),
		String("no chance!"),
	),
)

Matcher

A good example of how composition works in Go can be seen with Matcher.

A Matcher is just something with a Match(*http.Request) bool method that checks the request and returns if it matches.

There are some predefined matchers, but you could easily develop your own. Here MatchPath and MatchMethod are shown:

type Matcher interface {
	Match(*http.Request) bool
}

type MatchFunc func(*http.Request) bool

func (mf MatchFunc) Match(req *http.Request) bool {
	return mf(req)
}

type MatchMethod string

func (mh MatchMethod) Match(r *http.Request) bool {
	return r.Method == string(mh)
}

type MatchPath string

func (mh MatchPath) Match(r *http.Request) bool {
	return r.URL.Path == string(mh)
}

Now, since we are on Meta trips, lets do a meta matcher, that combines different matchers and returns one:

func And(ms ...Matcher) Matcher {
	return MatchFunc(
		func(req *http.Request) bool {
			for _, m := range ms {
				if !m.Match(req) {
					return false
				}
			}
			return true
		},
	)
}

Now the matchHandler composes a Matcher and a http.Handler and serves the http.Handler if the matcher matches the request:

type matchHandler struct {
	Matcher
	http.Handler
}


func (mh *matchHandler) Wrap(next http.Handler) ( http.Handler) {
	return http.HandlerFunc(func (w http.ResponseWriter, r *http.Request){
		if mh.Match(req) {
			mh.Handler.ServeHTTP(wr, req)
			return
		}
		next.ServeHTTP(wr, req)
	})
}

Now we could easily make some helper functions that gives us shortcuts for all the REST stuff.

func GETHandler(path string, handler http.Handler) *matchHandler {
	return &matchHandler{And(MatchPath(path), MatchMethod("GET")), handler}
}

func POSTHandler(path string, handler http.Handler) *matchHandler {
	return &matchHandler{And(MatchPath(path), MatchMethod("POST")), handler}
}

// and so on...

Now we have a poor man’s router that seamlessly integrates with the handler stack:

stack := wrap.New(
	GETHandler("/company", String("get company")),
	POSTHandler("/blubb", String("post blubb")),
	PATCHHandler("/company", String("patch company")),
)

But lets not forget, there is Map which maps matchers to handlers:

stack := wrap.New(
	Map(
		MatchQuery("name", "peter"), String("peter"),
		
		And(MatchMethod("POST"), MatchPath("/hi")), String("ho"),
		
		MatchPathRegex(regexp.MustCompile(`\/person\/customer\/[0-9]+`)), Map(
			MatchMethod("GET"),
			String("person customers"),
		),

		MatchPath("/person"), &MethodHandler{
			GET:     String("get person"),
			POST:    String("post person"),
			PATCH:   String("patch person"),
		},
	),
)


You can see how we can freely mix and match our handlers, routes and stacks.

Why is it only a poor man’s router?

While you can save your strings as routes and use them later (even prefill the placeholders with helper functions, and get the parameters out), I want a real router to allow me to create web apps within packages, that have their route paths relative to a mountpoint that can be set up from outside the package. I just want to mount them in the outer package beneath some path and have the routes to know their final path.

That is what another library (go-on/router) is for and this is left for another post.

Summary

We touched only the tip of the iceberg, so look into the wraps repositories for yourself. I hope, you got an impression, how powerful the Go interfaces are and how nicely they integrate with the wrap library.

Stay tuned!

Links

comments powered by Disqus