commit 8878c5e89c0d78288e024a825bfcdafa3712fe05 Author: Darko Luketic Date: Sun Oct 31 09:03:21 2021 +0100 initial diff --git a/README.md b/README.md new file mode 100644 index 0000000..7fa1830 --- /dev/null +++ b/README.md @@ -0,0 +1,130 @@ +# Sux + +Static route http router that considers the request method + +Useful for serving server-side rendered content. + +## How + +```go +package main + +import ( + "git.icod.de/dalu/sux" + "io" + "net/http" +) + +func main() { + r := sux.New() + r.GET("/", Hello) + r.GET("/simple", Simple) + http.ListenAndServe(":8080", r) +} + +func Hello(w http.ResponseWriter, r *http.Request) { + io.WriteString(w, "hello") +} + +func Simple(w http.ResponseWriter, r *http.Request) { + name := r.URL.Query().Get("name") + io.WriteString(w, "hello "+name) +} +``` + +### Performance + +``` +darko@arch ~ $ wrk -c1000 -t8 -d30s http://inuc:8080/ +Running 30s test @ http://inuc:8080/ + 8 threads and 1000 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 7.08ms 3.02ms 64.38ms 89.40% + Req/Sec 18.24k 2.11k 24.06k 73.88% + 4260394 requests in 30.00s, 491.63MB read +Requests/sec: 142025.60 +Transfer/sec: 16.39MB +darko@arch ~ $ wrk -c1000 -t8 -d30s http://inuc:8080/simple?name=Darko +Running 30s test @ http://inuc:8080/simple?name=Darko + 8 threads and 1000 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 8.11ms 3.84ms 243.61ms 89.96% + Req/Sec 16.01k 2.20k 23.88k 72.87% + 3723315 requests in 30.00s, 454.51MB read +Requests/sec: 124116.94 +Transfer/sec: 15.15MB + +``` + +Compared to https://github.com/julienschmidt/httprouter +``` +darko@arch ~ $ wrk -c1000 -t8 -d30s http://inuc:8080/ +Running 30s test @ http://inuc:8080/ + 8 threads and 1000 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 7.14ms 3.13ms 74.06ms 89.99% + Req/Sec 18.18k 2.19k 23.31k 75.88% + 4224358 requests in 30.00s, 487.47MB read +Requests/sec: 140826.15 +Transfer/sec: 16.25MB +darko@arch ~ $ wrk -c1000 -t8 -d30s http://inuc:8080/simple/Darko +Running 30s test @ http://inuc:8080/simple/Darko + 8 threads and 1000 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 7.78ms 3.40ms 60.03ms 88.79% + Req/Sec 16.55k 1.86k 19.95k 66.95% + 3873629 requests in 30.00s, 472.86MB read +Requests/sec: 129131.98 +Transfer/sec: 15.76MB +``` + +Compared to https://github.com/gocraft/web +``` +darko@arch ~ $ wrk -c1000 -t8 -d30s http://inuc:8080/ +Running 30s test @ http://inuc:8080/ + 8 threads and 1000 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 9.07ms 4.58ms 420.89ms 91.40% + Req/Sec 14.42k 2.28k 25.00k 80.03% + 3309152 requests in 30.00s, 378.70MB read +Requests/sec: 110315.36 +Transfer/sec: 12.62MB +darko@arch ~ $ wrk -c1000 -t8 -d30s http://inuc:8080/simple +Running 30s test @ http://inuc:8080/simple + 8 threads and 1000 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 9.86ms 4.00ms 66.98ms 87.79% + Req/Sec 13.05k 1.65k 17.52k 69.71% + 3055899 requests in 30.00s, 375.95MB read +Requests/sec: 101874.46 +Transfer/sec: 12.53MB +``` + +Compared to https://github.com/gomango/mux +``` +darko@arch ~ $ wrk -c1000 -t8 -d30s http://inuc:8080/ +Running 30s test @ http://inuc:8080/ + 8 threads and 1000 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 9.22ms 12.73ms 235.88ms 84.36% + Req/Sec 13.78k 1.77k 25.52k 74.17% + 3242004 requests in 30.00s, 377.20MB read + Socket errors: connect 0, read 0, write 0, timeout 10 +Requests/sec: 108078.84 +Transfer/sec: 12.57MB +darko@arch ~ $ wrk -c1000 -t8 -d30s http://inuc:8080/simple +Running 30s test @ http://inuc:8080/simple + 8 threads and 1000 connections + Thread Stats Avg Stdev Max +/- Stdev + Latency 16.24ms 14.39ms 150.41ms 56.30% + Req/Sec 7.77k 593.31 9.78k 68.20% + 1841839 requests in 30.00s, 226.59MB read +Requests/sec: 61402.97 +Transfer/sec: 7.55MB +``` + +Apples and Oranges but not quite + +Hardware + +http://www.intel.com/content/www/us/en/nuc/nuc-kit-d54250wykh.html diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..878fccd --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.icod.de/dalu/sux + +go 1.17 diff --git a/sux.go b/sux.go new file mode 100644 index 0000000..af92695 --- /dev/null +++ b/sux.go @@ -0,0 +1,128 @@ +package sux + +import ( + "net/http" +) + +const ( + nomatch uint8 = iota + static +) + +type ( + node struct { + term byte + ntype uint8 + handler http.HandlerFunc + child []*node + } + Router struct { + route map[string]*node + } +) + +var root *Router + +func makenode(term byte) *node { + return &node{ + term: term, + child: make([]*node, 0), + } +} + +func (n *node) addchild(term byte) *node { + if fn := n.findchild(term); fn == nil { + nn := makenode(term) + n.child = append(n.child, nn) + return nn + } else { + return fn + } +} + +func (n *node) maketree(word []byte, handler http.HandlerFunc) { + m := n + for i, l := 1, len(word); i < l; i++ { + m = m.addchild(word[i]) + } + m.ntype = static + m.handler = handler +} + +func (n *node) findchild(term byte) *node { + for _, v := range n.child { + if v.term == term { + return v + } + } + return nil +} + +func (n *node) find(word string) *node { + ss := []byte(word) + m := n + for i, l := 1, len(ss); i < l; i++ { + m = m.findchild(ss[i]) + if m == nil { + return nil + } + } + return m +} + +func (n *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if m, exists := n.route[r.Method]; exists { + m = m.find(r.URL.Path) + if m == nil { + http.NotFound(w, r) + return + } + if m.ntype == nomatch { + http.NotFound(w, r) + return + } + m.handler(w, r) + return + } +} + +func (n *Router) GET(path string, handler http.HandlerFunc) *Router { + n.route["GET"].maketree([]byte(path), handler) + return n +} +func (n *Router) POST(path string, handler http.HandlerFunc) *Router { + n.route["POST"].maketree([]byte(path), handler) + return n +} +func (n *Router) PUT(path string, handler http.HandlerFunc) *Router { + n.route["PUT"].maketree([]byte(path), handler) + return n +} +func (n *Router) PATCH(path string, handler http.HandlerFunc) *Router { + n.route["PATCH"].maketree([]byte(path), handler) + return n +} +func (n *Router) DELETE(path string, handler http.HandlerFunc) *Router { + n.route["DELETE"].maketree([]byte(path), handler) + return n +} +func (n *Router) OPTIONS(path string, handler http.HandlerFunc) *Router { + n.route["OPTIONS"].maketree([]byte(path), handler) + return n +} +func (n *Router) HEAD(path string, handler http.HandlerFunc) *Router { + n.route["HEAD"].maketree([]byte(path), handler) + return n +} + +func New() *Router { + root = &Router{route: make(map[string]*node)} + root.route["GET"] = makenode([]byte("/")[0]) + root.route["POST"] = makenode([]byte("/")[0]) + root.route["PUT"] = makenode([]byte("/")[0]) + root.route["PATCH"] = makenode([]byte("/")[0]) + root.route["DELETE"] = makenode([]byte("/")[0]) + root.route["OPTIONS"] = makenode([]byte("/")[0]) + root.route["HEAD"] = makenode([]byte("/")[0]) + return root +}