diff --git a/LICENSE b/LICENSE index a9eea1e..71feac8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ Copyright (c) 2013 Julien Schmidt Copyright (c) 2015-2016, 招牌疯子 Copyright (c) 2018-present Sergio Andres Virviescas Santana, fasthttp +Copyright (c) 2023-present pedia All rights reserved. diff --git a/README.md b/README.md index de5a60e..21b1047 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,16 @@ # Router [![Test status](https://github.com/pedia/router/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/pedia/router/actions?workflow=test) -[![Coverage Status](https://coveralls.io/repos/fasthttp/router/badge.svg?branch=master&service=github)](https://coveralls.io/github/fasthttp/router?branch=master) +[![Coverage Status](https://coveralls.io/repos/pedia/router/badge.svg?branch=master&service=github)](https://coveralls.io/github/pedia/router?branch=master) [![Go Report Card](https://goreportcard.com/badge/github.com/pedia/router)](https://goreportcard.com/report/github.com/pedia/router) [![GoDev](https://img.shields.io/badge/go.dev-reference-007d9c?logo=go&logoColor=white)](https://pkg.go.dev/github.com/pedia/router) -[![GitHub release](https://img.shields.io/github/release/fasthttp/router.svg)](https://github.com/pedia/router/releases) +[![GitHub release](https://img.shields.io/github/release/pedia/router.svg)](https://github.com/pedia/router/releases) -Router is a lightweight high performance HTTP request router (also called _multiplexer_ or just _mux_ for short) for [fasthttp](https://github.com/valyala/fasthttp). +Router is a lightweight high performance HTTP request router (also called _multiplexer_ or just _mux_ for short) for [go](https://pkg.go.dev/net/http). This 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. +Based on [fasthttp/router](https://github.com/fasthttp/router). Based on [julienschmidt/httprouter](https://github.com/julienschmidt/httprouter). ## Features @@ -78,17 +79,19 @@ package main import ( "fmt" "log" + "net/http" "github.com/pedia/router" - "github.com/valyala/fasthttp" ) -func Index(ctx *fasthttp.RequestCtx) { - ctx.WriteString("Welcome!") +// Index is the index handler +func Index(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Welcome!\n") } -func Hello(ctx *fasthttp.RequestCtx) { - fmt.Fprintf(ctx, "Hello, %s!\n", ctx.UserValue("name")) +// Hello is the Hello handler +func Hello(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "hello, %s!\n", router.UserValue(r, "name")) } func main() { @@ -96,13 +99,13 @@ func main() { r.GET("/", Index) r.GET("/hello/{name}", Hello) - log.Fatal(fasthttp.ListenAndServe(":8080", r.Handler)) + log.Fatal(http.ListenAndServe(":8080", r)) } ``` ### Named parameters -As you can see, `{name}` is a _named parameter_. The values are accessible via `RequestCtx.UserValues`. You can get the value of a parameter by using the `ctx.UserValue("name")`. +As you can see, `{name}` is a _named parameter_. The values are accessible via `router.UserValues`. You can get the value of a parameter by using the `router.UserValue("name")`. Named parameters only match a single path segment: @@ -186,14 +189,6 @@ For even better scalability, the child nodes on each tree level are ordered by p └- ``` -## Why doesn't this work with `http.Handler`? - -Because fasthttp doesn't provide http.Handler. See this [description](https://github.com/valyala/fasthttp#switching-from-nethttp-to-fasthttp). - -Fasthttp works with [RequestHandler](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler) functions instead of objects implementing Handler interface. So a Router provides a [Handler](https://pkg.go.dev/github.com/pedia/router#Router.Handler) interface to implement the fasthttp.ListenAndServe interface. - -Just try it out for yourself, the usage of Router is very straightforward. The package is compact and minimalistic, but also probably one of the easiest routers to set up. - ## 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`](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler), you can chain any `http.Handler` compatible middleware before the router. Or you could [just write your own](https://justinas.org/writing-http-middleware-in-go/), it's very easy! @@ -209,7 +204,7 @@ Have a look at these middleware examples: You can use another [http.Handler](https://pkg.go.dev/github.com/valyala/fasthttp#RequestHandler), for example another router, to handle requests which could not be matched by this router by using the [Router.NotFound](https://pkg.go.dev/github.com/pedia/router#Router.NotFound) handler. This allows chaining. -### Static files +### Static files(Not Ready) 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): diff --git a/_examples/auth/README.md b/_examples/auth/README.md index 7dda7b8..460f4fc 100644 --- a/_examples/auth/README.md +++ b/_examples/auth/README.md @@ -23,19 +23,19 @@ import ( "encoding/base64" "fmt" "log" + "net/http" "strings" + scrypt "github.com/elithrar/simple-scrypt" "github.com/pedia/router" - "github.com/elithrar/simple-scrypt" - "github.com/valyala/fasthttp" ) // basicAuth returns the username and password provided in the request's // Authorization header, if the request uses HTTP Basic Authentication. // See RFC 2617, Section 2. -func basicAuth(ctx *fasthttp.RequestCtx) (username, password string, ok bool) { - auth := ctx.Request.Header.Peek("Authorization") - if auth == nil { +func basicAuth(r *http.Request) (username, password string, ok bool) { + auth := r.Header.Get("Authorization") + if auth == "" { return } return parseBasicAuth(string(auth)) @@ -61,68 +61,71 @@ func parseBasicAuth(auth string) (username, password string, ok bool) { } // BasicAuth is the basic auth handler -func BasicAuth(h http.Handler, requiredUser string, requiredPasswordHash []byte) http.Handler { - return http.Handler(func(ctx *fasthttp.RequestCtx) { +func BasicAuth(h http.HandlerFunc, requiredUser string, requiredPasswordHash []byte) http.HandlerFunc { + return func(w http.ResponseWriter, r *http.Request) { // Get the Basic Authentication credentials - user, password, hasAuth := basicAuth(ctx) - - // WARNING: + user, password, hasAuth := basicAuth(r) + + // WARNING: // DO NOT use plain-text passwords for real apps. // A simple string comparison using == is vulnerable to a timing attack. // Instead, use the hash comparison function found in your hash library. // This example uses scrypt, which is a solid choice for secure hashing: // go get -u github.com/elithrar/simple-scrypt - + if hasAuth && user == requiredUser { - + // Uses the parameters from the existing derived key. Return an error if they don't match. err := scrypt.CompareHashAndPassword(requiredPasswordHash, []byte(password)) - if err != nil { - + if err != nil { + // log error and request Basic Authentication again below. log.Fatal(err) - - } else { - + + } else { + // Delegate request to the given handle - h(ctx) + h(w, r) return - - } - + + } + } - + // Request Basic Authentication otherwise - ctx.Error(http.StatusMessage(http.StatusUnauthorized), http.StatusUnauthorized) - ctx.Response.Header.Set("WWW-Authenticate", "Basic realm=Restricted") - }) + w.WriteHeader(http.StatusUnauthorized) + w.Header().Set("WWW-Authenticate", "Basic realm=Restricted") + } } // Index is the index handler -func Index(ctx *fasthttp.RequestCtx) { - fmt.Fprint(ctx, "Not protected!\n") +func Index(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Not protected!\n") } // Protected is the Protected handler -func Protected(ctx *fasthttp.RequestCtx) { - fmt.Fprint(ctx, "Protected!\n") +func Protected(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Protected!\n") } func main() { user := "gordon" pass := "secret!" - + // generate a hashed password from the password above: hashedPassword, err := scrypt.GenerateFromPassword([]byte(pass), scrypt.DefaultParams) - if err != nil { - log.Fatal(err) - } + if err != nil { + log.Fatal(err) + } r := router.New() r.GET("/", Index) r.GET("/protected/", BasicAuth(Protected, user, hashedPassword)) - log.Fatal(fasthttp.ListenAndServe(":8080", r.Handler)) + s := http.Server{Addr: ":8080", Handler: r} + log.Fatal(http.ListenAndServe(":8080", r)) + + // curl -vs --user gordon:secret! http://127.0.0.1:8080/protected/ } ``` diff --git a/_examples/basic/README.md b/_examples/basic/README.md index 2eae20a..ca8e55a 100644 --- a/_examples/basic/README.md +++ b/_examples/basic/README.md @@ -14,32 +14,37 @@ package main import ( "fmt" "log" + "net/http" "github.com/pedia/router" - "github.com/valyala/fasthttp" ) // Index is the index handler -func Index(ctx *fasthttp.RequestCtx) { - fmt.Fprint(ctx, "Welcome!\n") +func Index(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Welcome!\n") } // Hello is the Hello handler -func Hello(ctx *fasthttp.RequestCtx) { - fmt.Fprintf(ctx, "hello, %s!\n", ctx.UserValue("name")) +func Hello(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "hello, %s!\n", router.UserValue(r, "name")) } // MultiParams is the multi params handler -func MultiParams(ctx *fasthttp.RequestCtx) { - fmt.Fprintf(ctx, "hi, %s, %s!\n", ctx.UserValue("name"), ctx.UserValue("word")) +func MultiParams(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "hi, %s, %s!\n", router.UserValue(r, "name"), router.UserValue(r, "word")) +} + +// RegexParams is the params handler with regex validation +func RegexParams(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "hi, %s\n", router.UserValue(r, "name")) } // QueryArgs is used for uri query args test #11: // if the req uri is /ping?name=foo, output: Pong! foo // if the req uri is /piNg?name=foo, redirect to /ping, output: Pong! -func QueryArgs(ctx *fasthttp.RequestCtx) { - name := ctx.QueryArgs().Peek("name") - fmt.Fprintf(ctx, "Pong! %s\n", string(name)) +func QueryArgs(w http.ResponseWriter, r *http.Request) { + name := r.URL.Query().Get("name") + fmt.Fprintf(w, "Pong! %s\n", name) } func main() { @@ -47,8 +52,10 @@ func main() { r.GET("/", Index) r.GET("/hello/{name}", Hello) r.GET("/multi/{name}/{word}", MultiParams) + r.GET("/regex/{name:[a-zA-Z]+}/test", RegexParams) + r.GET("/optional/{name?:[a-zA-Z]+}/{word?}", MultiParams) r.GET("/ping", QueryArgs) - log.Fatal(fasthttp.ListenAndServe(":8080", r.Handler)) + log.Fatal(http.ListenAndServe(":8080", r)) } ``` diff --git a/_examples/hosts/README.md b/_examples/hosts/README.md index 1424320..598df4c 100644 --- a/_examples/hosts/README.md +++ b/_examples/hosts/README.md @@ -14,35 +14,35 @@ package main import ( "fmt" "log" + "net/http" "github.com/pedia/router" - "github.com/valyala/fasthttp" ) // Index is the index handler -func Index(ctx *fasthttp.RequestCtx) { - fmt.Fprint(ctx, "Welcome!\n") +func Index(w http.ResponseWriter, r *http.Request) { + fmt.Fprint(w, "Welcome!\n") } // Hello is the Hello handler -func Hello(ctx *fasthttp.RequestCtx) { - fmt.Fprintf(ctx, "hello, %s!\n", ctx.UserValue("name")) +func Hello(w http.ResponseWriter, r *http.Request) { + fmt.Fprintf(w, "hello, %s!\n", router.UserValue(r, "name")) } // HostSwitch is the host-handler map // We need an object that implements the http.Handler interface. // We just use a map here, in which we map host names (with port) to http.Handlers -type HostSwitch map[string]http.Handler +type HostSwitch map[string]http.HandlerFunc // CheckHost Implement a CheckHost method on our new type -func (hs HostSwitch) CheckHost(ctx *fasthttp.RequestCtx) { +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[string(ctx.Host())]; handler != nil { - handler(ctx) + if handler := hs[string(r.Host)]; handler != nil { + handler(w, r) } else { - // Handle host names for wich no handler is registered - ctx.Error("Forbidden", 403) // Or Redirect? + // Handle host names for which no handler is registered + w.WriteHeader(403) // Or Redirect? } } @@ -55,9 +55,12 @@ func main() { // Make a new HostSwitch and insert the router (our http handler) // for example.com and port 12345 hs := make(HostSwitch) - hs["example.com:12345"] = r.Handler + hs["example.com:12345"] = r.ServeHTTP // Use the HostSwitch to listen and serve on port 12345 - log.Fatal(fasthttp.ListenAndServe(":12345", hs.CheckHost)) + log.Fatal(http.ListenAndServe(":12345", hs)) + + // curl -vs -H "Host: example.com:12345" http://127.0.0.1:12345/ + // curl -vs http://127.0.0.1:12345/ } ```