• Stars
    star
    53
  • Rank 552,440 (Top 11 %)
  • Language
    Go
  • License
    MIT License
  • Created over 5 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A golang http router based on trie tree.

English ๆ—ฅๆœฌ่ชž

goblin

Mentioned in Awesome Go GitHub release CircleCI Go Report Card codecov GitHub license Go Reference Sourcegraph

A golang http router based on trie tree.

goblin

This logo was created by gopherize.me.

Table of contents

Features

  • Go1.21 >= 1.16
  • Simple data structure based on trie tree
  • Lightweight
    • Lines of codes: 2428
    • Package size: 140K
  • No dependencies other than standard packages
  • Compatible with net/http
  • More advanced than net/http's Servemux
    • Method based routing
    • Named parameter routing
    • Regular expression based routing
    • Middleware
    • Customizable error handlers
    • Default OPTIONS handler
  • 0allocs
    • Achieve 0 allocations in static routing
    • About 3allocs for named routes
      • Heap allocation occurs when creating parameter slices and storing parameters in context

Install

go get -u github.com/bmf-san/goblin

Example

A sample implementation is available.

Please refer to example_goblin_test.go.

Usage

Method based routing

Routing can be defined based on any HTTP method.

The following HTTP methods are supported. GET/POST/PUT/PATCH/DELETE/OPTIONS

r := goblin.NewRouter()

r.Methods(http.MethodGet).Handler(`/`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "/")
}))

r.Methods(http.MethodGet, http.MethodPost).Handler(`/methods`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    if r.Method == http.MethodGet {
        fmt.Fprintf(w, "GET")
    }
    if r.Method == http.MethodPost {
        fmt.Fprintf(w, "POST")
    }
}))

http.ListenAndServe(":9999", r)

Named parameter routing

You can define routing with named parameters (:paramName).

r := goblin.NewRouter()

r.Methods(http.MethodGet).Handler(`/foo/:id`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    id := goblin.GetParam(r.Context(), "id")
    fmt.Fprintf(w, "/foo/%v", id)
}))

r.Methods(http.MethodGet).Handler(`/foo/:name`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    name := goblin.GetParam(r.Context(), "name")
    fmt.Fprintf(w, "/foo/%v", name)
}))

http.ListenAndServe(":9999", r)

Regular expression based routing

By using regular expressions for named parameters (:paramName[pattern]), you can define routing using regular expressions.

r.Methods(http.MethodGet).Handler(`/foo/:id[^\d+$]`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    id := goblin.GetParam(r.Context(), "id")
    fmt.Fprintf(w, "/foo/%v", id)
}))

Middleware

Supports middleware to help pre-process requests and post-process responses.

Middleware can be defined for any routing.

Middleware can also be configured globally. If a middleware is configured globally, the middleware will be applied to all routing.

More than one middleware can be configured.

Middleware must be defined as a function that returns http.

// Implement middleware as a function that returns http.Handl
func global(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "global: before\n")
		next.ServeHTTP(w, r)
		fmt.Fprintf(w, "global: after\n")
	})
}

func first(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "first: before\n")
		next.ServeHTTP(w, r)
		fmt.Fprintf(w, "first: after\n")
	})
}

func second(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "second: before\n")
		next.ServeHTTP(w, r)
		fmt.Fprintf(w, "second: after\n")
	})
}

func third(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "third: before\n")
		next.ServeHTTP(w, r)
		fmt.Fprintf(w, "third: after\n")
	})
}

r := goblin.NewRouter()

// Set middleware globally
r.UseGlobal(global)
r.Methods(http.MethodGet).Handler(`/globalmiddleware`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "/globalmiddleware\n")
}))

// Use methods can be used to apply middleware
r.Methods(http.MethodGet).Use(first).Handler(`/middleware`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "middleware\n")
}))

// Multiple middleware can be configured
r.Methods(http.MethodGet).Use(second, third).Handler(`/middlewares`, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "middlewares\n")
}))

http.ListenAndServe(":9999", r)

A request to /globalmiddleware gives the following results.

global: before
/globalmiddleware
global: after

A request to /middleware gives the following results.

global: before
first: before
middleware
first: after
global: after

A request to /middlewares gives the following results.

global: before
second: before
third: before
middlewares
third: after
second: after
global: after

Customizable error handlers

You can define your own error handlers.

The following two types of error handlers can be defined

  • NotFoundHandler
    • Handler that is executed when no result matching the routing is obtained
  • MethodNotAllowedHandler
    • Handler that is executed when no matching method is found
func customMethodNotFound() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "customMethodNotFound")
	})
}

func customMethodAllowed() http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "customMethodNotAllowed")
	})
}

r := goblin.NewRouter()
r.NotFoundHandler = customMethodNotFound()
r.MethodNotAllowedHandler = customMethodAllowed()

http.ListenAndServe(":9999", r)

Default OPTIONS handler

You can define a default handler that will be executed when a request is made with the OPTIONS method.

func DefaultOPTIONSHandler(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.WriteHeader(http.StatusNoContent)
	})
}

r := goblin.NewRouter()
r.DefaultOPTIONSHandler = DefaultOPTIONSHandler()

http.ListenAndServe(":9999", r)

The default OPTIONS handler is useful, for example, in handling CORS OPTIONS requests (preflight requests).

Benchmark tests

We have a command to run a goblin benchmark test.

Please refer to Makefile.

Curious about benchmark comparison results with other HTTP Routers?

Please see here! bmf-san/go-router-benchmark

Design

This section describes the internal data structure of goblin.

While radix tree is often employed in performance-optimized HTTP Routers, goblin uses trie tree.

Compared to radix trees, trie tree have a disadvantage in terms of performance due to inferior memory usage. However, the simplicity of the algorithm and ease of understanding are overwhelmingly in favor of the trie tree.

HTTP Router may seem like a simple application with a simple specification, but it is surprisingly complex. You can see this by looking at the test cases. (If you have an idea for a better-looking test case implementation, please let us know.)

One advantage of using a simple algorithm is that it contributes to code maintainability. (This may sound like an excuse for the difficulty of implementing a radix tree... in fact, the difficulty of implementing an HTTP Router based on a radix tree frustrated me once...)

Using the source code of _examples as an example, I will explain the internal data structure of goblin.

The routing definitions are represented in a table as follows.

Method Path Handler Middleware
GET / RootHandler N/A
GET /foo FooHandler CORS
POST /foo FooHandler CORS
GET /foo/bar FooBarHandler N/A
GET /foo/bar/:name FooBarNameHandler N/A
POST /foo/:name FooNameHandler N/A
GET /baz BazHandler CORS

In gobin, such routing is represented as the following tree structure.

legend๏ผš<HTTP Method>,[Node]

<GET>
    โ”œโ”€โ”€ [/]
    |
    โ”œโ”€โ”€ [/foo]
    |        |
    |        โ””โ”€โ”€ [/bar]
    |                 |
    |                 โ””โ”€โ”€ [/:name]
    |
    โ””โ”€โ”€ [/baz]

<POST>
    โ””โ”€โ”€ [/foo]
             |
             โ””โ”€โ”€ [/:name]

The tree is constructed for each HTTP method.

Each node has handler and middleware definitions as data.

In order to simplify the explanation, data such as named routing data and global middleware data are omitted here.

Various other data is held in the internally constructed tree.

If you want to know more, use the debugger to take a peek at the internal structure.

If you have any ideas for improvements, please let us know!

Wiki

References are listed on the wiki.

Contribution

Issues and Pull Requests are always welcome.

We would be happy to receive your contributions.

Please review the following documents before making a contribution.

Sponsor

If you like it, I would be happy to have you sponsor it!

GitHub Sponsors - bmf-san

Or I would be happy to get a STAR.

It motivates me to keep up with ongoing maintenance :D

Stargazers

Stargazers repo roster for @bmf-san/goblin

Forkers

Forkers repo roster for @bmf-san/goblin

License

Based on the MIT License.

LICENSE

Author

bmf-san

More Repositories

1

go-clean-architecture-web-application-boilerplate

A web application boilerplate built with go and clean architecture.
Go
212
star
2

Rubel

Rubel is a cms built with Laravel and React.
PHP
71
star
3

gobel-api

Gobel is a headless cms built with golang.
Go
26
star
4

godon

Godon is a simple L4 load balancer built with golang
Go
19
star
5

oilking

A trading bot using bitflyer api.
Go
9
star
6

react-redux-spa-boilerplate

The SPA boilerplate working with react, react-redux, react-router, redux-form, redux-promise.
JavaScript
5
star
7

setup-kubernetes-cluster-on-vps-boilerplate

This is a boilerplate for setup kubernetes cluster on vps by terraform and ansible.
HCL
5
star
8

sea.css

sea.css is a simple and easy to use css framework.
CSS
4
star
9

akashi-slack-slash-command

This is a slack slash command for Akashi
Go
3
star
10

bmf-php-router

The simple URL router built with PHP
PHP
3
star
11

introduction-to-golang-http-router-made-with-net-http

It is a repository to introduce how to implement your own HTTP router with golang.
Go
3
star
12

vagrant-development-workflow

Vagrant Development Workflow
2
star
13

go-mysqldump

This is a mysqldump tool working with golang.
Go
2
star
14

akashigo

Go library for accessing the akashi API
Go
2
star
15

golem

A leveled logger in json format built with golang.
Go
2
star
16

terraform-ansible-openstack-boilerplate

Boilerplate for building vps using terraform and ansible.
HCL
2
star
17

psr4-autoloader-boilerplate

PSR-4 Autoloader Boilerplate
PHP
2
star
18

bitflyer-chat-morphological-analysis

Bitflyer Chat Morphological Analysis
PHP
2
star
19

go-router-benchmark

Benchmark tests for http router implemented in golang.
Go
2
star
20

laravel-test-handson

This is handson for feature test of laravel
PHP
1
star
21

goemon

A dotenv built with golang.
Go
1
star
22

bitflyer-private-api-and-slack-api-sample

Bitflyer Private Api And Slack Api Sample
JavaScript
1
star
23

bitbankgo

Go library for accessing the bitbank API
Go
1
star
24

docker-based-monitoring-stack-boilerplate

This is a boilerplate for docker based monitoring stack.
Dockerfile
1
star
25

go-snippets

This is my go snippets.
Go
1
star
26

gobel-admin-client-example

Gobel is a headless cms built with golang.
Vue
1
star
27

road-to-vimmer

This is a workbook for studing vim
1
star
28

book-introduction-to-golang-http-router-made-with-net-http

Introduction to Golang HTTP router made with net/http.
1
star
29

gobel-client-example

Gobel is a headless cms built with golang.
Go
1
star
30

docs-md-to-pdf-example

This is a document management sample that converts markdown format documents to PDF format. It supports emoji, toc generation, and mermaid.
JavaScript
1
star