muxie

package module
v1.0.3 Latest Latest
Warning

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

Go to latest
Published: Oct 18, 2018 License: MIT Imports: 10 Imported by: 42

README

Muxie

:steam_locomotive::train::train::train::train::train:
Fast trie implementation designed from scratch specifically for HTTP
A small and light router for creating sturdy backend Go applications

The little router that could. Built with ❤︎ by Gerasimos Maropoulos

Benchmark chart between muxie, httprouter, gin, gorilla mux, echo, vestigo and chi

Last updated on October 17, 2018. Click here to read more details.

Features

  • trie based: performance and useness are first class citizens, Muxie is based on the prefix tree data structure, designed from scratch and built for HTTP, and it is among the fastest outhere, if not the fastest one
  • grouping: group common routes based on their path prefixes
  • no external dependencies: weighing 30kb, Muxie is a tiny little library without external dependencies
  • closest wildcard resolution and prefix-based custom 404: wildcards, named parameters and static paths can all live and play together nice and fast in the same path prefix or suffix(!)
  • small api: with only 3 main methods for HTTP there's not much to learn
  • compatibility: built to be 100% compatible with the net/http standard package

Installation

The only requirement is the Go Programming Language

$ go get -u github.com/kataras/muxie

Example

package main

import (
    "fmt"
    "net/http"

    "github.com/rs/cors"

    "github.com/kataras/muxie"
)

func main() {
    mux := muxie.NewMux()
    mux.PathCorrection = true

    // _examples/6_middleware
    mux.Use(cors.Default().Handler)

    mux.HandleFunc("/", indexHandler)
    // Root wildcards, can be used for site-level custom not founds(404).
    mux.HandleFunc("/*path", notFoundHandler)

    // Grouping.
    profile := mux.Of("/profile")
    profile.HandleFunc("/:name", profileHandler)
    profile.HandleFunc("/:name/photos", profilePhotosHandler)
    // Wildcards can be used for prefix-level custom not found handler as well,
    // order does not matter.
    profile.HandleFunc("/*path", profileNotFoundHandler)

    // Dynamic paths with named parameters and wildcards or all together!
    mux.HandleFunc("/uploads/*file", listUploadsHandler)

    mux.HandleFunc("/uploads/:uploader", func(w http.ResponseWriter, r *http.Request) {
        uploader := muxie.GetParam(w, "uploader")
        fmt.Fprintf(w, "Hello Uploader: '%s'", uploader)
    })

    mux.HandleFunc("/uploads/info/*file", func(w http.ResponseWriter, r *http.Request) {
        file := muxie.GetParam(w, "file")
        fmt.Fprintf(w, "File info of: '%s'", file)
    })

    mux.HandleFunc("/uploads/totalsize", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Uploads total size is 4048")
    })

    fmt.Println("Server started at :8080")
    http.ListenAndServe(":8080", mux)
}

func notFoundHandler(w http.ResponseWriter, r *http.Request) {
    requestPath := muxie.GetParam(w, "path")
    // or r.URL.Path, we are in the root so it doesn't really matter.

    fmt.Fprintf(w, "Global Site Page of: '%s' not found", requestPath)
}

func profileNotFoundHandler(w http.ResponseWriter, r *http.Request) {
    requestSubPath := muxie.GetParam(w, "path")
    // requestSubPath = everyhing else after "http://localhost:8080/profile/..." 
    // but not /profile/:name or /profile/:name/photos because those will
    // be handled by the above route handlers we registered previously.

    fmt.Fprintf(w, "Profile Page of: '%s' not found", requestSubPath)
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "text/html;charset=utf8")
    fmt.Fprintf(w, "This is the <strong>%s</strong>", "index page")
}

func profileHandler(w http.ResponseWriter, r *http.Request) {
    name := muxie.GetParam(w, "name")
    fmt.Fprintf(w, "Profile of: '%s'", name)
}

func profilePhotosHandler(w http.ResponseWriter, r *http.Request) {
    name := muxie.GetParam(w, "name")
    fmt.Fprintf(w, "Photos of: '%s'", name)
}

func listUploadsHandler(w http.ResponseWriter, r *http.Request) {
    file := muxie.GetParam(w, "file")
    fmt.Fprintf(w, "Showing file: '%s'", file)
}

Want to see more examples and documentation? Check out the examples.

Philosophy

I believe that providing the right tools for the right job represents my best self and I really enjoy writing small libraries and even frameworks that can be used and learnt by thousands like me. I do it for the past two and a half years and I couldn't be more happy and proud for myself.

Iris is a web backend framework for Go that is well-known in the Go community, some of you hated it due to a "battle" between "competitors" followed by a single article written almost three years ago but the majority of you really love it so much that you recommend it to your co-workers, use it inside your companies, startups or your client's projects or even write your postgraduate dissertation based on your own experience with Iris. Both categories of fans gave me enough reasons to continue and overcome myself day by day.

It was about the first days of September(2018) that I decided to start working on the next Iris release(version 11) and all job interviews postponed indefinitely. If you have ever seen or hear about Iris, you already know that Iris is one of the fastest and easy-to-use frameworks, this is why it became so famous in so little time after all.

A lot improvements were pushed over a month+ working full-time on Iris. I have never seen a router or a framework supports so many patterns as the current Iris' internal router that is exposed by a beautiful API. However, I couldn't release it for the public yet, I felt that something was missing, I believed that I could do its router smarter and even faster(!) and that ate my guts. And then...in early October, after a lot of testing(and drinking) I found the missing part, it was that the routes' parameterized paths, wildcards and statics all-together for the same path prefix cannot play as fast as possible and good as they should, also I realised that the internal router's code was not the best ever written (it was written to be extremely fast and I didn't care about readability so much back then, when I finally made it to work faster than the rest I forgot to "prettify" it due to my excitement!)

Initially the trie.go and node.go were written for the Iris web framework's version 11 as you can understand by now, I believe that programming should be fun and not stressful, especially for new Gophers. So here we are, introducing a new autonomous Go-based mux(router) that it is light, fast and easy to use for all Gophers, not just for Iris users/developers.

The kataras/muxie repository contains the full source code of my trie implementation and the HTTP component(muxie.NewMux()) which is fully compatible with the net/http package. Users of this package are not limited on HTTP, they can use it to store and search simple key-value data into their programs (muxie.NewTrie()).

  • The trie implementation is easy to read, and if it is not for you please send me a message to explain to you personally
  • The API is simple, just three main methods and the two of them are the well-known Handle and HandleFunc, identically to the std package's net/http#ServeMux
  • Implements a way to store parameters without touching the *http.Request and change the standard handler definition by introducing a new type such as a Context or slow the whole HTTP serve process because of it, look the GetParam function and the internal paramsWriter structure that it is created and used inside the Mux#ServeHTTP
  • Besides the HTTP main functionality that this package offers, users should be able to use it for other things as well, the API is exposed as much as you walk through to
  • Supports named parameters and wildcards of course
  • Supports static path segments(parts, nodes) and named parameters and wildcards for the same path prefix without losing a bit of performance, unlike others that by-design they can't even do it

For the hesitants: There is a public repository (previously private) that you can follow the whole process of coding and designing until the final result of kataras/muxie's.

And... never forget to put some fun in your life ❤︎

Yours,
Gerasimos Maropoulos (@MakisMaropoulos)

License

MIT

Documentation

Index

Constants

View Source
const (
	// ParamStart is the character, as a string, which a path pattern starts to define its named parameter.
	ParamStart = ":"
	// WildcardParamStart is the character, as a string, which a path pattern starts to define its named parameter for wildcards.
	// It allows everything else after that path prefix
	// but the Trie checks for static paths and named parameters before that in order to support everything that other implementations do not,
	// and if nothing else found then it tries to find the closest wildcard path(super and unique).
	WildcardParamStart = "*"
)

Variables

View Source
var (
	Charset = "utf-8"

	JSON = &jsonProcessor{Prefix: nil, Indent: "", UnescapeHTML: false}
	XML  = &xmlProcessor{Indent: ""}
)
View Source
var DefaultKeysSorter = func(list []string) func(i, j int) bool {
	return func(i, j int) bool {
		return len(strings.Split(list[i], pathSep)) < len(strings.Split(list[j], pathSep))
	}
}

DefaultKeysSorter sorts as: first the "key (the path)" with the lowest number of slashes.

Functions

func Bind added in v1.0.3

func Bind(r *http.Request, b Binder, ptrOut interface{}) error

func Dispatch added in v1.0.3

func Dispatch(w http.ResponseWriter, d Dispatcher, v interface{}) error

func GetParam

func GetParam(w http.ResponseWriter, key string) string

GetParam returns the path parameter value based on its key, i.e "/hello/:name", the parameter key is the "name". For example if a route with pattern of "/hello/:name" is inserted to the `Trie` or handlded by the `Mux` and the path "/hello/kataras" is requested through the `Mux#ServeHTTP -> Trie#Search` then the `GetParam("name")` will return the value of "kataras". If not associated value with that key is found then it will return an empty string.

The function will do its job only if the given "w" http.ResponseWriter interface is an `paramsWriter`.

func SetParam

func SetParam(w http.ResponseWriter, key, value string) bool

SetParam sets manually a parameter to the "w" http.ResponseWriter which should be a *paramsWriter. This is not commonly used by the end-developers, unless sharing values(string messages only) between handlers is absolutely necessary.

Types

type Binder added in v1.0.3

type Binder interface {
	Bind(*http.Request, interface{}) error
}

type Dispatcher added in v1.0.3

type Dispatcher interface {
	// no io.Writer because we need to set the headers here,
	// Binder and Processor are only for HTTP.
	Dispatch(http.ResponseWriter, interface{}) error
}

type InsertOption

type InsertOption func(*Node)

InsertOption is just a function which accepts a pointer to a Node which can alt its `Handler`, `Tag` and `Data` fields.

See `WithHandler`, `WithTag` and `WithData`.

func WithData

func WithData(data interface{}) InsertOption

WithData sets the node's optionally `Data` field.

func WithHandler

func WithHandler(handler http.Handler) InsertOption

WithHandler sets the node's `Handler` field (useful for HTTP).

func WithTag

func WithTag(tag string) InsertOption

WithTag sets the node's `Tag` field (may be useful for HTTP).

type MethodHandler added in v1.0.2

type MethodHandler struct {
	// contains filtered or unexported fields
}

MethodHandler implements the `http.Handler` which can be used on `Mux#Handle/HandleFunc` to declare handlers responsible for specific HTTP method(s).

Look `Handle` and `HandleFunc`.

func Methods added in v1.0.2

func Methods() *MethodHandler

Methods returns a MethodHandler which caller can use to register handler for specific HTTP Methods inside the `Mux#Handle/HandleFunc`. Usage: mux := muxie.NewMux() mux.Handle("/user/:id", muxie.Methods().

Handle("GET", getUserHandler).
Handle("POST", saveUserHandler))

func (*MethodHandler) Handle added in v1.0.2

func (m *MethodHandler) Handle(method string, handler http.Handler) *MethodHandler

Handle adds a handler to be responsible for a specific HTTP Method. Returns this MethodHandler for further calls. Usage: Handle("GET", myGetHandler).HandleFunc("DELETE", func(w http.ResponseWriter, r *http.Request){[...]}) Handle("POST, PUT", saveOrUpdateHandler)

^ can accept many methods for the same handler
^ methods should be separated by comma, comma following by a space or just space

func (*MethodHandler) HandleFunc added in v1.0.2

func (m *MethodHandler) HandleFunc(method string, handlerFunc func(w http.ResponseWriter, r *http.Request)) *MethodHandler

HandleFunc adds a handler function to be responsible for a specific HTTP Method. Returns this MethodHandler for further calls.

func (*MethodHandler) ServeHTTP added in v1.0.2

func (m *MethodHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)

type Mux

type Mux struct {
	PathCorrection bool
	Routes         *Trie
	// contains filtered or unexported fields
}

Mux is an HTTP request multiplexer. It matches the URL of each incoming request against a list of registered nodes and calls the handler for the pattern that most closely matches the URL.

Patterns name fixed, rooted paths and dynamic like /profile/:name or /profile/:name/friends or even /files/*file when ":name" and "*file" are the named parameters and wildcard parameters respectfully.

Note that since a pattern ending in a slash names a rooted subtree, the pattern "/*myparam" matches all paths not matched by other registered patterns, but not the URL with Path == "/", for that you would need the pattern "/".

See `NewMux`.

func NewMux

func NewMux() *Mux

NewMux returns a new HTTP multiplexer which uses a fast, if not the fastest implementation of the trie data structure that is designed especially for path segments.

func (*Mux) Handle

func (m *Mux) Handle(pattern string, handler http.Handler)

Handle registers a route handler for a path pattern.

func (*Mux) HandleFunc

func (m *Mux) HandleFunc(pattern string, handlerFunc func(http.ResponseWriter, *http.Request))

HandleFunc registers a route handler function for a path pattern.

func (*Mux) Of

func (m *Mux) Of(prefix string) SubMux

Of returns a new Mux which its Handle and HandleFunc will register the path based on given "prefix", i.e: mux := NewMux() v1 := mux.Of("/v1") v1.HandleFunc("/users", myHandler) The above will register the "myHandler" to the "/v1/users" path pattern.

func (*Mux) ServeHTTP

func (m *Mux) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP exposes and serves the registered routes.

func (m *Mux) Unlink() SubMux

Unlink will remove any inheritance fields from the parent mux (and its parent) that are inherited with the `Of` function. Returns the current SubMux. Usage:

mux := NewMux() mux.Use(myLoggerMiddleware) v1 := mux.Of("/v1").Unlink() // v1 will no longer have the "myLoggerMiddleware". v1.HandleFunc("/users", myHandler)

func (*Mux) Use added in v1.0.1

func (m *Mux) Use(middlewares ...Wrapper)

Use adds middleware that should be called before each mux route's main handler. Should be called before `Handle/HandleFunc`. Order matters.

A Wrapper is just a type of `func(http.Handler) http.Handler` which is a common type definition for net/http middlewares.

To add a middleware for a specific route and not in the whole mux use the `Handle/HandleFunc` with the package-level `muxie.Pre` function instead. Functionality of `Use` is pretty self-explained but new gophers should take a look of the examples for further details.

type Node

type Node struct {

	// insert main data relative to http and a tag for things like route names.
	Handler http.Handler
	Tag     string

	// other insert data.
	Data interface{}
	// contains filtered or unexported fields
}

Node is the trie's node which path patterns with their data like an HTTP handler are saved to. See `Trie` too.

func NewNode

func NewNode() *Node

NewNode returns a new, empty, Node.

func (*Node) IsEnd

func (n *Node) IsEnd() bool

IsEnd returns true if this Node is a final path, has a key.

func (*Node) Keys

func (n *Node) Keys(sorter NodeKeysSorter) (list []string)

Keys returns this node's key (if it's a final path segment) and its children's node's key. The "sorter" can be optionally used to sort the result.

func (*Node) Parent

func (n *Node) Parent() *Node

Parent returns the parent of that node, can return nil if this is the root node.

func (*Node) String

func (n *Node) String() string

String returns the key, which is the path pattern for the HTTP Mux.

type NodeKeysSorter

type NodeKeysSorter = func(list []string) func(i, j int) bool

NodeKeysSorter is the type definition for the sorting logic that caller can pass on `GetKeys` and `Autocomplete`.

type ParamEntry

type ParamEntry struct {
	Key   string
	Value string
}

ParamEntry holds the Key and the Value of a named path parameter.

func GetParams

func GetParams(w http.ResponseWriter) []ParamEntry

GetParams returns all the available parameters based on the "w" http.ResponseWriter which should be a *paramsWriter.

The function will do its job only if the given "w" http.ResponseWriter interface is an `paramsWriter`.

type ParamsSetter

type ParamsSetter interface {
	Set(string, string)
}

ParamsSetter is the interface which should be implemented by the params writer for `Search` in order to store the found named path parameters, if any.

type Processor added in v1.0.3

type Processor interface {
	Binder
	Dispatcher
}

type SubMux

type SubMux interface {
	Of(prefix string) SubMux
	Unlink() SubMux
	Use(middlewares ...Wrapper)
	Handle(pattern string, handler http.Handler)
	HandleFunc(pattern string, handlerFunc func(http.ResponseWriter, *http.Request))
}

SubMux is the child of a main Mux.

type Trie

type Trie struct {
	// contains filtered or unexported fields
}

Trie contains the main logic for adding and searching nodes for path segments. It supports wildcard and named path parameters. Trie supports very coblex and useful path patterns for routes. The Trie checks for static paths(path without : or *) and named parameters before that in order to support everything that other implementations do not, and if nothing else found then it tries to find the closest wildcard path(super and unique).

func NewTrie

func NewTrie() *Trie

NewTrie returns a new, empty Trie. It is only useful for end-developers that want to design their own mux/router based on my trie implementation.

See `Trie`

func (*Trie) Autocomplete

func (t *Trie) Autocomplete(prefix string, sorter NodeKeysSorter) (list []string)

Autocomplete returns the keys that starts with "prefix", this is useful for custom search-engines built on top of my trie implementation.

func (*Trie) HasPrefix

func (t *Trie) HasPrefix(prefix string) bool

HasPrefix returns true if "prefix" is found inside the registered nodes.

func (*Trie) Insert

func (t *Trie) Insert(pattern string, options ...InsertOption)

Insert adds a node to the trie.

func (*Trie) Parents

func (t *Trie) Parents(prefix string) (parents []*Node)

Parents returns the list of nodes that a node with "prefix" key belongs to.

func (*Trie) Search

func (t *Trie) Search(q string, params ParamsSetter) *Node

Search is the most important part of the Trie. It will try to find the responsible node for a specific query (or a request path for HTTP endpoints).

Search supports searching for static paths(path without : or *) and paths that contain named parameters or wildcards. Priority as: 1. static paths 2. named parameters with ":" 3. wildcards 4. closest wildcard if not found, if any 5. root wildcard

func (*Trie) SearchPrefix

func (t *Trie) SearchPrefix(prefix string) *Node

SearchPrefix returns the last node which holds the key which starts with "prefix".

type Wrapper added in v1.0.1

type Wrapper func(http.Handler) http.Handler

Wrapper is just a type of `func(http.Handler) http.Handler` which is a common type definition for net/http middlewares.

type Wrappers added in v1.0.1

type Wrappers struct {
	// contains filtered or unexported fields
}

Wrappers contains `Wrapper`s that can be registered and used by a "main route handler". Look the `Pre` and `For/ForFunc` functions too.

func Pre added in v1.0.1

func Pre(middleware ...Wrapper) Wrappers

Pre starts a chain of handlers for wrapping a "main route handler" the registered "middleware" will run before the main handler(see `Wrappers#For/ForFunc`).

Usage: mux := muxie.NewMux() myMiddlewares := muxie.Pre(myFirstMiddleware, mySecondMiddleware) mux.Handle("/", myMiddlewares.ForFunc(myMainRouteHandler))

func (Wrappers) For added in v1.0.1

func (w Wrappers) For(main http.Handler) http.Handler

For registers the wrappers for a specific handler and returns a handler that can be passed via the `Handle` function.

func (Wrappers) ForFunc added in v1.0.1

func (w Wrappers) ForFunc(mainFunc func(http.ResponseWriter, *http.Request)) http.Handler

ForFunc registers the wrappers for a specific raw handler function and returns a handler that can be passed via the `Handle` function.

Directories

Path Synopsis
_benchmarks
chi
gin
_examples
6_middleware
Package main will explore the helpers for middleware(s) that Muxie has to offer, but they are totally optional, you can still use your favourite pattern to wrap route handlers.
Package main will explore the helpers for middleware(s) that Muxie has to offer, but they are totally optional, you can still use your favourite pattern to wrap route handlers.

Jump to

Keyboard shortcuts

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