From 45ec83b9d3f487186ec86b98cdaedd33c7d331ea Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Wed, 7 Nov 2018 22:08:22 +0100 Subject: [PATCH 01/18] Add README.md --- README.md | 159 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..4b3b80b --- /dev/null +++ b/README.md @@ -0,0 +1,159 @@ +# router + +Router is a HTTP router for Golang. It's built on [httprouter](https://github.com/julienschmidt/httprouter) and takes inspiration from [labstack/echo](https://github.com/labstack/echo), but with reduced complexity and easier data binding. + +The data binding is made easier by specifying your input as a parameter to your function + +Example: + +```golang +type someType struct { + A string `json:"a"` + B int `json:"b"` +} + +func handlePOST(c *router.Context, input someType) error { + fmt.Println(input) + return c.NoContent(200) +} +``` + +### Why make data binding shorter? + +Many applications read, bind and validate data for most calls. In Echo this could mean adding boilerplate code to every call. This extra boilerplate code can make your code significantly longer and very hard to read. + +```golang +func handlePOST(c *echo.Context) error { + var input someType + err := c.Bind(&input) + if err != nil { + return c.NoContent(http.StatusBadRequest) + } + err = c.Validate(input) + if err != nil { + return c.NoContent(http.StatusBadRequest) + } + + // Your actual code +} +``` + +In router you define your code to read, bind and validate the input once, and it applies to every POST, PATCH and PUT call. + +### What about the performance of dynamic parameters? + +While using dynamic parameters takes a bit of extra processing, this barely has any impact on performance. Router can still handle tens to hunderds of thousands of requests per second. + +### How does middleware work? + +Middleware works similar to most other routers. The dynamic parameters has no effect on middleware, input data is parsed after all middlewares and right before your handler. + +## Installation + +```bash +go get git.fuyu.moe/Fuyu/router +``` + +## Getting started + +```golang +package main + +import "git.fuyu.moe/Fuyu/router" + +func main() { + // Create a router instance + r := router.New() + + // Add routes + r.GET(`/`, yourGetFunc) + r.POST(`/`, yourPostFunc) + + // Start router + panic(r.Start(`127.0.0.1:8080`)) +} +``` + +## Advice + +### Configuration + +For a serious project you should set `r.Reader`, `r.ErrorHandler`, `r.NotFoundHandler`, and `r.MethodNotAllowedHandler`. + +### Templating + +You can set `r.Renderer` and call `c.Render(code, tmplName, data)` in your handlers + + + +## Examples + +```golang +package main + +import ( + "fmt" + "time" + + "git.fuyu.moe/Fuyu/router" +) + +type product struct { + Name string `json:"name"` +} + +func main() { + r := router.New() + r.Use(accessLog) + + r.GET(`/hello`, hello) + + a := r.Group(`/api`, gandalf) + a.POST(`/product`, createProduct) + a.PATCH(`/product/:id`, updateProduct) + a.POST(`/logout`, logout) + + r.Start(`:8080`) +} + +func accessLog(next router.Handle) router.Handle { + return func(c *router.Context) error { + t := time.Now() + err := next(c) + + fmt.Println(c.Request.Method, c.Request.URL.Path, c.Request.RemoteAddr, t, time.Since(t)) + + return err + } +} + +func gandalf(next router.Handle) router.Handle { + youShallPass := false + return func(c *router.Context) error { + if !youShallPass { + return c.String(401, `You shall not pass`) + } + + return next(c) + } +} + +func hello(c *router.Context) error { + return c.String(200, `Hello`) +} + +func createProduct(c *router.Context, p product) error { + return c.JSON(200, p) +} + +func updateProduct(c *router.Context, p product) error { + productID := c.Param(`id`) + return c.String(200, fmt.Sprintf( + `ProductID %d new name %s`, productID, p.Name, + )) +} + +func logout(c *router.Context) error { + return c.String(200, `logout`) +} +``` From 2c930bacf138f3b66b7c07d9e07fdb7f543113d7 Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Sun, 18 Nov 2018 15:39:46 +0100 Subject: [PATCH 02/18] Use net/http for default handler messages --- context.go | 5 +++++ default.go | 9 ++++----- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/context.go b/context.go index cf6c73d..60ab4df 100644 --- a/context.go +++ b/context.go @@ -35,6 +35,11 @@ func (c *Context) String(code int, s string) error { return err } +// StatusText returns the given status code with the matching status text +func (c *Context) StatusText(code int) error { + return c.String(code, http.StatusText(code)) +} + // NoContent returns the given status code without writing anything to the body func (c *Context) NoContent(code int) error { c.Response.WriteHeader(code) diff --git a/default.go b/default.go index 2f0aeac..d36c6c8 100644 --- a/default.go +++ b/default.go @@ -2,20 +2,19 @@ package router import ( "encoding/json" - "fmt" + "net/http" ) func defaultNotFoundHandler(c *Context) error { - return c.String(404, `not found`) + return c.StatusText(http.StatusNotFound) } func defaultMethodNotAllowedHandler(c *Context) error { - return c.String(504, `method not allowed`) + return c.StatusText(http.StatusMethodNotAllowed) } func defaultErrorHandler(c *Context, err interface{}) { - fmt.Println(err) - c.String(500, `internal server error`) + _ = c.StatusText(http.StatusInternalServerError) } func defaultReader(c *Context, dst interface{}) bool { From 4c83818ecdd4c96bc639b8d3c57c173b43af2e2b Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Sun, 18 Nov 2018 16:20:13 +0100 Subject: [PATCH 03/18] Fix Middleware comment --- router.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router.go b/router.go index f091723..79bbb71 100644 --- a/router.go +++ b/router.go @@ -20,7 +20,7 @@ type Handle = func(*Context) error // ErrorHandle handles a request type ErrorHandle func(*Context, interface{}) -// Middleware TODO: +// Middleware is a function that runs before your route, it gets the next handler as a parameter type Middleware func(Handle) Handle // Binder reads input to dst, returns true is successful From a16ee2740c0db7be61bdf48d9c491988d4e6c62f Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Sun, 18 Nov 2018 16:29:06 +0100 Subject: [PATCH 04/18] Add error to return of Reader --- default.go | 7 +++---- router.go | 13 +++++++++---- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/default.go b/default.go index d36c6c8..ecda050 100644 --- a/default.go +++ b/default.go @@ -17,12 +17,11 @@ func defaultErrorHandler(c *Context, err interface{}) { _ = c.StatusText(http.StatusInternalServerError) } -func defaultReader(c *Context, dst interface{}) bool { +func defaultReader(c *Context, dst interface{}) (bool, error) { err := json.NewDecoder(c.Request.Body).Decode(dst) if err != nil { - c.NoContent(400) - return false + return false, c.StatusText(http.StatusBadRequest) } - return true + return true, nil } diff --git a/router.go b/router.go index 79bbb71..04980c1 100644 --- a/router.go +++ b/router.go @@ -24,7 +24,7 @@ type ErrorHandle func(*Context, interface{}) type Middleware func(Handle) Handle // Binder reads input to dst, returns true is successful -type Reader func(c *Context, dst interface{}) bool +type Reader func(c *Context, dst interface{}) (bool, error) // Router is the router itself type Router struct { @@ -164,11 +164,16 @@ func handlePOST(r *Router, f interface{}) Handle { return func(c *Context) error { data := reflect.New(inputRt) - if !r.Reader(c, data.Interface()) { + if r.Reader != nil { + ok, err := r.Reader(c, data.Interface()) c.Request.Body.Close() - return nil + if err != nil { + return err + } + if !ok { + return nil + } } - c.Request.Body.Close() out := funcRv.Call([]reflect.Value{reflect.ValueOf(c), data.Elem()}) From d30d304cea836fe6502173a96e194f596c85512d Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Fri, 30 Nov 2018 14:24:28 +0100 Subject: [PATCH 05/18] Add Redirect to Context --- context.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/context.go b/context.go index 60ab4df..21b1307 100644 --- a/context.go +++ b/context.go @@ -20,6 +20,11 @@ func newContext(router *Router, res http.ResponseWriter, req *http.Request, para return &Context{router, req, res, param.ByName, make(map[string]interface{})} } +func (c *Context) Redirect(code int, url string) error { + http.Redirect(c.Response, c.Request, url, code) + return nil +} + // String returns the given status code and writes the bytes to the body func (c *Context) Bytes(code int, b []byte) error { c.Response.WriteHeader(code) From 4cc4ce2a2e6b201a922cf952e75cc06f423ea857 Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Fri, 30 Nov 2018 14:27:00 +0100 Subject: [PATCH 06/18] Add QueryParam to Context --- context.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/context.go b/context.go index 21b1307..2c65adb 100644 --- a/context.go +++ b/context.go @@ -20,6 +20,15 @@ func newContext(router *Router, res http.ResponseWriter, req *http.Request, para return &Context{router, req, res, param.ByName, make(map[string]interface{})} } +func (c *Context) QueryParam(param string) string { + params := c.Request.URL.Query()[param] + if params == nil { + return `` + } + + return params[0] +} + func (c *Context) Redirect(code int, url string) error { http.Redirect(c.Response, c.Request, url, code) return nil From 0990f5bafe386b45e5555771861af8cd79365a02 Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Fri, 30 Nov 2018 14:29:59 +0100 Subject: [PATCH 07/18] Remove unused code --- router.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/router.go b/router.go index 04980c1..dbf517f 100644 --- a/router.go +++ b/router.go @@ -125,13 +125,6 @@ func (r *Router) getHttpr() *httprouter.Router { return httpr } -func handleErr(errHandler ErrorHandle, err interface{}) Handle { - return func(c *Context) error { - errHandler(c, err) - return nil - } -} - func checkInterfaceHandle(f interface{}) { if _, ok := f.(Handle); ok { return From 890ff550eb3350bba97f5d9d9135e8ca6a086ba4 Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Fri, 30 Nov 2018 14:48:07 +0100 Subject: [PATCH 08/18] Fix function comments --- context.go | 6 +++++- router.go | 4 ++-- type.go | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/context.go b/context.go index 2c65adb..ee7116a 100644 --- a/context.go +++ b/context.go @@ -20,6 +20,8 @@ func newContext(router *Router, res http.ResponseWriter, req *http.Request, para return &Context{router, req, res, param.ByName, make(map[string]interface{})} } +// QueryParam returns the specified parameter from the query string. +// Returns an empty string if it doesn't exist. Returns the first parameter if multiple instances exist func (c *Context) QueryParam(param string) string { params := c.Request.URL.Query()[param] if params == nil { @@ -29,12 +31,13 @@ func (c *Context) QueryParam(param string) string { return params[0] } +// Redirect sends a redirect to the client func (c *Context) Redirect(code int, url string) error { http.Redirect(c.Response, c.Request, url, code) return nil } -// String returns the given status code and writes the bytes to the body +// Bytes returns the given status code and writes the bytes to the body func (c *Context) Bytes(code int, b []byte) error { c.Response.WriteHeader(code) _, err := c.Response.Write(b) @@ -67,6 +70,7 @@ func (c *Context) JSON(code int, data interface{}) error { return json.NewEncoder(c.Response).Encode(data) // TODO: Encode to buffer first to prevent partial responses on error } +// Render renders a templating using the Renderer set in router func (c *Context) Render(code int, template string, data interface{}) error { if c.router.Renderer == nil { panic(`Cannot call render without a renderer set`) diff --git a/router.go b/router.go index dbf517f..a878555 100644 --- a/router.go +++ b/router.go @@ -23,7 +23,7 @@ type ErrorHandle func(*Context, interface{}) // Middleware is a function that runs before your route, it gets the next handler as a parameter type Middleware func(Handle) Handle -// Binder reads input to dst, returns true is successful +// Reader reads input to dst, returns true if successful type Reader func(c *Context, dst interface{}) (bool, error) // Router is the router itself @@ -159,7 +159,7 @@ func handlePOST(r *Router, f interface{}) Handle { if r.Reader != nil { ok, err := r.Reader(c, data.Interface()) - c.Request.Body.Close() + _ = c.Request.Body.Close() if err != nil { return err } diff --git a/type.go b/type.go index 2d5c6a5..bea7215 100644 --- a/type.go +++ b/type.go @@ -4,6 +4,7 @@ import ( "io" ) +// Renderer renders a template type Renderer interface { Render(w io.Writer, template string, data interface{}, c *Context) error } From 717c6f65fcdc2ec28f0939473f333ee4b084e4c5 Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Fri, 30 Nov 2018 14:48:29 +0100 Subject: [PATCH 09/18] Use a buffer in Render --- context.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/context.go b/context.go index ee7116a..e6cc4b3 100644 --- a/context.go +++ b/context.go @@ -1,7 +1,9 @@ package router import ( + "bytes" "encoding/json" + "io" "net/http" "github.com/julienschmidt/httprouter" @@ -76,9 +78,16 @@ func (c *Context) Render(code int, template string, data interface{}) error { panic(`Cannot call render without a renderer set`) } + var b bytes.Buffer + err := c.router.Renderer.Render(&b, template, data, c) + if err != nil { + return err + } + c.Response.Header().Set(`Content-Type`, `text/html`) c.Response.WriteHeader(code) - return c.router.Renderer.Render(c.Response, template, data, c) + _, _ = io.Copy(c.Response, &b) + return nil } // Set sets a value in the context. Set is not safe to be used concurrently From 15df08f21b55abfe63e42cf62ae5bae35ebd3888 Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Tue, 11 Jun 2019 16:24:53 +0200 Subject: [PATCH 10/18] Add Stop function --- router.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/router.go b/router.go index a878555..dd8cfd7 100644 --- a/router.go +++ b/router.go @@ -1,8 +1,10 @@ package router import ( + "context" "net/http" "reflect" + "time" "github.com/julienschmidt/httprouter" ) @@ -35,6 +37,7 @@ type Router struct { NotFoundHandler Handle MethodNotAllowedHandler Handle ErrorHandler ErrorHandle + server *http.Server } // New returns a new Router @@ -94,7 +97,25 @@ func (r *Router) OPTIONS(path string, handle Handle, middleware ...Middleware) { func (r *Router) Start(addr string) error { httpr := r.getHttpr() - return http.ListenAndServe(addr, httpr) + r.server = &http.Server{Addr: addr, Handler: httpr} + return r.server.ListenAndServe() +} + +// Stop stops the web server +func (r *Router) Stop() error { + if r.server == nil { + return nil + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Second*5)) + defer cancel() + err := r.server.Shutdown(ctx) + if err == context.DeadlineExceeded { + err = r.server.Close() + } + r.server = nil + + return err } func (r *Router) getHttpr() *httprouter.Router { From 60f82143da0552ec3f3ead1df439df0fc71f689c Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Tue, 11 Jun 2019 16:30:58 +0200 Subject: [PATCH 11/18] Add TLS version of Start --- router.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/router.go b/router.go index dd8cfd7..7b50138 100644 --- a/router.go +++ b/router.go @@ -2,6 +2,7 @@ package router import ( "context" + "crypto/tls" "net/http" "reflect" "time" @@ -101,6 +102,13 @@ func (r *Router) Start(addr string) error { return r.server.ListenAndServe() } +func (r *Router) StartTLS(addr, certFile, keyFile string, conf *tls.Config) error { + httpr := r.getHttpr() + + r.server = &http.Server{Addr: addr, Handler: httpr, TLSConfig: conf} + return r.server.ListenAndServeTLS(certFile, keyFile) +} + // Stop stops the web server func (r *Router) Stop() error { if r.server == nil { From 317961ab6eb975d39636c425ae3116114c5de07d Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Wed, 3 Jul 2019 12:24:45 +0200 Subject: [PATCH 12/18] Clean up some code --- router.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/router.go b/router.go index 7b50138..584ba01 100644 --- a/router.go +++ b/router.go @@ -102,6 +102,7 @@ func (r *Router) Start(addr string) error { return r.server.ListenAndServe() } +// StartTLS starts a TLS web server using the given key, cert and config and binds to the given address func (r *Router) StartTLS(addr, certFile, keyFile string, conf *tls.Config) error { httpr := r.getHttpr() @@ -115,7 +116,7 @@ func (r *Router) Stop() error { return nil } - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(time.Second*5)) + ctx, cancel := context.WithTimeout(context.Background(), time.Second*5) defer cancel() err := r.server.Shutdown(ctx) if err == context.DeadlineExceeded { @@ -211,7 +212,7 @@ func handleReq(r *Router, handle Handle, m []Middleware) httprouter.Handle { c := newContext(r, res, req, param) f := handle - for i := len(m) - 1; i >= 0; i-- { // TODO: 1,2,3 of 3,2,1 + for i := len(m) - 1; i >= 0; i-- { f = m[i](f) } From 2a612eb82faeea8c0d499cdde0abf54398bb39d0 Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Wed, 3 Jul 2019 12:26:29 +0200 Subject: [PATCH 13/18] Fix middlewares getting overwritten --- router.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/router.go b/router.go index 584ba01..1584600 100644 --- a/router.go +++ b/router.go @@ -136,7 +136,11 @@ func (r *Router) getHttpr() *httprouter.Router { handle = handlePOST(r, v.Handle) } - httpr.Handle(v.Method, v.Path, handleReq(r, handle, append(r.middleware, v.Middleware...))) + middleware := make([]Middleware, len(r.middleware)+len(v.Middleware)) + copy(middleware, r.middleware) + copy(middleware[len(r.middleware):], v.Middleware) + + httpr.Handle(v.Method, v.Path, handleReq(r, handle, middleware)) } httpr.NotFound = http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { From d504c9d2b516cca9e8a51398fce1661ffe291094 Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Mon, 6 Apr 2020 16:41:26 +0200 Subject: [PATCH 14/18] Add Context.RealIP --- context.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/context.go b/context.go index e6cc4b3..6b8b7a9 100644 --- a/context.go +++ b/context.go @@ -4,7 +4,9 @@ import ( "bytes" "encoding/json" "io" + "net" "net/http" + "strings" "github.com/julienschmidt/httprouter" ) @@ -99,3 +101,17 @@ func (c *Context) Set(key string, value interface{}) { func (c *Context) Get(key string) interface{} { return c.store[key] } + +// RealIP uses proxy headers for the real ip, if none exist the IP of the current connection is returned +func (c *Context) RealIP() string { + if ip := c.Request.Header.Get(`X-Forwarded-For`); ip != `` { + return strings.Split(ip, `, `)[0] + } + + if ip := c.Request.Header.Get(`X-Real-IP`); ip != `` { + return ip + } + + ra, _, _ := net.SplitHostPort(c.Request.RemoteAddr) + return ra +} From 5b5a102c71454d1e39a6575545fb48d0c89a36b2 Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Thu, 9 Apr 2020 10:41:23 +0200 Subject: [PATCH 15/18] Export NewContext --- context.go | 3 ++- router.go | 6 ++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/context.go b/context.go index 6b8b7a9..eec329c 100644 --- a/context.go +++ b/context.go @@ -20,7 +20,8 @@ type Context struct { store map[string]interface{} } -func newContext(router *Router, res http.ResponseWriter, req *http.Request, param httprouter.Params) *Context { +// NewContext creates a new context, this function is only exported for use in tests +func NewContext(router *Router, res http.ResponseWriter, req *http.Request, param httprouter.Params) *Context { return &Context{router, req, res, param.ByName, make(map[string]interface{})} } diff --git a/router.go b/router.go index 1584600..f53b4ca 100644 --- a/router.go +++ b/router.go @@ -152,7 +152,7 @@ func (r *Router) getHttpr() *httprouter.Router { }) httpr.PanicHandler = func(res http.ResponseWriter, req *http.Request, err interface{}) { - c := newContext(r, res, req, nil) + c := NewContext(r, res, req, nil) r.ErrorHandler(c, err) } @@ -181,8 +181,6 @@ func checkInterfaceHandle(f interface{}) { if rt.In(0) != reflect.TypeOf(&Context{}) { panic(`handle should accept Context as first argument`) } - - return } func handlePOST(r *Router, f interface{}) Handle { @@ -213,7 +211,7 @@ func handlePOST(r *Router, f interface{}) Handle { func handleReq(r *Router, handle Handle, m []Middleware) httprouter.Handle { return func(res http.ResponseWriter, req *http.Request, param httprouter.Params) { - c := newContext(r, res, req, param) + c := NewContext(r, res, req, param) f := handle for i := len(m) - 1; i >= 0; i-- { From 66ae2a435c93a4dad4bd57dc2e1fdcf5f73efedc Mon Sep 17 00:00:00 2001 From: NiseVoid Date: Tue, 19 May 2020 17:04:00 +0200 Subject: [PATCH 16/18] Add TrimTrailingSlashes option --- router.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/router.go b/router.go index f53b4ca..d869fc6 100644 --- a/router.go +++ b/router.go @@ -38,6 +38,7 @@ type Router struct { NotFoundHandler Handle MethodNotAllowedHandler Handle ErrorHandler ErrorHandle + TrimTrailingSlashes bool server *http.Server } @@ -127,7 +128,7 @@ func (r *Router) Stop() error { return err } -func (r *Router) getHttpr() *httprouter.Router { +func (r *Router) getHttpr() http.Handler { httpr := httprouter.New() for _, v := range r.routes { @@ -156,6 +157,18 @@ func (r *Router) getHttpr() *httprouter.Router { r.ErrorHandler(c, err) } + if r.TrimTrailingSlashes { + httpr.RedirectTrailingSlash = false + + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + l := len(req.URL.Path) + if l > 1 && req.URL.Path[l-1] == '/' { + req.URL.Path = req.URL.Path[:l-1] + } + httpr.ServeHTTP(w, req) + }) + } + return httpr } From 283c75b32e971b100c8aa7c25c0a448f682d77d5 Mon Sep 17 00:00:00 2001 From: Elwin Tamminga Date: Tue, 20 Jul 2021 10:49:55 +0200 Subject: [PATCH 17/18] Remove port when using headers in c.RealIP Co-authored-by: Elwin Tamminga Co-committed-by: Elwin Tamminga --- context.go | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/context.go b/context.go index eec329c..e9c2fd6 100644 --- a/context.go +++ b/context.go @@ -105,14 +105,17 @@ func (c *Context) Get(key string) interface{} { // RealIP uses proxy headers for the real ip, if none exist the IP of the current connection is returned func (c *Context) RealIP() string { + reqIP := c.Request.RemoteAddr + if ip := c.Request.Header.Get(`X-Forwarded-For`); ip != `` { - return strings.Split(ip, `, `)[0] + reqIP = strings.Split(ip, `, `)[0] + } else if ip := c.Request.Header.Get(`X-Real-IP`); ip != `` { + reqIP = ip } - if ip := c.Request.Header.Get(`X-Real-IP`); ip != `` { - return ip + ra, _, _ := net.SplitHostPort(reqIP) + if ra != `` { + reqIP = ra } - - ra, _, _ := net.SplitHostPort(c.Request.RemoteAddr) - return ra + return reqIP } From 152bf49c4b925a761b3a0aa5c4830da3c9f5082e Mon Sep 17 00:00:00 2001 From: robinknaapen Date: Mon, 28 Mar 2022 10:38:46 +0200 Subject: [PATCH 18/18] Add support for net.Listener --- go.mod | 5 +++++ go.sum | 2 ++ router.go | 9 +++++++++ 3 files changed, 16 insertions(+) create mode 100644 go.mod create mode 100644 go.sum diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..dc9d16a --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module git.fuyu.moe/Fuyu/router + +go 1.13 + +require github.com/julienschmidt/httprouter v1.3.0 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..096c54e --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= diff --git a/router.go b/router.go index d869fc6..754ab42 100644 --- a/router.go +++ b/router.go @@ -3,6 +3,7 @@ package router import ( "context" "crypto/tls" + "net" "net/http" "reflect" "time" @@ -95,6 +96,14 @@ func (r *Router) OPTIONS(path string, handle Handle, middleware ...Middleware) { r.routes = append(r.routes, route{`OPTIONS`, path, handle, middleware}) } +// Serve accepts incoming HTTP connections on the listener +func (r *Router) Serve(l net.Listener) error { + httpr := r.getHttpr() + + r.server = &http.Server{Handler: httpr} + return r.server.Serve(l) +} + // Start starts the web server and binds to the given address func (r *Router) Start(addr string) error { httpr := r.getHttpr()