xujiajun/gorouter is a simple and fast HTTP router for Go. It is easy to build RESTful APIs and your web framework.

Overview

gorouter GoDoc Build Status Go Report Card Coverage Status License Release Awesome

xujiajun/gorouter is a simple and fast HTTP router for Go. It is easy to build RESTful APIs and your web framework.

Motivation

I wanted a simple and fast HTTP GO router, which supports regexp. I prefer to support regexp is because otherwise it will need the logic to check the URL parameter type, thus increasing the program complexity. So I did some searching on Github and found the wonderful julienschmidt/httprouter: it is very fast,unfortunately it does not support regexp. Later I found out about gorilla/mux: it is powerful as well,but a written benchmark shows me that it is somewhat slow. So I tried to develop a new router which both supports regexp and should be fast. Finally I did it and named xujiajun/gorouter. By the way, this is my first GO open source project. It may be the fastest GO HTTP router which supports regexp, and regarding its performance please refer to my latest Benchmarks.

Features

Requirements

  • golang 1.8+

Installation

go get -u github.com/xujiajun/gorouter

Usage

Static routes

package main

import (
	"log"
	"net/http"

	"github.com/xujiajun/gorouter"
)

func main() {
	mux := gorouter.New()
	mux.GET("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("hello world"))
	})
	log.Fatal(http.ListenAndServe(":8181", mux))
}

URL Parameters

package main

import (
	"log"
	"net/http"

	"github.com/xujiajun/gorouter"
)

func main() {
	mux := gorouter.New()
	//url parameters match
	mux.GET("/user/:id", func(w http.ResponseWriter, r *http.Request) {
		//get one URL parameter
		id := gorouter.GetParam(r, "id")
		//get all URL parameters
		//id := gorouter.GetAllParams(r)
		//fmt.Println(id)
		w.Write([]byte("match user/:id ! get id:" + id))
	})

	log.Fatal(http.ListenAndServe(":8181", mux))
}

Regex Parameters

package main

import (
	"log"
	"net/http"

	"github.com/xujiajun/gorouter"
)

func main() {
	mux := gorouter.New()
	//url regex match
	mux.GET("/user/{id:[0-9]+}", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("match user/{id:[0-9]+} !"))
	})

	log.Fatal(http.ListenAndServe(":8181", mux))
}

Routes Groups

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/xujiajun/gorouter"
)

func usersHandler(w http.ResponseWriter, r *http.Request) {
	fmt.Fprint(w, "/api/users")
}

func main() {
	mux := gorouter.New()
	mux.Group("/api").GET("/users", usersHandler)

	log.Fatal(http.ListenAndServe(":8181", mux))
}

Reverse Routing

package main

import (
	"fmt"
	"net/http"

	"github.com/xujiajun/gorouter"
)

func main() {
	mux := gorouter.New()

	routeName1 := "user_event"
	mux.GETAndName("/users/:user/events", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("/users/:user/events"))
	}, routeName1)

	routeName2 := "repos_owner"
	mux.GETAndName("/repos/{owner:\\w+}/{repo:\\w+}", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("/repos/{owner:\\w+}/{repo:\\w+}"))
	}, routeName2)

	params := make(map[string]string)
	params["user"] = "xujiajun"
	fmt.Println(mux.Generate(http.MethodGet, routeName1, params)) // /users/xujiajun/events <nil>

	params = make(map[string]string)
	params["owner"] = "xujiajun"
	params["repo"] = "xujiajun_repo"
	fmt.Println(mux.Generate(http.MethodGet, routeName2, params)) // /repos/xujiajun/xujiajun_repo <nil>
}

Custom NotFoundHandler

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/xujiajun/gorouter"
)

func notFoundFunc(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusNotFound)
	fmt.Fprint(w, "404 page !!!")
}

func main() {
	mux := gorouter.New()
	mux.NotFoundFunc(notFoundFunc)
	mux.GET("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("hello world"))
	})

	log.Fatal(http.ListenAndServe(":8181", mux))
}

Custom PanicHandler

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/xujiajun/gorouter"
)

func main() {
	mux := gorouter.New()
	mux.PanicHandler = func(w http.ResponseWriter, req *http.Request, err interface{}) {
		w.WriteHeader(http.StatusInternalServerError)
		fmt.Println("err from recover is :", err)
		fmt.Fprint(w, "received a panic")
	}
	mux.GET("/panic", func(w http.ResponseWriter, r *http.Request) {
		panic("panic")
	})

	log.Fatal(http.ListenAndServe(":8181", mux))
}

Middlewares Chain

package main

import (
	"fmt"
	"log"
	"net/http"

	"github.com/xujiajun/gorouter"
)

type statusRecorder struct {
	http.ResponseWriter
	status int
}

func (rec *statusRecorder) WriteHeader(code int) {
	rec.status = code
	rec.ResponseWriter.WriteHeader(code)
}

//https://upgear.io/blog/golang-tip-wrapping-http-response-writer-for-middleware/
func withStatusRecord(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		rec := statusRecorder{w, http.StatusOK}
		next.ServeHTTP(&rec, r)
		log.Printf("response status: %v\n", rec.status)
	}
}

func notFoundFunc(w http.ResponseWriter, r *http.Request) {
	w.WriteHeader(http.StatusNotFound)
	fmt.Fprint(w, "Not found page !")
}

func withLogging(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		log.Printf("Logged connection from %s", r.RemoteAddr)
		next.ServeHTTP(w, r)
	}
}

func withTracing(next http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		log.Printf("Tracing request for %s", r.RequestURI)
		next.ServeHTTP(w, r)
	}
}

func main() {
	mux := gorouter.New()
	mux.NotFoundFunc(notFoundFunc)
	mux.Use(withLogging, withTracing, withStatusRecord)
	mux.GET("/", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("hello world"))
	})

	log.Fatal(http.ListenAndServe(":8181", mux))
}

Serve static files

package main

import (
	"log"
	"net/http"
	"os"
	
	"github.com/xujiajun/gorouter"
)

//ServeFiles serve static resources
func ServeFiles(w http.ResponseWriter, r *http.Request) {
	wd, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}

	dir := wd + "/examples/serveStaticFiles/files"
	http.StripPrefix("/files/", http.FileServer(http.Dir(dir))).ServeHTTP(w, r)
}

func main() {
	mux := gorouter.New()
	mux.GET("/hi", func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("hi"))
	})
	//defined prefix
	mux2 := mux.Group("/files")
	//http://127.0.0.1:8181/files/demo.txt
	//will match
	mux2.GET("/{filename:[0-9a-zA-Z_.]+}", func(w http.ResponseWriter, r *http.Request) {
		ServeFiles(w, r)
	})

	//http://127.0.0.1:8181/files/a/demo2.txt
	//http://127.0.0.1:8181/files/a/demo.txt
	//will match
	mux2.GET("/{fileDir:[0-9a-zA-Z_.]+}/{filename:[0-9a-zA-Z_.]+}", func(w http.ResponseWriter, r *http.Request) {
		ServeFiles(w, r)
	})

	log.Fatal(http.ListenAndServe(":8181", mux))
}

Detail see serveStaticFiles example

Pattern Rule

The syntax here is modeled after julienschmidt/httprouter and gorilla/mux

Syntax Description Example
:name named parameter /user/:name
{name:regexp} named with regexp parameter /user/{name:[0-9a-zA-Z]+}
:id named with regexp parameter /user/:id

And :id is short for {id:[0-9]+}, :name are short for {name:[0-9a-zA-Z_]+}

if use default regex checks unless you know what you're doing

Benchmarks

the benchmarks code for gorouter be found in the gorouter-bench repository.

go test -bench=.

Benchmark System:

  • Go Version : go1.11.2 darwin/amd64
  • OS: Mac OS X 10.13.6
  • Architecture: x86_64
  • 16 GB 2133 MHz LPDDR3
  • CPU: 3.1 GHz Intel Core i7

Tested routers:

Thanks the author of httprouter: @julienschmidt give me advise about benchmark issues/24

Result:

Given some routing matching syntax differences, divide GithubAPI into two groups:

Using GithubAPI Result:

BenchmarkBeegoMuxRouterWithGithubAPI-8   	   10000	    142398 ns/op	  134752 B/op	    1038 allocs/op
BenchmarkBoneRouterWithGithubAPI-8       	    1000	   2104486 ns/op	  720160 B/op	    8620 allocs/op
BenchmarkTrieMuxRouterWithGithubAPI-8    	   20000	     80845 ns/op	   65856 B/op	     537 allocs/op
BenchmarkHttpRouterWithGithubAPI-8       	   50000	     30169 ns/op	   13792 B/op	     167 allocs/op
BenchmarkGoRouter1WithGithubAPI-8        	   30000	     57793 ns/op	   13832 B/op	     406 allocs/op

Using GithubAPI2 Result:

BenchmarkGoRouter2WithGithubAPI2-8       	   30000	     57613 ns/op	   13832 B/op	     406 allocs/op
BenchmarkChiRouterWithGithubAPI2-8       	   10000	    143224 ns/op	  104436 B/op	    1110 allocs/op
BenchmarkMuxRouterWithGithubAPI2-8       	     300	   4450731 ns/op	   61463 B/op	     995 allocs/op

All togther Result:

➜  gorouter git:(master) go test -bench=.
GithubAPI Routes: 203
GithubAPI2 Routes: 203
   BeegoMuxRouter: 111072 Bytes
   BoneRouter: 100992 Bytes
   ChiRouter: 71512 Bytes
   HttpRouter: 37016 Bytes
   trie-mux: 131128 Bytes
   MuxRouter: 1378496 Bytes
   GoRouter1: 83824 Bytes
   GoRouter2: 85584 Bytes
goos: darwin
goarch: amd64
pkg: github.com/xujiajun/gorouter
BenchmarkBeegoMuxRouterWithGithubAPI-8   	   10000	    142398 ns/op	  134752 B/op	    1038 allocs/op
BenchmarkBoneRouterWithGithubAPI-8       	    1000	   2104486 ns/op	  720160 B/op	    8620 allocs/op
BenchmarkTrieMuxRouterWithGithubAPI-8    	   20000	     80845 ns/op	   65856 B/op	     537 allocs/op
BenchmarkHttpRouterWithGithubAPI-8       	   50000	     30169 ns/op	   13792 B/op	     167 allocs/op
BenchmarkGoRouter1WithGithubAPI-8        	   30000	     57793 ns/op	   13832 B/op	     406 allocs/op
BenchmarkGoRouter2WithGithubAPI2-8       	   30000	     57613 ns/op	   13832 B/op	     406 allocs/op
BenchmarkChiRouterWithGithubAPI2-8       	   10000	    143224 ns/op	  104436 B/op	    1110 allocs/op
BenchmarkMuxRouterWithGithubAPI2-8       	     300	   4450731 ns/op	   61463 B/op	     995 allocs/op
PASS
ok  	github.com/xujiajun/gorouter	15.918s

Conclusions:

  • Performance (xujiajun/gorouter,julienschmidt/httprouter and teambition/trie-mux are fast)

  • Memory Consumption (xujiajun/gorouter and julienschmidt/httprouter are fewer)

  • Features (julienschmidt/httprouter not supports regexp,but others support it)

if you want a high performance router which supports regexp, maybe xujiajun/gorouter is good choice.

if you want a high performance router which not supports regexp, maybe julienschmidt/httprouter is good choice.

In the end, as julienschmidt said performance can not be the (only) criterion for choosing a router. Play around a bit with some of the routers, and choose the one you like best.

Contributing

If you'd like to help out with the project. You can put up a Pull Request. Thanks to all contributors.

Author

License

The gorouter is open-sourced software licensed under the MIT Licensed

Acknowledgements

This package is inspired by the following:

Issues
  • Benchmark is not (only) measuring the routing cost

    Benchmark is not (only) measuring the routing cost

    Currently your benchmark code does the following:

    func benchRoutes(b *testing.B, router http.Handler, routes []route) {
    	b.N = 10000
    	b.ReportAllocs()
    	b.ResetTimer()
    
    	for i := 0; i < b.N; i++ {
    		for _, route := range routes {
    			router.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(route.method, route.path, nil))
    		}
    	}
    }
    

    The critical part is the router.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(route.method, route.path, nil)) within the loop, meaning that you allocate and initialize a new Recorder and a Request in every loop iteration. Unfortunately that is likely much more expensive than the actual routing, meaning that both the measured memory cost and the execution time are completely meaningless, since there is a huge baseline cost.

    opened by julienschmidt 7
  • How to remove regex from :id and :name parameters?

    How to remove regex from :id and :name parameters?

    I like the idea of this router, and I'm planning on using it for an upcoming project. However the automatic regex matching for :id and :name is bothering me. I don't want to use it, and if I do, I'll provide my own regex. How do I prevent any regex matching from happening for those parameters?

    I don't think it's a good idea to assume the regex that people want to use for random parameters, it can cause bugs and other problems. But that's just my opinion.

    question 
    opened by awulkan 5
  • Which CPU was used for your benchmarks?

    Which CPU was used for your benchmarks?

    Your current benchmark doesn't say which CPU model you're using for the benchmark. This is probably the most important part to include. I assume you're running some pre 7th-gen Intel i5/i7 CPU since it's on a Mac with DDR3 RAM.

    Benchmark System: Go Version : go1.11.2 darwin/amd64 OS: Mac OS X 10.13.6 Architecture: x86_64 RAM; 16 GB 2133 MHz LPDDR3 CPU: ???

    question 
    opened by awulkan 3
  • Are there any plans to support reverse routing?

    Are there any plans to support reverse routing?

    Your router looks very attractive from a performance perspective.

    But it looks like it is missing a very important feature for REST API that is reverse routing.

    I am widely using gorilla/mux along with its reverse routing right now and would like to find a faster alternative for it.

    If your router would have this feature, it would be very useful.

    enhancement 
    opened by pamburus 2
  • Router always return 404 response code.

    Router always return 404 response code.

    Router always return 404 response header "Hello world!" run the same with error code.

    # go version 
    go version go1.13.7 linux/amd64
    
    # curl  localhost:8181 
    hello world
    
    # curl  localhost:8181 -I 
    HTTP/1.1 404 Not Found
    
    opened by 0x00dec0de 1
  • static file handler is too complex

    static file handler is too complex

    user need to add url partern for every depth folder ? is there a short way to match a total folder as below

    // FileSystem custom file system handler
    type FileSystem struct {
        fs http.FileSystem
    }
    
    // Open opens file
    func (fs FileSystem) Open(path string) (http.File, error) {
        f, err := fs.fs.Open(path)
        if err != nil {
            return nil, err
        }
    
        s, err := f.Stat()
        if s.IsDir() {
            index := strings.TrimSuffix(path, "/") + "/index.html"
            if _, err := fs.fs.Open(index); err != nil {
                return nil, err
            }
        }
    
        return f, nil
    }
    
    
    fileServer := http.FileServer(FileSystem{http.Dir(".")})
    http.Handle("/static/", http.StripPrefix(strings.TrimRight("/static/", "/"), fileServer))
    
    opened by freboat 1
  • can't match suffix '/' in url

    can't match suffix '/' in url

    image

    image

    Server side code:

    func main() {
    	mux := gorouter.New()
    	//url regex match
    	mux.GET("/user/{id:[0-9]+}/", func(w http.ResponseWriter, r *http.Request) {
    		w.Write([]byte("match user/{id:[0-9]+} !"))
    	})
    
    	log.Fatal(http.ListenAndServe(":8181", mux))
    }
    
    
    opened by vision9527 1
Releases(v1.2.0)
Owner
徐佳军
You will never know what you can do till you try.
徐佳军
Bxog is a simple and fast HTTP router for Go (HTTP request multiplexer).

Bxog is a simple and fast HTTP router for Go (HTTP request multiplexer). Usage An example of using the multiplexer: package main import ( "io" "net

Eduard 103 Apr 10, 2022
Simple router build on `net/http` supports custom middleWare.

XMUS-ROUTER Fast lightweight router build on net/http supports delegate and in url params. usage : Create new router using NewRouter() which need rout

amupxm [amir hossein mokarrami far] 5 Dec 27, 2021
Go HTTP request router and web framework benchmark

Go HTTP Router Benchmark This benchmark suite aims to compare the performance of HTTP request routers for Go by implementing the routing structure of

Peter Kieltyka 23 Apr 21, 2022
Fast, simple, and lightweight HTTP router for Golang

Sariaf Fast, simple and lightweight HTTP router for golang Install go get -u github.com/majidsajadi/sariaf Features Lightweight compatible with net/ht

defectivepixel 33 Mar 3, 2022
:rotating_light: Is a lightweight, fast and extensible zero allocation HTTP router for Go used to create customizable frameworks.

LARS LARS is a fast radix-tree based, zero allocation, HTTP router for Go. view examples. If looking for a more pure Go solution, be sure to check out

Go Playgound 387 Jun 11, 2022
Fast and flexible HTTP router

treemux - fast and flexible HTTP router Basic example Debug logging CORS example Error handling Rate limiting using Redis Gzip compression OpenTelemet

Vladimir Mihailenco 441 Jun 25, 2022
FastRouter is a fast, flexible HTTP router written in Go.

FastRouter FastRouter is a fast, flexible HTTP router written in Go. FastRouter contains some customizable options, such as TrailingSlashesPolicy, Pan

Razon Yang 21 Apr 19, 2022
Pure is a fast radix-tree based HTTP router

package pure Pure is a fast radix-tree based HTTP router that sticks to the native implementations of Go's "net/http" package; in essence, keeping the

Go Playgound 126 Jun 25, 2022
A powerful HTTP router and URL matcher for building Go web servers with 🦍

gorilla/mux https://www.gorillatoolkit.org/pkg/mux Package gorilla/mux implements a request router and dispatcher for matching incoming requests to th

Gorilla Web Toolkit 16.8k Jun 22, 2022
Go Server/API micro framework, HTTP request router, multiplexer, mux

?? gorouter Go Server/API micro framework, HTTP request router, multiplexer, mux. ?? ABOUT Contributors: Rafał Lorenz Want to contribute ? Feel free t

Rafał Lorenz 128 Jun 13, 2022
Simple Golang HTTP router

Bellt Simple Golang HTTP router Bellt Package implements a request router with the aim of managing controller actions based on fixed and parameterized

Guilherme Caruso 53 Jan 24, 2022
Simple HTTP router for Go

ngamux Simple HTTP router for Go Installation Examples Todo Installation Run this command with correctly configured Go toolchain. go get github.com/ng

null 51 Jun 1, 2022
Simple http router.

Simple router based on tree structure. import ( "fmt" "log" "net/http" router "github.com/dmitruk-v/router/v1" ) func main() { ro := router.New

Valeriy Dmitruk 0 Dec 29, 2021
lightweight, idiomatic and composable router for building Go HTTP services

chi is a lightweight, idiomatic and composable router for building Go HTTP services. It's especially good at helping you write large REST API services

go-chi 11.7k Jun 26, 2022
:tongue: CleverGo is a lightweight, feature rich and high performance HTTP router for Go.

CleverGo CleverGo is a lightweight, feature rich and trie based high performance HTTP request router. go get -u clevergo.tech/clevergo English 简体中文 Fe

CleverGo Web Framework 251 May 30, 2022
Lightweight Router for Golang using net/http standard library with custom route parsing, handler and context.

Go-Lightweight-Router Lightweight Router for Golang using net/http standard library with custom route parsing, handler and context. Further developmen

null 0 Nov 3, 2021
A high performance HTTP request router that scales well

HttpRouter HttpRouter is a lightweight high performance HTTP request router (also called multiplexer or just mux for short) for Go. In contrast to the

Julien Schmidt 14.2k Jun 26, 2022
High-speed, flexible tree-based HTTP router for Go.

httptreemux High-speed, flexible, tree-based HTTP router for Go. This is inspired by Julien Schmidt's httprouter, in that it uses a patricia tree, but

Daniel Imfeld 545 Jun 23, 2022
Go HTTP router

violetear Go HTTP router http://violetear.org Design Goals Keep it simple and small, avoiding extra complexity at all cost. KISS Support for static an

Nicolas Embriz 105 Mar 21, 2022