Coder Social home page Coder Social logo

uptrace / bunrouter Goto Github PK

View Code? Open in Web Editor NEW
706.0 9.0 36.0 343 KB

Golang HTTP router

Home Page: https://bunrouter.uptrace.dev/guide/golang-router.html

License: MIT License

Go 96.85% Makefile 0.72% JavaScript 0.07% Shell 2.36%
http-router golang go router

bunrouter's Introduction

Fast and flexible HTTP router for Go

build workflow PkgGoDev Documentation Chat

BunRouter is brought to you by ⭐ uptrace/uptrace. Uptrace is an open-source APM tool that supports distributed tracing, metrics, and logs. You can use it to monitor applications and set up automatic alerts to receive notifications via email, Slack, Telegram, and others. Star it as well!

TLDR BunRouter is as fast as httprouter, but supports middlewares, routing rules priority, and error handling.

BunRouter is an extremely fast HTTP router for Go with unique combination of features:

  • Middlewares allow to extract common operations from HTTP handlers into reusable functions.
  • Error handling allows to further reduce the size of HTTP handlers by handling errors in middlewares.
  • Routes priority enables meaningful matching priority for routing rules: first static nodes, then named nodes, lastly wildcard nodes.
  • net/http compatible API which means using minimal API without constructing huge wrappers that try to do everything: from serving static files to XML generation (for example, gin.Context or echo.Context).
Router Middlewares Error handling Routes priority net/http API
BunRouter ✔️ ✔️ ✔️ ✔️
httprouter ✔️
Chi ✔️ ✔️ ✔️
Echo ✔️ ✔️
Gin ✔️ ✔️

Learn:

Examples:

Projects using BunRouter:

Benchmarks:

Benchmark results
BenchmarkGin_Param               	16019718	        74.16 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_Param        	12560001	        95.04 ns/op	      32 B/op	       1 allocs/op
BenchmarkBunrouter_Param         	50015306	        23.81 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_Param5              	 8997234	       131.5 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_Param5       	 4809441	       261.3 ns/op	     160 B/op	       1 allocs/op
BenchmarkBunrouter_Param5        	10789635	       114.0 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_Param20             	 3953041	       302.4 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_Param20      	 1661373	       743.3 ns/op	     640 B/op	       1 allocs/op
BenchmarkBunrouter_Param20       	 2462354	       482.8 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_ParamWrite          	 9258986	       128.0 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_ParamWrite   	 9908178	       123.0 ns/op	      32 B/op	       1 allocs/op
BenchmarkBunrouter_ParamWrite    	15511226	        70.62 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_GithubStatic        	12781513	        94.17 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_GithubStatic 	30077443	        37.36 ns/op	       0 B/op	       0 allocs/op
BenchmarkBunrouter_GithubStatic  	37160334	        32.41 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_GithubParam         	 6971791	       169.2 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_GithubParam  	 5464755	       217.4 ns/op	      96 B/op	       1 allocs/op
BenchmarkBunrouter_GithubParam   	12047902	       101.2 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_GithubAll           	   32758	     37382 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_GithubAll    	   27324	     43932 ns/op	   13792 B/op	     167 allocs/op
BenchmarkBunrouter_GithubAll     	   57910	     20914 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_GPlusStatic         	17788194	        69.13 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_GPlusStatic  	60191341	        19.84 ns/op	       0 B/op	       0 allocs/op
BenchmarkBunrouter_GPlusStatic   	87114368	        14.06 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_GPlusParam          	10075399	       119.5 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_GPlusParam   	 8272046	       149.2 ns/op	      64 B/op	       1 allocs/op
BenchmarkBunrouter_GPlusParam    	37359979	        32.43 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_GPlus2Params        	 7375279	       162.9 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_GPlus2Params 	 6538942	       186.7 ns/op	      64 B/op	       1 allocs/op
BenchmarkBunrouter_GPlus2Params  	19681939	        61.51 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_GPlusAll            	  647716	      1752 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_GPlusAll     	  590356	      2085 ns/op	     640 B/op	      11 allocs/op
BenchmarkBunrouter_GPlusAll      	 1685287	       712.8 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_ParseStatic         	14566458	        76.58 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_ParseStatic  	52994076	        21.02 ns/op	       0 B/op	       0 allocs/op
BenchmarkBunrouter_ParseStatic   	50583933	        23.83 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_ParseParam          	13443874	        90.66 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_ParseParam   	 8825664	       135.6 ns/op	      64 B/op	       1 allocs/op
BenchmarkBunrouter_ParseParam    	38058278	        31.33 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_Parse2Params        	10179813	       118.1 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_Parse2Params 	 7801735	       152.9 ns/op	      64 B/op	       1 allocs/op
BenchmarkBunrouter_Parse2Params  	23704574	        50.78 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_ParseAll            	  394884	      3073 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_ParseAll     	  410238	      3011 ns/op	     640 B/op	      16 allocs/op
BenchmarkBunrouter_ParseAll      	  810908	      1487 ns/op	       0 B/op	       0 allocs/op
BenchmarkGin_StaticAll           	   50658	     23699 ns/op	       0 B/op	       0 allocs/op
BenchmarkHttpRouter_StaticAll    	  105313	     11518 ns/op	       0 B/op	       0 allocs/op
BenchmarkBunrouter_StaticAll     	   99674	     12188 ns/op	       0 B/op	       0 allocs/op

Quickstart

Install:

go get github.com/uptrace/bunrouter

Run the example:

package main

import (
	"html/template"
	"log"
	"net/http"

	"github.com/uptrace/bunrouter"
	"github.com/uptrace/bunrouter/extra/reqlog"
)

func main() {
	router := bunrouter.New(
		bunrouter.Use(reqlog.NewMiddleware()),
	)

	router.GET("/", indexHandler)

	router.WithGroup("/api", func(g *bunrouter.Group) {
		g.GET("/users/:id", debugHandler)
		g.GET("/users/current", debugHandler)
		g.GET("/users/*path", debugHandler)
	})

	log.Println("listening on http://localhost:9999")
	log.Println(http.ListenAndServe(":9999", router))
}

func indexHandler(w http.ResponseWriter, req bunrouter.Request) error {
	return indexTemplate().Execute(w, nil)
}

func debugHandler(w http.ResponseWriter, req bunrouter.Request) error {
	return bunrouter.JSON(w, bunrouter.H{
		"route":  req.Route(),
		"params": req.Params().Map(),
	})
}

var indexTmpl = `
<html>
  <h1>Welcome</h1>
  <ul>
    <li><a href="/api/users/123">/api/users/123</a></li>
    <li><a href="/api/users/current">/api/users/current</a></li>
    <li><a href="/api/users/foo/bar">/api/users/foo/bar</a></li>
  </ul>
</html>
`

func indexTemplate() *template.Template {
	return template.Must(template.New("index").Parse(indexTmpl))
}

See the Golang Router documentation for details.

See also

bunrouter's People

Contributors

danielecina avatar frederikhors avatar vmihailenco avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

bunrouter's Issues

How to emulate a webhit

💡 The feature or bug you are proposing

Normally I do something like this:

	resp := httptest.NewRecorder()
	req := bunrouter.Request{}
	req.URL = &url.URL{}

and call the controller (handlerfunc) like this:

	controller.GetMoreRecords(resp, req)
	result := resp.Body.String()

then do some asserts on the body
But since req.URL = &url.URL{} doesn't work, I wonder how I can test it now.

Router unexpectedly panics in some cases

Consider the following program:

package main

import (
	"net/http"

	"github.com/uptrace/bunrouter"
)

func main() {
	router := bunrouter.New()

	router.GET("/campaigns", func(w http.ResponseWriter, req bunrouter.Request) error {
		return nil
	})

	router.GET("/causes", func(w http.ResponseWriter, req bunrouter.Request) error {
		return nil
	})

	http.ListenAndServe(":3000", router)
}

When I try http://localhost:3000/c or http://localhost:3000/cam, I get a 404 error, normal so far. But for some reason, the router panics with http://localhost:3000/ca, same for http://localhost:3000.

Is it intended?

Mutual TLS with bun router

💡 Adding Mutual TLS to router.

📄 In microservices environment we are communicating with Mutual TLS between two services. And this is right now a cumbersome process doing it with http server. If there is a better way to do this with bun router that would be great.

🚀 A way to do Mutual TLS

    // Create a CA certificate pool and add cert.pem to it
    //root_ca.crt  server.crt  server.key
    caCert, err := ioutil.ReadFile(cfg.RootCert)
    if err != nil {
            log.Fatal(err)
    }
    caCertPool := x509.NewCertPool()
    caCertPool.AppendCertsFromPEM(caCert)

    // Create the TLS Config with the CA pool and enable Client certificate validation
    tlsConfig := &tls.Config{
            ClientCAs:  caCertPool,
            ClientAuth: tls.RequireAndVerifyClientCert,
    }
    tlsConfig.BuildNameToCertificate()

    // Create a Server instance to listen on port 8443 with the TLS config
    server := &http.Server{
            Addr:      ":8443",
            TLSConfig: tlsConfig,
    }

    // Listen to HTTPS connections with the server certificate and wait
    log.Fatal(server.ListenAndServeTLS(cfg.MyCert, cfg.MyKey))

How this can be done with bun router

SERIOUS, issues with CORS and OPTIONS calls

Coming from go-chi I was using this code:

func main() {
	r := chi.NewRouter()

	r.Use(newCorsMiddleware())

	r.Get("/", myHandler)
	r.Post("/", myHandler)

	http.ListenAndServe(":3000", r)
}

func newCorsMiddleware(corsOriginList []string) func(h http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return cors.New(cors.Options{
			AllowedOrigins:   corsOriginList,
			AllowCredentials: true,
		}).Handler(next)
	}
}

When I use it from browser (with a fetch method in javascript) and an OPTIONS call starts it works, I get 204 No Content and all the right Access-Control-Allow-..... correctly.

With the same code but bunrouter instead of go-chi things are different:

func main() {
	r := bunrouter.New(bunrouter.WithMiddleware(newCorsMiddleware(corsOriginList)))

	router.NewGroup("/",
		bunrouter.WithGroup(func(group *bunrouter.Group) {
			group.GET("", myHandler)
			group.POST("", myHandler)
			//group.OPTIONS("", myHandler)
		}),
	)

	http.ListenAndServe(":3000", r)
}

func newCorsMiddleware(corsOriginList []string) func(h http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		return cors.New(cors.Options{
			AllowedOrigins:   corsOriginList,
			AllowCredentials: true,
		}).Handler(next)
	}
}

During the OPTIONS call I get the 405 Method Not Allowed but all the right Access-Control-Allow-..... correctly.

In go-chi I don't need to use router.Options() because this is a problem that https://github.com/rs/cors handles, not me.

I think something is broken with bunrouter in this case, infact if I add a group.OPTIONS("", myHandler) the call works, but obviously this is wrong because I'm not directly handling OPTIONS calls.

Ability To Append or Remove Middleware to an Existing Router Group During Runtime

💡 The feature or bug you are proposing

Feature: Ability to append or remove a middleware to/from an existing group

📄 The description of the bug or the logic behind your proposal

Cannot append or remove a middleware to/from an already existing group

🚀 The expected result

Maybe an .addMiddleware(...) and a .removeMiddleware(...) function to append or remove a new middleware to/from an existing route.

Issue with trailing slash redirect

BunRouter is redirecting a POST request without a trailing to the GET Handler

I have the following code
image

If i send a POST request to /accounts/ then it works perfectly fine
But if send a POST request to /accounts then it redirects to GET /accounts
I have attached some screenshots below
As it can be seen that it works fine if i have a trailing slash
image

but if I remove the trailing slash I get this
image

Notice how I get method not allowed that is because I am getting redirected to a GET handler for /accounts/ which doesn't exists
image

POST Request to /accounts should get redirected to POST handler of /accounts/ instead of GET handler of /accounts

P.S: I am using bunrouter v1.0.20

splice isn't used when serving files

💡 The feature or bug you are proposing

splice isn't used when serving static files.

📄 The description of the bug or the logic behind your proposal

I have a special use case where I need to serve files from a local http server. Golang has a special behavior when the destination writer is a https://pkg.go.dev/net#TCPConn.ReadFrom and your source is a "splice-able" reader, like a file or a socket on a Linux host.

Steps to reproduce:

  1. Download a large enough file (few hundreds MiB) and store it in /home/dev/Downloads
  2. Run a bunrouter app serving static files on port 8083
package main

import (
	"flag"
	"log"
	"net/http"

	"github.com/uptrace/bunrouter"
	"github.com/uptrace/bunrouter/extra/reqlog"
)

var (
	dir = flag.String("dir", "/home/dev/Downloads", "file directory")
)

func main() {
	flag.Parse()

	router := bunrouter.New(
		bunrouter.Use(reqlog.NewMiddleware(reqlog.WithVerbose(false))),
	)

	fileServer := http.FileServer(http.Dir(*dir))
	fileServer = http.StripPrefix("/static", fileServer)

	router.GET("/static/*path", bunrouter.HTTPHandler(fileServer))

	log.Println("listening on http://localhost:8083 - serving ", *dir)
	log.Println(http.ListenAndServe(":8083", router))
}
  1. Run a net/http app running on port 8082
package main

import (
	"flag"
	"log"
	"net/http"
)

var (
	dir = flag.String("dir", "/home/dev/Downloads", "file directory")
)

func main() {
	flag.Parse()

	fs := http.FileServer(http.Dir(*dir))
	http.Handle("/static/", http.StripPrefix("/static", fs))

	log.Println("listening on http://localhost:8082 - serving ", *dir)
	log.Println(http.ListenAndServe(":8082", nil))
}
  1. Use a http benchmark tool like wrk, ab or h2load
    e.g. :
  1. Results on a 16 cores host with each core at 100%:
  • bunrouter: 16GiB/s
  • net/http: 44GiB/s

The whole point of using splice is to avoid unnecessary cpu cycle.

🚀 The expected result

bunrouter uses https://pkg.go.dev/net#TCPConn.ReadFrom which allow splice to be used.

Expose path for Group

💡 The feature or bug you are proposing

#91

📄 The description of the bug or the logic behind your proposal

Should be readonly accessible

🚀 The expected result

Group has path exposed

panic: routes "/x/*path" and "/x/" have different param names for the same route

I have this code that works with github.com/uptrace/bunrouter v1.0.17 and github.com/uptrace/bunrouter/extra/reqlog v1.0.17:

reverseProxy := newReverseProxy(host)

router.WithGroup("/custom", func(group *bunrouter.Group) {
  group.GET("/*path", bunrouter.HTTPHandler(reverseProxy))
  group.POST("/", bunrouter.HTTPHandler(reverseProxy))
})

now after the update to github.com/uptrace/bunrouter v1.0.18 and github.com/uptrace/bunrouter/extra/reqlog v1.0.18 it panics with:

panic: routes "/custom/*path" and "/custom/" have different param names for the same route

Why?

What code should I use for this?

How to set params and emulate request to controller (handlerfunc)

💡 The feature or bug you are proposing

Normally I'll do :

	resp := httptest.NewRecorder()
	req := bunrouter.Request{}
	req.Request = &http.Request{}

and I will create params like this:

	params := httprouter.Params{}
	params = append(params, httprouter.Param{Key: "id", Value: "157"})

But I can't find a way to add those to the request. (this is my question)
because proviously (github.com/julienschmidt/httprouter) I used to do :

controller.GetOneRecord(resp, req, params)

but now the params are wrapped inside of the request.

Wrong middleware order when using multiple middlewares directly to the bunrouter.Router and executing the MethodNotAllowedHandler.

💡 The feature or bug you are proposing

Wrong middleware order when using multiple middlewares directly to the bunrouter.Router and executing the MethodNotAllowedHandler.

📄 The description of the bug or the logic behind your proposal

Middleware is applied twice when using multiple middlewares directly to the bunrouter.Router.

package main

import (
	"net/http"

	"github.com/uptrace/bunrouter"
)

func main() {
	router := bunrouter.New(
		bunrouter.Use(firstMiddleware),
		bunrouter.Use(secondMiddleware),
	)
	router.GET("/", indexHandler)
	http.ListenAndServe(":8080", router)
}

func firstMiddleware(next bunrouter.HandlerFunc) bunrouter.HandlerFunc {
	return func(w http.ResponseWriter, r bunrouter.Request) error {
		w.Header().Add("First", "xxxx")
		return next(w, r)
	}
}

func secondMiddleware(next bunrouter.HandlerFunc) bunrouter.HandlerFunc {
	return func(w http.ResponseWriter, r bunrouter.Request) error {
		w.Header().Add("Second", "xxxx")
		return next(w, r)
	}
}

func indexHandler(w http.ResponseWriter, r bunrouter.Request) error {
	return bunrouter.JSON(w, bunrouter.H{
		"message": "Hello, World!",
	})
}
curl -X POST -v -s http://localhost:8080 1> /dev/null
*   Trying ::1:8080...
* Connected to localhost (::1) port 8080 (#0)
> POST / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.77.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 405 Method Not Allowed
< First: xxxx
< First: xxxx
< Second: xxxx
< Second: xxxx
< Date: Tue, 22 Mar 2022 04:26:49 GMT
< Content-Length: 0
<
* Connection #0 to host localhost left intact

This is not happening when attaching the middlewares to a bunrouter.Group.

package main

import (
	"net/http"

	"github.com/uptrace/bunrouter"
)

func main() {
	router := bunrouter.New()
	api := router.NewGroup(
		"/api",
		bunrouter.Use(firstMiddleware),
		bunrouter.Use(secondMiddleware),
	)
	api.GET("/", indexHandler)
	http.ListenAndServe(":8080", router)
}

func firstMiddleware(next bunrouter.HandlerFunc) bunrouter.HandlerFunc {
	return func(w http.ResponseWriter, r bunrouter.Request) error {
		w.Header().Add("First", "xxxx")
		return next(w, r)
	}
}

func secondMiddleware(next bunrouter.HandlerFunc) bunrouter.HandlerFunc {
	return func(w http.ResponseWriter, r bunrouter.Request) error {
		w.Header().Add("Second", "xxxx")
		return next(w, r)
	}
}

func indexHandler(w http.ResponseWriter, r bunrouter.Request) error {
	return bunrouter.JSON(w, bunrouter.H{
		"message": "Hello, World!",
	})
}
curl -X POST -v -s http://localhost:8080/api/ 1> /dev/null
*   Trying ::1:8080...
* Connected to localhost (::1) port 8080 (#0)
> POST /api/ HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.77.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 405 Method Not Allowed
< First: xxxx
< Second: xxxx
< Date: Tue, 22 Mar 2022 04:42:52 GMT
< Content-Length: 0
<
* Connection #0 to host localhost left intact

serving static files gives 404

💡 The feature or bug you are proposing

I try to serve static files, but even my simplest static dir fails.

📄 The description of the bug or the logic behind your proposal

	rootDir := libFile.RootDir()
	dir := rootDir + "/website/css/"
	fileServer := http.FileServer(http.Dir(dir))
	router.GET("/css/*path", bunrouter.HTTPHandler(fileServer))

dir is :
"/Users/marcelloh/data/go-private/SailboatCompanion/website/css/"
(which is the correct location)

🚀 The expected result

not a 404 when I call /css/mycss.css

When I debug, I can see that the node /css (which is node 0) is overwritten by the node /country

trailing slash redirect documentation wrong

According to documentation, r.POST("/api/v1/test", ...) should support both path: /api/v1/test and /api/v1/test/

This is not the case, when I call /api/v1/test/, I get a 405 Method not allowed error. The redirect to /api/v1/test does not happen.

Same applies when I define route as r.POST("/api/v1/test/", ...) In this case, only /api/v1/test/ works. When calling /api/v1/test I get HTTP 405 error.

Bug: Route doesn't match if inside the url are present encoded characters

Current Bug

I tried to register an api but the bunRouter route doesn't match if inside the path are present encoded characters

registered route ->/api/backend/projects/:projectId/branches/:branchName/files/:filePath
called url -> /api/backend/projects/project-id/branches/branch-id/files/config-extension%252Fcms-backend%252FcmsProperties.json

I use the method bunrouter.New().Compat().handle() to register the route.

Version used

v1.0.10

Are there plans to add named routes (aka reversing routes)?

💡 The feature or bug you are proposing

gorilla/mux supports named routes and these are very handy for url building, reporting, etc. Are there any plans to introduce these in bunrouter?

🚀 The expected result

How this could look like:

r.Name("user").GET("/users/:id", createUserHandler)

// returns a url.URL (or simply a string)
u := r.URLTo("user", "id", "1234")

unable to upgrade reqlog.flusher to websocket websocket: response does not implement http.Hijacker using websocket

I'm trying for the first time to use websockets (github.com/gorilla/websocket) with bunrouter.

I'm using

bunRouterOptions := []bunrouter.Option{
  // some...
}

bunRouterOptions = append(bunRouterOptions, bunrouter.Use(reqlog.NewMiddleware(reqlog.WithVerbose(true))))

router.Use(gqlgenDataloadersMiddleware).WithGroup("/graphql", func(group *bunrouter.Group) {
  group.GET("", bunrouter.HTTPHandler(gqlgenHandler))
  group.POST("", bunrouter.HTTPHandler(gqlgenHandler))
})

but I'm having this error:

unable to upgrade reqlog.flusher to websocket websocket: response does not implement http.Hijacker:
http: superfluous response.WriteHeader call from github.com/uptrace/bunrouter/extra/reqlog.(*statusCodeRecorder).WriteHeader (middleware.go:134)

If I comment the append reqlog.NewMiddleware line it works.

Panic on route handler

Hi,
I'm trying to handle two endpoints: /api/internal and /api/internal/*params.

IMO these are 2 different path, so the router should not panic.

Btw, I think that you should return an error instead of panic: in my case I'm using the router as dynamic matcher, so the panic can be an unattended behaviour.

request Params not populated when handler invoked directly during testing

Using a test request generator like this, from the docs:

func NewRequest(method, target string, body io.Reader) bunrouter.Request {
	return bunrouter.NewRequest(httptest.NewRequest(method, target, body))
}

then generating a bunrouter.Request with:

testbed.NewRequest("GET", "/users/1", nil)

and expecting it to match based on the following rule:

router.GET("/users/:id", usersHandler.GetUser)

code within my GetUser handler cannot access the params when testing, i.e. passing the bunrouter.Request and an httptest.ResponseRecorder into the handler directly.

I've confirmed the Params work as expected when invoking the server from an actual client, but from what I can tell the Params map and nodes are all empty/nil when the handler is invoked directly using a test request.

For example, I've got this line at the top of my GetUser handler:

fmt.Printf("params: %#v\n", r.Params().Map())

When the handler is invoked via the router from a real client API call, it prints:

params: map[string]string{"id":"1"}

When the handler is invoked from a test suite, using a bunrouter.Request, it prints:

params: map[string]string(nil)

reqlog middleware

💡 The feature or bug you are proposing

[feature] reqlog middleware to be configurable

📄 The description of the bug or the logic behind your proposal

Are you able to make the logger configurable so it can include other parameters?

ip, path, referrer, agent, header etc etc

router.WithGroup question

💡 The feature or bug you are proposing

I have this code:

	router.WithGroup("/todo", func(group *bunrouter.Group) {
		group.GET("", mr.DebugHandler)
		group.GET("/:id", mr.DebugHandler)
		group.POST("/edit/:id", mr.DebugHandler)
	})

Which I think should be able to serve /todo/edit/9

📄 The description of the bug or the logic behind your proposal

My webserver shows me:
Page not found (/todo/edit/9) or method (GET) not allowed

🚀 The expected result

{"params":{"id":"9"},"route":"/todo/edit/:id"}

Am I doing something wrong here?

Issue with SSE server "flusher, err := w.(http.Flusher)" error

I'm trying to use the amazing https://github.com/r3labs/sse with bunrouter.

func main() {
	sseHandler := newSSEHandler()

	router := bunrouter.New(
		bunrouter.WithMiddleware(reqlog.NewMiddleware(reqlog.WithEnabled(true))),
	)

	router.NewGroup("sse",
		bunrouter.WithGroup(func(group *bunrouter.Group) {
			group.GET("", sseHandler)
		}),
	)

	// Start server here...
}

func newSSEHandler() bunrouter.HandlerFunc {
	sseServer := sse.New()

	sseServer.CreateStream("test")

	return func(w http.ResponseWriter, r bunrouter.Request) error {
		sseServer.HTTPHandler(w, r.Request)

		return nil
	}
}

But when it starts it gives:

Streaming unsupported!

which comes from here: https://github.com/r3labs/sse/blob/11986ea9f6a2aa21c3a8237090fb95d8a72ca577/http.go#L19:

func (s *Server) HTTPHandler(w http.ResponseWriter, r *http.Request) {
	flusher, err := w.(http.Flusher)
	if !err {
		http.Error(w, "Streaming unsupported!", http.StatusInternalServerError)
		return
	}

	// ...

Is this a problem with bunrouter?

How can I fix it?

How to convert existing http.Handler middleware to bunrouter one

💡 The feature or bug you are proposing

Feature

📄 The description of the bug or the logic behind your proposal

The reason for this request is that there are plenty of existing middleware code base which provide standard http.Handler and it would be useful to re-use it within bunrouter.

For example, I'm interested to use existing legacy codebase based on gorilla/mux which and its middleware and easily convert it to bunrouter middleware. I managed to figure out mux router part, but I'm struggle to understand how easily re-use existing gorilla/mux middleware functions in bunrouter. Could you please provide a working example which will not require much of the rewrite for existing middleware codebase.

🚀 The expected result

It would be extremely useful to get documentation how to convert gorilla/mux middleware to bunrouter one, e.g. how to use this code in bunrouter:

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Do stuff here
        log.Println(r.RequestURI)
        // Call the next handler, which can be another middleware in the chain, or the final handler.
        next.ServeHTTP(w, r)
    })
}

Where is the code for benchmarks?

I found these benchmarks here: https://bunrouter.uptrace.dev/guide/#benchmark.

I'm currently using go-chi and I'm terrified about these results:

BenchmarkChi_Param               	 1430458	       821.3 ns/op	     448 B/op	       3 allocs/op
BenchmarkBunrouter_Param         	51672237	        23.51 ns/op	       0 B/op	       0 allocs/op

BenchmarkChi_Param5              	 1000000	      1092 ns/op	     448 B/op	       3 allocs/op
BenchmarkBunrouter_Param5        	10738804	       115.1 ns/op	       0 B/op	       0 allocs/op

BenchmarkChi_Param20             	  710602	      1914 ns/op	     448 B/op	       3 allocs/op
BenchmarkBunrouter_Param20       	 2489767	       490.5 ns/op	       0 B/op	       0 allocs/op

BenchmarkChi_ParamWrite          	 1312530	       889.9 ns/op	     448 B/op	       3 allocs/op
BenchmarkBunrouter_ParamWrite    	17280351	        70.58 ns/op	       0 B/op	       0 allocs/op

BenchmarkChi_GithubStatic        	 1486658	       792.3 ns/op	     448 B/op	       3 allocs/op
BenchmarkBunrouter_GithubStatic  	37493840	        32.58 ns/op	       0 B/op	       0 allocs/op

BenchmarkChi_GithubParam         	 1000000	      1097 ns/op	     448 B/op	       3 allocs/op
BenchmarkBunrouter_GithubParam   	11867866	       102.0 ns/op	       0 B/op	       0 allocs/op

BenchmarkChi_GithubAll           	    5326	    221631 ns/op	   90948 B/op	     609 allocs/op
BenchmarkBunrouter_GithubAll     	   57127	     21424 ns/op	       0 B/op	       0 allocs/op

BenchmarkChi_GPlusStatic         	 1596351	       750.9 ns/op	     448 B/op	       3 allocs/op
BenchmarkBunrouter_GPlusStatic   	76150867	        14.34 ns/op	       0 B/op	       0 allocs/op

BenchmarkChi_GPlusParam          	 1354800	       860.0 ns/op	     448 B/op	       3 allocs/op
BenchmarkBunrouter_GPlusParam    	31069144	        33.65 ns/op	       0 B/op	       0 allocs/op

BenchmarkChi_GPlus2Params        	 1264284	       952.5 ns/op	     448 B/op	       3 allocs/op
BenchmarkBunrouter_GPlus2Params  	19280515	        63.94 ns/op	       0 B/op	       0 allocs/op

BenchmarkChi_GPlusAll            	   99026	     12357 ns/op	    5824 B/op	      39 allocs/op
BenchmarkBunrouter_GPlusAll      	 1556529	       771.2 ns/op	       0 B/op	       0 allocs/op

BenchmarkChi_ParseStatic         	 1540129	       755.6 ns/op	     448 B/op	       3 allocs/op
BenchmarkBunrouter_ParseStatic   	45109171	        23.51 ns/op	       0 B/op	       0 allocs/op

BenchmarkChi_ParseParam          	 1363994	       834.5 ns/op	     448 B/op	       3 allocs/op
BenchmarkBunrouter_ParseParam    	34156603	        32.62 ns/op	       0 B/op	       0 allocs/op

BenchmarkChi_Parse2Params        	 1341004	       889.0 ns/op	     448 B/op	       3 allocs/op
BenchmarkBunrouter_Parse2Params  	23576023	        50.04 ns/op	       0 B/op	       0 allocs/op

BenchmarkChi_ParseAll            	   51376	     23343 ns/op	   11648 B/op	      78 allocs/op
BenchmarkBunrouter_ParseAll      	  745998	      1522 ns/op	       0 B/op	       0 allocs/op

BenchmarkChi_StaticAll           	    9376	    141685 ns/op	   70339 B/op	     471 allocs/op
BenchmarkBunrouter_StaticAll     	   94906	     12327 ns/op	       0 B/op	       0 allocs/op

Can I ask you where is the code?

Router panics when requesting unconfigured "/"

Consider the following program:

package main

import (
	"net/http"

	"github.com/uptrace/bunrouter"
)

func main() {
	router := bunrouter.New()

	router.GET("/campaigns", func(w http.ResponseWriter, req bunrouter.Request) error {
		return nil
	})

	http.ListenAndServe(":3000", router)
}

Requesting http://localhost:3000 makes the router panic.

Is it an intended behavior?

Structured loging with reqlog

I am sorry to open issues for these questions but I believe it is also necessary for other people who will benefit by searching for keywords in the future here.

We need to have a structured log, what do you think about extending reqlog to have a JSON instead of a plain text?

Something like https://github.com/rs/zerolog?

How to: redirect

I can't find any documentation on how to do a redirect
(or it is well hidden)

Preffered / Recommended way of binding JSON objects

💡 The feature or bug you are proposing

In GIN we have ginCtx.ShouldBindWith(&model, bindingForm), so I was wondering if we can do something similar with bunrouter.

📄 The description of the bug or the logic behind your proposal

Not a bug

🚀 The expected result

Say we have a struct:

type User struct {
        ID                  int       `json:"id"`
	FullName            string    `json:"full_name"`
}

and a POST request payload:

{
  'full_name': 'some name'
}

Now after binding JSON we should have a similar resulting struct:

User {id: nil, FullName: "some name"}

Content-Type returned by JSON is not application/json when used in special middlewares

💡 The feature or bug you are proposing

Using bunrouter.JSON in handlers passed to either WithNotFoundHandler or WithMethodNotAllowedHandler results in application/text Content-Type header.

📄 The description of the bug or the logic behind your proposal

Constructing my router like this:

router := bunrouter.New(
	bunrouter.WithMiddleware(reqLogMiddleware),
	bunrouter.WithMiddleware(errorHandlerMiddleware),
	bunrouter.WithNotFoundHandler(notFoundHandler),
	bunrouter.WithMethodNotAllowedHandler(methodNotAllowedHandler),
)

Both of my notFoundHandler and methodNotAllowedHandler middlewares send a valid JSON response with:

bunrouter.JSON(w, err)

Where err is a custom HTTPError type with json marshaling tags.

The JSON marshaling does work and the response received on the client is something like:

{"code":"not_found","message":"no route found for /"}

But the Content-Type header received is text/plain; charset=utf-8.

🚀 The expected result

bunrouter.JSON should always set application/json as the Content-Type, even when used in special middlewares.

go get github.com/uptrace/bunrouter not pulling in two folders.

💡 The feature or bug you are proposing

When running go get github.com/uptrace/bunrouter the example and extra folders are not being pulled in.

I've ran the command in two separate work stations and both times extra and example folders were not included in the go get. I'm primarily trying to bring in the reqlog middleware from extra as per the documentation/getting started.

I don't have a proposed solution to this issue, but I wanted to see if there is a known reason behind this occuring. Any info would be great!

Why is this function called so many times?

Using the example CORS if I put a breakpoint on line: https://github.com/uptrace/bunrouter/blob/master/example/cors/main.go#L67 my debugger stops many many times there when I start the app.

Can I ask you why so many times?

Shorter code:

package main

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

	"github.com/rs/cors"
	"github.com/uptrace/bunrouter"
)

func main() {
	router := bunrouter.New(
		bunrouter.WithMiddleware(newCorsMiddleware([]string{"http://localhost:9999"})),
	)

	router.GET("/", func(w http.ResponseWriter, req bunrouter.Request) error {
		fmt.Println(req.Method, req.Route(), req.Params().Map())
		
		return nil
	})

	log.Println(http.ListenAndServe(":9999", router))
}

func newCorsMiddleware(allowedOrigins []string) bunrouter.MiddlewareFunc {
	return func(next bunrouter.HandlerFunc) bunrouter.HandlerFunc {
		corsHandler := cors.New(cors.Options{ // put a breakpoint on this line
			AllowedOrigins:   allowedOrigins,
			AllowCredentials: true,
		}).Handler(next)

		return bunrouter.HTTPHandler(corsHandler)
	}
}

Panic handing error

I'm trying the https://bunrouter.uptrace.dev/guide/handlers.html#panic-recovering, but I'm having this if I provoke a panic("fake") in an handler:

goroutine 23 [running]:
main.panicHandler.func1.1()
        C:/project/cmd/app/router.go:75 +0xbe
panic({0xdbbe00, 0xf23f60})
        C:/Program Files/Go/src/runtime/panic.go:1038 +0x215
main.newGqlgenPlaygroundHandler.func1({0xf32378, 0xc0000ac000}, {0xc000096100, {{0xc00009c004, 0x5}, 0xc000190980, 0x0}})
        C:/project/cmd/app/gqlgen.go:48 +0x59
main.securityHeadersMiddleware.func1({0xf32378, 0xc0000ac000}, {0xc000096100, {{0xc00009c004, 0x5}, 0xc000190980, 0x0}})
        C:/project/cmd/app/security.go:15 +0x28b
github.com/uptrace/bunrouter/extra/reqlog.(*middleware).Middleware.func1({0xf325b8, 0xc0000a6000}, {0xc000096100, {{0xc00009c004, 0x5}, 0xc000190980, 0x0}})
        C:/Users/User/go/pkg/mod/github.com/uptrace/bunrouter/extra/[email protected]/middleware.go:76 +0x151
github.com/uptrace/bunrouter.(*Router).ServeHTTP(0xc00016c420, {0xf325b8, 0xc0000a6000}, 0xc000096100)
        C:/Users/User/go/pkg/mod/github.com/uptrace/[email protected]/router.go:46 +0xaa
github.com/rs/cors.(*Cors).Handler.func1({0xf325b8, 0xc0000a6000}, 0xc000096100)
        C:/Users/User/go/pkg/mod/github.com/rs/[email protected]/cors.go:219 +0x1bd
net/http.HandlerFunc.ServeHTTP(0xe8119a, {0xf325b8, 0xc0000a6000}, 0x2a17fbee03b)
        C:/Program Files/Go/src/net/http/server.go:2046 +0x2f
main.panicHandler.func1({0xf325b8, 0xc0000a6000}, 0x10000c0002b7af0)
        C:/project/cmd/app/router.go:85 +0xc2
net/http.HandlerFunc.ServeHTTP(0x0, {0xf325b8, 0xc0000a6000}, 0x9022f5)
        C:/Program Files/Go/src/net/http/server.go:2046 +0x2f
net/http.serverHandler.ServeHTTP({0xc000088360}, {0xf325b8, 0xc0000a6000}, 0xc000096100)
        C:/Program Files/Go/src/net/http/server.go:2878 +0x43b
net/http.(*conn).serve(0xc000242820, {0xf36360, 0xc000252cf0})
        C:/Program Files/Go/src/net/http/server.go:1929 +0xb08
created by net/http.(*Server).Serve
        C:/Program Files/Go/src/net/http/server.go:3033 +0x4e8

goroutine 1 [chan receive]:
main.StartServer({{0xc000050190, 0x1, 0x1}, {0xc000053340, 0x2, 0x2}, {0xc0000212c0, 0x7}, {0xc000026cd0, 0x41}, ...}, ...)
        C:/project/cmd/app/server.go:40 +0x27e
main.main()
        C:/project/cmd/app/main.go:51 +0x2bb

goroutine 5 [select]:
database/sql.(*DB).connectionOpener(0xc00002c340, {0xf362b8, 0xc000030340})
        C:/Program Files/Go/src/database/sql/sql.go:1196 +0x93
created by database/sql.OpenDB
        C:/Program Files/Go/src/database/sql/sql.go:794 +0x188

goroutine 21 [syscall]:
os/signal.signal_recv()
        C:/Program Files/Go/src/runtime/sigqueue.go:169 +0x98
os/signal.loop()
        C:/Program Files/Go/src/os/signal/signal_unix.go:24 +0x19
created by os/signal.Notify.func1.1
        C:/Program Files/Go/src/os/signal/signal.go:151 +0x2c

goroutine 34 [IO wait]:
internal/poll.runtime_pollWait(0x2a17fbee0c8, 0x72)
        C:/Program Files/Go/src/runtime/netpoll.go:229 +0x89
internal/poll.(*pollDesc).wait(0x0, 0x0, 0x0)
        C:/Program Files/Go/src/internal/poll/fd_poll_runtime.go:84 +0x32
internal/poll.execIO(0xc00023d418, 0xea20d8)
        C:/Program Files/Go/src/internal/poll/fd_windows.go:175 +0xe5
internal/poll.(*FD).Read(0xc00023d400, {0xc000088371, 0x1, 0x1})
        C:/Program Files/Go/src/internal/poll/fd_windows.go:441 +0x25f
net.(*netFD).Read(0xc00023d400, {0xc000088371, 0xc0001355a8, 0xc000135668})
        C:/Program Files/Go/src/net/fd_posix.go:56 +0x29
net.(*conn).Read(0xc00014c928, {0xc000088371, 0xc000059fd0, 0xa1d277})
        C:/Program Files/Go/src/net/net.go:183 +0x45
net/http.(*connReader).backgroundRead(0xc000088360)
        C:/Program Files/Go/src/net/http/server.go:672 +0x3f
created by net/http.(*connReader).startBackgroundRead
        C:/Program Files/Go/src/net/http/server.go:668 +0xcf

goroutine 22 [IO wait]:
internal/poll.runtime_pollWait(0x2a17fbee1b0, 0x72)
        C:/Program Files/Go/src/runtime/netpoll.go:229 +0x89
internal/poll.(*pollDesc).wait(0xc0000802e0, 0x8ad1b4, 0x0)
        C:/Program Files/Go/src/internal/poll/fd_poll_runtime.go:84 +0x32
internal/poll.execIO(0xc00023d198, 0xc0002b5bc0)
        C:/Program Files/Go/src/internal/poll/fd_windows.go:175 +0xe5
internal/poll.(*FD).acceptOne(0xc00023d180, 0x23c, {0xc0002640f0, 0x8, 0x0}, 0x0)
        C:/Program Files/Go/src/internal/poll/fd_windows.go:810 +0x6d
internal/poll.(*FD).Accept(0xc00023d180, 0xc0002b5d98)
        C:/Program Files/Go/src/internal/poll/fd_windows.go:844 +0x1d6
net.(*netFD).accept(0xc00023d180)
        C:/Program Files/Go/src/net/fd_windows.go:139 +0x65
net.(*TCPListener).accept(0xc000118e28)
        C:/Program Files/Go/src/net/tcpsock_posix.go:140 +0x28
net.(*TCPListener).Accept(0xc000118e28)
        C:/Program Files/Go/src/net/tcpsock.go:262 +0x3d
net/http.(*Server).Serve(0xc00019a380, {0xf323d8, 0xc000118e28})
        C:/Program Files/Go/src/net/http/server.go:3001 +0x394
net/http.(*Server).ListenAndServe(0xc00019a380)
        C:/Program Files/Go/src/net/http/server.go:2930 +0x7d
main.StartServer.func1()
        C:/project/cmd/app/server.go:30 +0x38
created by main.StartServer
        C:/project/cmd/app/server.go:26 +0x272
2021/10/20 20:15:34 http: superfluous response.WriteHeader call from main.panicHandler.func1.1 (router.go:81)

Why?

This is StartServer:

func StartServer(config config.Config, handler http.Handler) {
	var srv = &http.Server{
		Addr:    ":" + config.Port,
		Handler: handler,
	}

  var err error

  if config.TLSCertFile == "" {
    err = srv.ListenAndServe()
  } else {
    err = srv.ListenAndServeTLS(config.TLSCertFile, config.TLSCertKeyFile)
  }

  if !errors.Is(err, http.ErrServerClosed) {
    log.Err(err).Msg("")
  }
}

How to pass a value to middleware?

Normally I would use middleware like this:

router.Use(middleware.MyMiddleware).GET("/demo", myDemoHandler)

But let's say, I want to check in the middleware if the user has permissions to use the handler.
Something like this:

router.Use(middleware.Permissions("RW")).POST("/demo", myDemoHandler)

How can I achieve something like this?

`unable to upgrade struct { httpsnoop.Unwrapper...` and `http: superfluous response.WriteHeader call`

Sorry to bother you again.

I updated to bunrouter and reqlog 1.0.14 and the websocket works.

But now if I reject origin it warns with:

http: superfluous response.WriteHeader call from github.com/uptrace/bunrouter/extra/reqlog.NewResponseWriter.func1.1 (middleware.go:120)

unable to upgrade struct { httpsnoop.Unwrapper; http.ResponseWriter; http.Flusher; http.CloseNotifier; http.Hijacker; io.ReaderFrom } to websocket websocket: request origin not allowed by Upgrader.CheckOrigin

the code is:

func newHandler() *handler.Server {
	gqlgenServer := handler.New(gqlgen.NewExecutableSchema(gqlgen.Config{Resolvers: &resolvers}))

	gqlgenServer.AddTransport(transport.Websocket{
		KeepAlivePingInterval: 10 * time.Second,
		Upgrader: websocket.Upgrader{
			CheckOrigin: func(r *http.Request) bool {
				return false // if true here it has no errors with 1.0.14
			},
		}},
	)

	gqlgenServer.AddTransport(transport.POST{})

	return gqlgenServer
}

gqlgenHandler := newHandler()

router.Use(authMiddleware).WithGroup("/graphql", func(group *bunrouter.Group) {
  group.POST("", bunrouter.HTTPHandler(gqlgenHandler))
})

Why?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.