hutil

package module
v3.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 4, 2023 License: MIT Imports: 8 Imported by: 0

README

hutil

Introduction

hutil is a set of helpers to work with net/http. It's limited by design, based on what I need the most in my projects.

Middleware

The most useful type is hutil.MiddlewareStack. It is used to stack middlewares and wrap a http.Handler. Look at the documentation here to see how to use it.

We also provide a logging middleware. Look at the documentation here to see how to use it.

Logging

You can get a logging middlware this way:

logFn := func(req *http.Request, statusCode int, responseSize int, elapsed time.Duration) {
    log.Printf("[%s] %s -> %d: %s", http.StatusText(statusCode), req.URL.Path, responseSize, elapsed)
}
m := hutil.NewLoggingMiddleware(logFn)

Shift path

This function is useful to build complex routing based on the URL path using only the standard library HTTP package.

It's more involved than using something like gorilla/mux but not that much for basic things.

Suppose you have the following routes:

/user
/user/feed/<id>
/user/profile/<id>
/search/inventory
/search/company

You could build your routing like this:

type userHandler struct {
}

func (h *userHandler) handle(w http.ResponseWriter, req *http.Request) {
	var head string
	head, req.URL.Path = hutil.ShiftPath(req.URL.Path)
	switch head {
	case "profile":
		h.profile(w, req)
	case "feed":
		h.feed(w, req)
	}
}

func (h *userHandler) profile(w http.ResponseWriter, req *http.Request) {
	profileID := req.URL.Path
	fmt.Printf("profile, profile id: %s\n", profileID)
}
func (h *userHandler) feed(w http.ResponseWriter, req *http.Request) {
	profileID := req.URL.Path
	fmt.Printf("feed, profile id: %s\n", profileID)
}

type searchHandler struct {
}

func (h *searchHandler) handle(w http.ResponseWriter, req *http.Request) {
	var head string
	head, req.URL.Path = hutil.ShiftPath(req.URL.Path)
	switch head {
	case "inventory":
		h.inventorySearch(w, req)
	case "company":
		h.companySearch(w, req)
	}
}

func (h *searchHandler) inventorySearch(w http.ResponseWriter, req *http.Request) {
	fmt.Println("inventory search")
}
func (h *searchHandler) companySearch(w http.ResponseWriter, req *http.Request) {
	fmt.Println("company search")
}

type router struct {
	uh *userHandler
	sh *searchHandler
}

func (r *router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	var head string
	head, req.URL.Path = hutil.ShiftPath(req.URL.Path)
	switch head {
	case "user":
		r.uh.handle(w, req)
	case "search":
		r.sh.handle(w, req)
	}
}

func main() {
	r := &router{
		uh: new(userHandler),
		sh: new(searchHandler),
	}
	http.ListenAndServe(":3204", r)
}

Documentation

Overview

Package hutil contains helper functions and types to write HTTP handlers with Go.

It is opiononated and minimalist: * `ShiftPath` is used to build routing * `Chain` is used to build a chain of middlewares * `NewLoggingMiddleware` is used to create a middleware which logs the requests. It is compatible with `Chain`

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func ShiftPath

func ShiftPath(p string) (head string, tail string)

ShiftPath splits the path into the first component and the rest of the path. The returned head will never have a slash in it, if the path has no tail head will be empty. The tail will never have a trailing slash.

Example
var urlPath = "/search/myhome/inventory"

var head string
head, urlPath = ShiftPath(urlPath)
switch head {
case "search":
	// handler.search(w, req)
case "feed":
	// handler.feed(w, req)
default:
	// handler.home(w, req)
}

fmt.Printf("head: %s\n", head)
fmt.Printf("url path: %s\n", urlPath)
Output:

head: search
url path: /myhome/inventory

Types

type Middleware

type Middleware func(next http.Handler) http.Handler

Middleware is a function taking a HTTP handler and returning a new handler that wraps it. For example, you could have a middleware that injects a session in the request's context:

// Create a middleware that injects a session in the request context
func sessionMiddleware(next http.Handler) Middleware {
	return func(w http.ResponseWriter, req *http.Request) {
		session := fetchSession(req)
		ctx := contet.WithValue(req.Context(), "session", session)
		req = req.WithContext(ctx)
		next.ServeHTTP(w, req)
	}
}

Note that the you _have_ to call `next.ServeHTTP` otherwise requests stop in this handler.

Next you wrap your own handler with this middleware:

myHandler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
	...
)
handler := sessionMiddleware(myHandler)

Now whenever your handler is called it can easily get the session

func NewLoggingMiddleware

func NewLoggingMiddleware(logger *zap.Logger) Middleware

NewLoggingMiddleware returns a middleware that logs the requests and the results of the request as processed by the next middleware (or handler) in the chain.

The middleware doesn't log by itself, instead the logFn you pass should do that. THe middleware merely gives you the data to log.

Note about the execution time. Depending on where in the chain you place the logging handler, you will get different execution times. Take the following chain example: Middleware1 -> Logging -> FinalHandler (your business-logic handler, or muxer for example)

The logging middleware, when called, will start counting AFTER the two handlers before in the chain, meaning it will only measure the execution time of the final handler. This isn't always what you want, because if you have a middleware in the chain that can take some measurable time, you probably want to count it too in the execution time. Thus, make sure you place the logging handler at the correct place in the chain.

type MiddlewareStack

type MiddlewareStack []Middleware

MiddlewareStack as its name imply, stacks middlewares. You stack a middleware by calling `Use`. Keep in mind that the first middleware you stack will be the middleware that wraps everything else, this means if you have something like a logging middleware it should be stacked first.

Here is an example:

var stack MiddlewareStack
stack.Use(newLoggingMiddleware())
stack.Use(newRateLimitMiddleware())
stack.Use(newSessionMiddleware())
stack.Use(newUserMiddleware())

This will create the following chain of middleware calls: logging -> ratelimit -> session -> user

Example
createMiddleware := func(buf *bytes.Buffer, s string) func(next http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
			buf.WriteString(s)
			next.ServeHTTP(w, req)
		})
	}
}

var (
	s   MiddlewareStack
	buf bytes.Buffer
)

var (
	m1 = createMiddleware(&buf, "m1")
	m2 = createMiddleware(&buf, "m2")
	m3 = createMiddleware(&buf, "m3")
)

s.Use(m1)
s.Use(m2)
s.Use(m3)

ts := httptest.NewServer(s.Handler(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
	fmt.Fprint(w, "foobar")
})))
defer ts.Close()

res, _ := http.Get(ts.URL)

data, _ := io.ReadAll(res.Body)
defer res.Body.Close()

fmt.Println(string(data))
fmt.Println(buf.String())
Output:

foobar
m1m2m3

func (MiddlewareStack) Handler

func (s MiddlewareStack) Handler(h http.Handler) http.Handler

Handler wraps the provided final handler with all the middleware stacked and returns a http.Handler instance.

Here is an example:

myHandler := http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
	...
)

var stack MiddlewareStack
stack.Use(newLoggingMiddleware())
stack.Use(newRateLimitMiddleware())
handler := stack.Handler(myHandler)

This will create the following chain of calls: logging -> ratelimit -> myHandler

func (*MiddlewareStack) Use

func (s *MiddlewareStack) Use(h Middleware)

Use stacks a middleware.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL