Coder Social home page Coder Social logo

julienschmidt / httprouter Goto Github PK

View Code? Open in Web Editor NEW
14.9K 318.0 1.4K 269 KB

A high performance HTTP request router that scales well

Home Page: https://pkg.go.dev/github.com/julienschmidt/httprouter

License: BSD 3-Clause "New" or "Revised" License

Go 100.00%
go router http mux httprouter golang

httprouter's Introduction

HttpRouter Build Status Coverage Status Docs

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

In contrast to the default mux of Go's net/http package, this router supports variables in the routing pattern and matches against the request method. It also scales better.

The router is optimized for high performance and a small memory footprint. It scales well even with very long paths and a large number of routes. A compressing dynamic trie (radix tree) structure is used for efficient matching.

Features

Only explicit matches: With other routers, like http.ServeMux, a requested URL path could match multiple patterns. Therefore they have some awkward pattern priority rules, like longest match or first registered, first matched. By design of this router, a request can only match exactly one or no route. As a result, there are also no unintended matches, which makes it great for SEO and improves the user experience.

Stop caring about trailing slashes: Choose the URL style you like, the router automatically redirects the client if a trailing slash is missing or if there is one extra. Of course it only does so, if the new path has a handler. If you don't like it, you can turn off this behavior.

Path auto-correction: Besides detecting the missing or additional trailing slash at no extra cost, the router can also fix wrong cases and remove superfluous path elements (like ../ or //). Is CAPTAIN CAPS LOCK one of your users? HttpRouter can help him by making a case-insensitive look-up and redirecting him to the correct URL.

Parameters in your routing pattern: Stop parsing the requested URL path, just give the path segment a name and the router delivers the dynamic value to you. Because of the design of the router, path parameters are very cheap.

Zero Garbage: The matching and dispatching process generates zero bytes of garbage. The only heap allocations that are made are building the slice of the key-value pairs for path parameters, and building new context and request objects (the latter only in the standard Handler/HandlerFunc API). In the 3-argument API, if the request path contains no parameters not a single heap allocation is necessary.

Best Performance: Benchmarks speak for themselves. See below for technical details of the implementation.

No more server crashes: You can set a Panic handler to deal with panics occurring during handling a HTTP request. The router then recovers and lets the PanicHandler log what happened and deliver a nice error page.

Perfect for APIs: The router design encourages to build sensible, hierarchical RESTful APIs. Moreover it has built-in native support for OPTIONS requests and 405 Method Not Allowed replies.

Of course you can also set custom NotFound and MethodNotAllowed handlers and serve static files.

Usage

This is just a quick introduction, view the Docs for details.

Let's start with a trivial example:

package main

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

    "github.com/julienschmidt/httprouter"
)

func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
    fmt.Fprint(w, "Welcome!\n")
}

func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
    fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
}

func main() {
    router := httprouter.New()
    router.GET("/", Index)
    router.GET("/hello/:name", Hello)

    log.Fatal(http.ListenAndServe(":8080", router))
}

Named parameters

As you can see, :name is a named parameter. The values are accessible via httprouter.Params, which is just a slice of httprouter.Params. You can get the value of a parameter either by its index in the slice, or by using the ByName(name) method: :name can be retrieved by ByName("name").

When using a http.Handler (using router.Handler or http.HandlerFunc) instead of HttpRouter's handle API using a 3rd function parameter, the named parameters are stored in the request.Context. See more below under Why doesn't this work with http.Handler?.

Named parameters only match a single path segment:

Pattern: /user/:user

 /user/gordon              match
 /user/you                 match
 /user/gordon/profile      no match
 /user/                    no match

Note: Since this router has only explicit matches, you can not register static routes and parameters for the same path segment. For example you can not register the patterns /user/new and /user/:user for the same request method at the same time. The routing of different request methods is independent from each other.

Catch-All parameters

The second type are catch-all parameters and have the form *name. Like the name suggests, they match everything. Therefore they must always be at the end of the pattern:

Pattern: /src/*filepath

 /src/                     match
 /src/somefile.go          match
 /src/subdir/somefile.go   match

How does it work?

The router relies on a tree structure which makes heavy use of common prefixes, it is basically a compact prefix tree (or just Radix tree). Nodes with a common prefix also share a common parent. Here is a short example what the routing tree for the GET request method could look like:

Priority   Path             Handle
9          \                *<1>
3          ├s               nil
2          |├earch\         *<2>
1          |└upport\        *<3>
2          ├blog\           *<4>
1          |    └:post      nil
1          |         └\     *<5>
2          ├about-us\       *<6>
1          |        └team\  *<7>
1          └contact\        *<8>

Every *<num> represents the memory address of a handler function (a pointer). If you follow a path trough the tree from the root to the leaf, you get the complete route path, e.g \blog\:post\, where :post is just a placeholder (parameter) for an actual post name. Unlike hash-maps, a tree structure also allows us to use dynamic parts like the :post parameter, since we actually match against the routing patterns instead of just comparing hashes. As benchmarks show, this works very well and efficient.

Since URL paths have a hierarchical structure and make use only of a limited set of characters (byte values), it is very likely that there are a lot of common prefixes. This allows us to easily reduce the routing into ever smaller problems. Moreover the router manages a separate tree for every request method. For one thing it is more space efficient than holding a method->handle map in every single node, it also allows us to greatly reduce the routing problem before even starting the look-up in the prefix-tree.

For even better scalability, the child nodes on each tree level are ordered by priority, where the priority is just the number of handles registered in sub nodes (children, grandchildren, and so on..). This helps in two ways:

  1. Nodes which are part of the most routing paths are evaluated first. This helps to make as much routes as possible to be reachable as fast as possible.
  2. It is some sort of cost compensation. The longest reachable path (highest cost) can always be evaluated first. The following scheme visualizes the tree structure. Nodes are evaluated from top to bottom and from left to right.
├------------
├---------
├-----
├----
├--
├--
└-

Why doesn't this work with http.Handler?

It does! The router itself implements the http.Handler interface. Moreover the router provides convenient adapters for http.Handlers and http.HandlerFuncs which allows them to be used as a httprouter.Handle when registering a route.

Named parameters can be accessed request.Context:

func Hello(w http.ResponseWriter, r *http.Request) {
    params := httprouter.ParamsFromContext(r.Context())

    fmt.Fprintf(w, "hello, %s!\n", params.ByName("name"))
}

Alternatively, one can also use params := r.Context().Value(httprouter.ParamsKey) instead of the helper function.

Just try it out for yourself, the usage of HttpRouter is very straightforward. The package is compact and minimalistic, but also probably one of the easiest routers to set up.

Automatic OPTIONS responses and CORS

One might wish to modify automatic responses to OPTIONS requests, e.g. to support CORS preflight requests or to set other headers. This can be achieved using the Router.GlobalOPTIONS handler:

router.GlobalOPTIONS = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    if r.Header.Get("Access-Control-Request-Method") != "" {
        // Set CORS headers
        header := w.Header()
        header.Set("Access-Control-Allow-Methods", header.Get("Allow"))
        header.Set("Access-Control-Allow-Origin", "*")
    }

    // Adjust status code to 204
    w.WriteHeader(http.StatusNoContent)
})

Where can I find Middleware X?

This package just provides a very efficient request router with a few extra features. The router is just a http.Handler, you can chain any http.Handler compatible middleware before the router, for example the Gorilla handlers. Or you could just write your own, it's very easy!

Alternatively, you could try a web framework based on HttpRouter.

Multi-domain / Sub-domains

Here is a quick example: Does your server serve multiple domains / hosts? You want to use sub-domains? Define a router per host!

// We need an object that implements the http.Handler interface.
// Therefore we need a type for which we implement the ServeHTTP method.
// We just use a map here, in which we map host names (with port) to http.Handlers
type HostSwitch map[string]http.Handler

// Implement the ServeHTTP method on our new type
func (hs HostSwitch) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	// Check if a http.Handler is registered for the given host.
	// If yes, use it to handle the request.
	if handler := hs[r.Host]; handler != nil {
		handler.ServeHTTP(w, r)
	} else {
		// Handle host names for which no handler is registered
		http.Error(w, "Forbidden", 403) // Or Redirect?
	}
}

func main() {
	// Initialize a router as usual
	router := httprouter.New()
	router.GET("/", Index)
	router.GET("/hello/:name", Hello)

	// Make a new HostSwitch and insert the router (our http handler)
	// for example.com and port 12345
	hs := make(HostSwitch)
	hs["example.com:12345"] = router

	// Use the HostSwitch to listen and serve on port 12345
	log.Fatal(http.ListenAndServe(":12345", hs))
}

Basic Authentication

Another quick example: Basic Authentication (RFC 2617) for handles:

package main

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

	"github.com/julienschmidt/httprouter"
)

func BasicAuth(h httprouter.Handle, requiredUser, requiredPassword string) httprouter.Handle {
	return func(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
		// Get the Basic Authentication credentials
		user, password, hasAuth := r.BasicAuth()

		if hasAuth && user == requiredUser && password == requiredPassword {
			// Delegate request to the given handle
			h(w, r, ps)
		} else {
			// Request Basic Authentication otherwise
			w.Header().Set("WWW-Authenticate", "Basic realm=Restricted")
			http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
		}
	}
}

func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	fmt.Fprint(w, "Not protected!\n")
}

func Protected(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	fmt.Fprint(w, "Protected!\n")
}

func main() {
	user := "gordon"
	pass := "secret!"

	router := httprouter.New()
	router.GET("/", Index)
	router.GET("/protected/", BasicAuth(Protected, user, pass))

	log.Fatal(http.ListenAndServe(":8080", router))
}

Chaining with the NotFound handler

NOTE: It might be required to set Router.HandleMethodNotAllowed to false to avoid problems.

You can use another http.Handler, for example another router, to handle requests which could not be matched by this router by using the Router.NotFound handler. This allows chaining.

Static files

The NotFound handler can for example be used to serve static files from the root path / (like an index.html file along with other assets):

// Serve static files from the ./public directory
router.NotFound = http.FileServer(http.Dir("public"))

But this approach sidesteps the strict core rules of this router to avoid routing problems. A cleaner approach is to use a distinct sub-path for serving files, like /static/*filepath or /files/*filepath.

Web Frameworks based on HttpRouter

If the HttpRouter is a bit too minimalistic for you, you might try one of the following more high-level 3rd-party web frameworks building upon the HttpRouter package:

  • Ace: Blazing fast Go Web Framework
  • api2go: A JSON API Implementation for Go
  • Gin: Features a martini-like API with much better performance
  • Goat: A minimalistic REST API server in Go
  • goMiddlewareChain: An express.js-like-middleware-chain
  • Hikaru: Supports standalone and Google AppEngine
  • Hitch: Hitch ties httprouter, httpcontext, and middleware up in a bow
  • httpway: Simple middleware extension with context for httprouter and a server with gracefully shutdown support
  • kami: A tiny web framework using x/net/context
  • Medeina: Inspired by Ruby's Roda and Cuba
  • Neko: A lightweight web application framework for Golang
  • pbgo: pbgo is a mini RPC/REST framework based on Protobuf
  • River: River is a simple and lightweight REST server
  • siesta: Composable HTTP handlers with contexts
  • xmux: xmux is a httprouter fork on top of xhandler (net/context aware)

httprouter's People

Contributors

abiosoft avatar comradin avatar corneldamian avatar da-z avatar dahankzter avatar darrenscerri avatar dlsniper avatar donutloop avatar edwardbetts avatar farmergreg avatar fov6363 avatar fredwangwang avatar georgemac avatar grantstephens avatar javierprovecho avatar jeremyloy avatar jjeffery avatar josemukorivo avatar julienschmidt avatar karlpokus avatar lhigueragamboa avatar mhor avatar peterldowns avatar philippfranke avatar preetam avatar razonyang avatar readmecritic avatar rogpeppe avatar stamblerre avatar techmexdev 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  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

httprouter's Issues

panic: wildcard route conflicts with existing children

Hi,
I could not understand why the error is happening, since I'm not using wildcard routes. I think this could be a bug.

package main

import (
    "fmt"
    `github.com/julienschmidt/httprouter`
    "log"
    "net/http"
)

func main() {
    router := httprouter.New()
    router.GET(`/assets/:type/:asset`, Debug)
    router.GET(`/:year/:month/:day/:slug`, Debug)
    router.GET(`/`, Index)

    log.Fatal(http.ListenAndServe(`:8080`, router))
}

func Index(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
    fmt.Fprint(rw, "Home page with summary of blog posts\n")
}

func Debug(rw http.ResponseWriter, req *http.Request, params httprouter.Params) {
    fmt.Fprintf(rw, "debug, %s\n", params)
}

Here is the relevant part of the dump:

goroutine 16 [running]:
runtime.panic(0x5f05c0, 0xc208000740)
    /usr/local/go/src/pkg/runtime/panic.c:279 +0xf5
github.com/julienschmidt/httprouter.(*node).insertChild(0xc208004180, 0xc208000704, 0x6c9811, 0x17, 0x715b78)
    .../Go/src/github.com/julienschmidt/httprouter/tree.go:201 +0x11e
github.com/julienschmidt/httprouter.(*node).addRoute(0xc208004180, 0x6c9811, 0x17, 0x715b78)
    .../Go/src/github.com/julienschmidt/httprouter/tree.go:172 +0x952
github.com/julienschmidt/httprouter.(*Router).Handle(0xc20800e4e0, 0x696fd0, 0x3, 0x6c9810, 0x18, 0x715b78)
    .../Go/src/github.com/julienschmidt/httprouter/router.go:205 +0x186
github.com/julienschmidt/httprouter.(*Router).GET(0xc20800e4e0, 0x6c9810, 0x18, 0x715b78)
    .../Go/src/github.com/julienschmidt/httprouter/router.go:159 +0x59
main.main()
    .../Go/src/.../main.go:13 +0xb5

panic: conflict with wildcard route

When I use these 2 distinct routes, I get a panic. Seems to me like this is a bug? Am I missing something?

router.GET("/api/v1/:user_id/buoys/:id/show/", Hello)
router.GET("/api/v1/:user_id/buoys/:name/search/", Hello)

Sample program below:

package main

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

    "github.com/OutCast-IO/httprouter"
)

func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
    fmt.Fprint(w, "Welcome!\n")
}

func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
    fmt.Fprintf(w, "hello")
}

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

    router.GET("/api/v1/:user_id/buoys/:id/show/", Hello)
    router.GET("/api/v1/:user_id/buoys/:name/search/", Hello)

    log.Fatal(http.ListenAndServe(":8080", router))
}

Serve Static Files from Root

I tried searching the documentation for an explaination on how to server static content from "/". In my application design all of the API uses /api/* all paths outside the api folder are considered static content.

Is there a current way of handling this in httprouter?

Panic on Handle /:foo:bar

The following program panics with "slice bounds out of range" error. This
case should be caught and a more understandable message issued instead.

package main

import (
    "github.com/julienschmidt/httprouter"
    "net/http"
)

func main() {
    r := httprouter.New()
    r.Handle("GET", "/:foo:bar/oops", func(http.ResponseWriter, *http.Request, httprouter.Params) {})
}

The panic is as follows:

panic: runtime error: slice bounds out of range

goroutine 1 [running]:
github.com/julienschmidt/httprouter.(*node).insertChild(0xc20805c120, 0x1, 0x6bd190, 0xe, 0x72ea70)
    /home/rog/src/go/src/github.com/julienschmidt/httprouter/tree.go:217 +0x57a
github.com/julienschmidt/httprouter.(*node).addRoute(0xc20805c060, 0x6bd190, 0xe, 0x72ea70)
    /home/rog/src/go/src/github.com/julienschmidt/httprouter/tree.go:184 +0xac
github.com/julienschmidt/httprouter.(*Router).Handle(0xc208030f70, 0x6af1d0, 0x3, 0x6bd190, 0xe, 0x72ea70)
    /home/rog/src/go/src/github.com/julienschmidt/httprouter/router.go:219 +0x1ae
main.main()
    /home/rog/src/tst.go:10 +0x9a

Question on Params

Hi @julienschmidt

Not so much an issue, as much as a question.

Just wondering why Param is a struct instead of a map.
Wouldn't it be more efficient to say if value, ok := ps["somekey"]; ok { foo(value) } ?

Since Param is a struct ByName(name string) looks like it iterates through the array of Params until a string match its found. I totally get that ps[0] is slightly more performant, but wouldn't a map be more useful for string matching? Maybe not?

Just curious what influenced the data structure choice for Param! Im sure there is a good reason!

Thanks again for this router!!!

Single page app with public asset directory

I'm having difficulty implementing a wildcard route that handles all paths except the assets directory.

r.Static("/public" , "./public")

r.GET("/*_", func(c *gin.Context) {...})

I'm getting 'panic: conflict with wildcard route'

How can this be handled with httprouter?

Routes, named params and case sensitivity

Am I correct in assuming that the design that allows for quick route matching also restricts the routes themselves to being case sensitive?

e.g. For the given route with a named paramter

func Hello(w http.ResponseWriter, r *http.Request, vars map[string]string) {
    fmt.Fprintf(w, "Hello, %s!\n", vars["name"])
}
func main() {
    router := httprouter.New()
    router.GET("/hello/:name", Hello)
    log.Fatal(http.ListenAndServe(":12345", router))
}

Is there any way to always return Hello Joe for the following requests?

/hello/Joe
/HELLO/Joe
/hElLo/Joe

i.e. The desire is to have the route be case insensitive with the values of named parameters retaining their casing.

Parse query parameter /hello?name=username

I am not sure email for help is mentioned or there is a mailing list for this project.

How do parse query parameter? Like I have /hello?name=abhijit the for this pattern I should get "abhijit" when I read from ps.ByName("name"), however unable to read

index out of range

Hi,

I am trying to use httprouter for part of my routes. An example is

package main

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

    "github.com/julienschmidt/httprouter"
)

func Index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
    fmt.Fprint(w, "Welcome!\n")
}

func Hello(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
    fmt.Fprintf(w, "hello, %s!\n", ps.ByName("name"))
}

func main() {
    router := httprouter.New()
    //router.GET("/", Index)
    router.GET("/api/hello/:name", Hello)

    http.Handle("/api/", router)

    log.Fatal(http.ListenAndServe(":8080", nil))
}

this works, but crashes when given a url like

http://localhost:8080/api/world/abc

stack trace is

2014/06/11 09:30:09 http: panic serving [::1]:51474: runtime error: index out of range
goroutine 3 [running]:
net/http.func·009()
    /Users/bsr/Work/ttt/repo/go/src/pkg/net/http/server.go:1093 +0xae
runtime.panic(0x2066c0, 0x497457)
    /Users/bsr/Work/ttt/repo/go/src/pkg/runtime/panic.c:248 +0x106
github.com/julienschmidt/httprouter.(*node).getValue(0xc2100380c0, 0xc210048504, 0xe, 0x0, 0x0, ...)
    /Users/bsr/Work/ttt/src/github.com/julienschmidt/httprouter/tree.go:409 +0x3f2
github.com/julienschmidt/httprouter.(*Router).ServeHTTP(0xc2100484a0, 0x584500, 0xc21000f460, 0xc2100581a0)
    /Users/bsr/Work/ttt/src/github.com/julienschmidt/httprouter/router.go:266 +0xf9
net/http.(*ServeMux).ServeHTTP(0xc21001e5d0, 0x584500, 0xc21000f460, 0xc2100581a0)
    /Users/bsr/Work/ttt/repo/go/src/pkg/net/http/server.go:1496 +0x163
net/http.serverHandler.ServeHTTP(0xc21001f410, 0x584500, 0xc21000f460, 0xc2100581a0)

Seeing "panic: wildcard route conflicts with existing children" unexpectedly

When trying to add simple routes that should not overlap, I get wildcard route conflicts.

Simple crafted example:

package main

import (
    "fmt"
    "net/http"
    "github.com/julienschmidt/httprouter"
)

func Index(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Welcome!\n")
}

func main() {
    router := httprouter.New()
    router.Handler("HEAD", "/:some/:thing", http.HandlerFunc(Index))
    router.Handler("GET", "/:some/:thing", http.HandlerFunc(Index))
    router.Handler("GET", "/otherthing", http.HandlerFunc(Index))
    http.Handle("/", router)
}
$ go run test.go

goroutine 1 [running]:
runtime.panic(0x1afa20, 0xc21000a600)
    /go/1.2.2/libexec/src/pkg/runtime/panic.c:266 +0xb6
github.com/julienschmidt/httprouter.(*node).insertChild(0xc2100390c0, 0xc210000102, 0x254591, 0xc, 0xc21000a5f0)
    /build/src/github.com/julienschmidt/httprouter/tree.go:201 +0x148
github.com/julienschmidt/httprouter.(*node).addRoute(0xc2100390c0, 0x254591, 0xc, 0xc21000a5f0)
    /build/src/github.com/julienschmidt/httprouter/tree.go:172 +0x8d5
github.com/julienschmidt/httprouter.(*Router).Handle(0xc2100484a0, 0x247fe0, 0x3, 0x254590, 0xd, ...)
    /build/src/github.com/julienschmidt/httprouter/router.go:205 +0x15a
github.com/julienschmidt/httprouter.(*Router).Handler(0xc2100484a0, 0x247fe0, 0x3, 0x254590, 0xd, ...)
    /build/src/github.com/julienschmidt/httprouter/router.go:215 +0x9f
main.main()
    /some/user/path/test.go:18 +0x16d
exit status 2

In the sample code the routes that appear to conflict are /:some/:thing and /otherthing. These should not really be conflicting though, as one requires two path components, and the other is a fixed route with no wildcards. Kind of odd. Is this a bug or intended behavior?

panic error

Hi, I m serving a single page application from /

http.Handle("/", http.FileServer(http.Dir("./web_root")))
http.HandleFunc("/order_create", handlerOrderCreate)
http.HandleFunc("/order_show", handlerOrderShow)

--> works fine

router.ServeFiles("/*filepath", http.Dir("./web_root"))
router.GET( "/orders/:id", handlerOrderShow)
router.POST("/orders", handlerOrderCreate)

--> panic: conflict with wildcard route

goroutine 16 [running]:
runtime.panic(0x293240, 0xc208000980)
/usr/local/go/src/pkg/runtime/panic.c:279 +0xf5
github.com/julienschmidt/httprouter.(*node).addRoute(0xc2080042a0, 0x392f70, 0xb, 0x433330)
/Users/sallas/go/src/github.com/julienschmidt/httprouter/tree.go:141 +0x594

What could I do to make it work in /?

Discussion: 405 Support breaks wildcard routes

As reported by @ydnar in #51, the 405 Support introduced in #51 breaks wildcard routes:

This completely breaks wildcard routes for alternate methods, for example:

r.Handle("GET", "/v1/search", search)
r.Handle("OPTIONS", "/*path", handler)
GET requests for /v1/search will always return a 405 with this change.

Flexible Handle Type

What do you think about making the Lookup method (or a similar one) more generic in terms of not enforcing a specific function footprint?

Currently, when building something on top of httprouter, each handler has to be a func(w http.ResponseWriter, r *http.Request, _ httprouter.Params). It would be nice if the Lookup method (or a similar one) returns an interface{}, while httprouter itself still enforces the httprouter.Handle type for its own public API. This would allow using custom handle types for modules build on top of httprouter.

Add support for HEAD requests

HEAD requests are only occasionally needed, but if your API omits them that causes a problem. It should be easy to add HEAD support.

Route Structure

I am a little confused, how do you implement basic route of the form "/:title" without conflicting with "/"

Byte representation of /

In

if path[len(path)-1] == '/' {
, there is the following line:
if path[len(path)-1] == '/' {

Variably, "/" and '/' are used, seemingly interchangeably. The former represents a string, the latter represents a rune literal. Is the variable representation intentional? It seems like it could lead to bugs down the line.

ETIMEDOUT

I am running into sudden increase of connection timeouts after switching from gorilla/mux to httprouter.
My briefly skimming of the source code didn't reveal anything and it seemed unintuitive to me that changing routing package could lead to more timeouts.

Test client, written in node.js, makes 30 requests in parallel, 4000 in total.
Using gorilla/mux, there were zero ETIMEDOUT errors.
Using httprouter, client ran into ~10 ETIMEDOUT errors.

Are there any differences you are aware of in how your package handles connections vs how Gorilla does it?

Wildcard routing

Hello,

I am porting an application where all traffic gets sent to the same index.html, regardless of path except the API endpoint. AngularJS is responsible for building the page. Go will work as the API that will tell Angular how the page is served.

http://mongular.org/

Essentially I need all traffic aside from anything under the 'foo.com/mongolar' (this is configurable) to be routed to one index.html.

I am having problem with the wildcard route. Is this doable with your router?

I am also serving some js and css as static files from their own folders.

Feature request: chainable router

Say I made a blog app with httprouter. It would be nice if my users could run it under an url-prefix like /blog easily, without having to plug all the cords in the right places.

This is super easy to solve; my blogapp package exports an httprouter.Router-value with all its routes already configured correctly and the users just chain it to their router.

Something like this:

import "blogapp"

// ...

router.Chain("/blog", blogapp.Router)

After that line, any request that matches the prefix /blog will be routed by blogapp.Router.

Low complexity, great value.

Wildcard HTTP method support

It seems there's currently no way to handle all methods with a handler. Would it be possible to add something like support for specifying * (or nil) as the method, and have that be called if an explicit method is not provided?

In my particular case, I need to process chunks of a URL regardless of the method and pass the request on to a proxy.

OPTIONS

I apologize if I am missing something obvious; How do I define a route for OPTIONS requests?

Q: sub-domains support?

Hi!
Thank you for your job! It's great!
Could you please approach me, how sub-domains could be supported?
Thanks

Cannot find package

I am new to go. I copy your example from usage and build it. I got error.

c:\Apps\Google\Projects\myGoAppWebstorm\src>go build myGoAppWebstorm.go
myGoAppWebstorm.go:5:5: cannot find package "github.com/julienschmidt/httprouter
" in any of:
C:\Go\src\pkg\github.com\julienschmidt\httprouter (from $GOROOT)
C:\Apps\Google\Projects\src\github.com\julienschmidt\httprouter (from $G
OPATH)

What am I doing wrong?

Disable directory listing with ServeFiles

Hi!,
When I use this ...

router.ServeFiles("/static/*filepath", http.Dir("/var/www/static"))

It works, but this show the directories, there are any way for disable the directory listing? (without insert a empty index.html in every dir)

Add roles and permissions for authorization in routes

Hi!
Would be good idea add roles and permissions at routes? In this moment, i have a project, where i need put roles and permissions (like as rbac or acl). In beginning, i thought put this in each view where pointing every route, but, i think that it is better add roles and permissions necessary in each route, and do the checks of permissions in the wrapper httpHandler function.

I don't know if this affect to performance..

P.D.: sorry for my english.

HTTP 405 Method Not Allowed

When a path matches but the method doesn't, I'd like to respond with a custom MethodNotAllowed handler. Is this something you'd consider for this library?

remove a handler

hi,
Is there any way to remove a registered handler? for example:

router.Remove("GET", "/hello/:name")

Thanks.

Google App Engine (GAE)

Hello,

how can I use this together with Google App Engine?
Here is a working example that uses the net/http library:

package main

import (
    "fmt"
    "net/http"
)

func init() {
    http.HandleFunc("/", index)
}

func index(w http.ResponseWriter, r *http.Request) {
    fmt.Fprint(w, "Hello, world!")
}

I tried to do the same with httprouter:

package main

import (
    "fmt"
    "net/http"
    "github.com/julienschmidt/httprouter"
)

func init() {
    router := httprouter.New()
    router.GET("/", index)
}

func index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
    fmt.Fprint(w, "Hello, world!")
}

But it doesnt work. I always get 404 page not found.

discussion: swagger support

Hi Julien,

I just set up a project called Swugger to show how swagger support might be acheived for httprouter. It's working, and it uses go-restful's swagger support to document some 'dummy' go-restful routes while setting up real routes in httprouter. I'd like to actually just fork go-restful and strip it back into a swagger provider for httprouter (and others), but before I do that I'd like to check in with you first.

So, I have 2 questions:

Firstly, have you considered Swagger support already? Or any other form of service description feature? Are you already working on something like this for httprouter? For example, it might be nice to optionally represent routing documentation inside httprouter's node, but then on the other hand, it would add baggage to a small and tidy framework.

Secondly, if you're interested at all, could you take a look at swugger (see the readme), and see if it's an interesting idea to you. If you have any feelings about how you might do things differently, I'd love to know.

Feedback aside I'll just go ahead and make a less 'ugger' version of swugger (forking and stripping back go-restful back towards swagger support only. I may also change it to use a less fluent-interface style of library - just adding a RouteDoc{} struct to each route definition might feel more Go-ish to me).

Anyway, there it is, thanks for the awesome work on making a fast router.

Wild card with different names causes panic

If I have two routes like

/client/:id
/client/:client_id/resource

I get a panic

panic: conflict with wildcard route

Is this a feature? I was just not expecting having different wildcard names to cause an issue.

Also the panic message does not provide much of a hint of which routes are causing the issue.

Thanks.

Get parameters through standard http.Handler

Hello,

you write yourself that there is no efficient way to retrieve parameters without providing a third parameter.

I thought of four possible ways which we see partly in other routers:

  1. Save parameter in request.URL.Query() pat
  2. Save match with request and resolve it extra gorilla/mux
  3. Rerun route matcher on url to extract parameter.

The third one would require another extraction of the route parameter but as it is optional and does not require any extra cache layer I think it could be still efficient (if this is your main concern).

Best,
Bo

Laravel style Routing

Julien,

Can you give me a hint on what me be changed so that the route parameters are enclosed in {} and question marks can be used to make it optional just like in Laravel.

http://laravel.com/docs/4.2/routing#route-parameters

Route::get('user/{id}', function($id)
{
return 'User '.$id;
});

Optional Route Parameters

Route::get('user/{name?}', function($name = null)
{
return $name;
});

Optional Route Parameters With Defaults

Route::get('user/{name?}', function($name = 'John')
{
return $name;
});

Regular Expression Route Constraints

Route::get('user/{name}', function($name)
{
//
})
->where('name', '[A-Za-z]+');

Route::get('user/{id}', function($id)
{
//
})
->where('id', '[0-9]+');

undefined: catchPanic

I'm not sure what catchPanic is, but it is undefined with testing I have for this package.

405 Method Not Allowed

Hi Julien

The router currently returns 404 Not Found even if the path (and thus the resource) exists but the request method does not match. In such a situation, to be a good HTTP citizen, it should return 405 Method Not Allowed and indicate the available methods for the desired resource in the "Allow" header.

I've looked into your router implementation. The way the routes are currently organized, doing this wouldn't be that easy (and fast) because you have a separate tree for each request method. I think you should have one tree instead and store the defined methods along with their handlers in the node structure.

To customize the server response, in addition to the NotFound handler to be found in the Router structure, there should also be a MethodNotAllowed handler. It should be passed a list of allowed methods.

Regards,
Claudio

panic: runtime error: index out of range (net/http.Request.URL.Path == '')

Panic:

http: panic serving 127.0.0.1:33049: runtime error: index out of range
        /home/me/goworkspace/src/github.com/julienschmidt/httprouter/router.go:287 +0x4a0

Offending line:

if path[len(path)-1] == '/' {

Link to source: https://github.com/julienschmidt/httprouter/blob/.../router.go#L287

Program to run:

package main

import (
    "fmt"
    "net/http"
    "github.com/julienschmidt/httprouter"
)

func main() {
  r := httprouter.New()
    r.HandlerFunc("GET", "/", func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprint(w, "Hello from")
    })

    fmt.Println(http.ListenAndServe(":8080", r))
}

Command to run, to cause panic:

echo -e 'GET http://www.example.com HTTP/1.1\r\n' | ncat localhost 8080

Reasoning:

URL Params

This is more of general question than a real issue, I am fairly new to go and I am trying to convert some of our ruby projects over to go using httprouter. Is there a way to have httproute handle params in the url? something like http://example.com/generate?user=12&group=mygroup I have tried to set a general generate route and read the params, but I keep getting params as empty

Routes with name

Hello!
I would like know if is possible put a name to routes like as:

router := httprouter.New()
    router.GET("/hello/:name", Hello).Name("hello")
    ....
    routeHello := router.GetRoute("hello").Params("name", "Emilio")

If this is not possible, Do you plan add this feature?

P.D.: Very nice perfomance 👍

Best,
Emilio

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.