From 42cf24fda27b74388738bcd8851e8c52ffcc0b66 Mon Sep 17 00:00:00 2001 From: "Gerasimos (Makis) Maropoulos" Date: Wed, 22 Feb 2017 00:51:50 +0200 Subject: [PATCH] Add a simple and pure `.Regex` middleware for routers that don't support regex route path validations out-of-the-box Former-commit-id: 84cf1fb267e54543ad6d419b2ca39658b2773b58 --- .github/CONTRIBUTING.md | 25 +++++------- .github/PULL_REQUEST_TEMPLATE.md | 68 ++++++------------------------- HISTORY.md | 4 +- adaptors/gorillamux/gorillamux.go | 6 +++ adaptors/httprouter/httprouter.go | 6 +++ configuration.go | 16 +++++++- handler_test.go | 2 +- policy_gorillamux_test.go | 2 - policy_httprouter_test.go | 23 ++++++++--- router.go | 61 +++++++++++++++++++++++++++ 10 files changed, 132 insertions(+), 81 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 5f078b73..0c95c110 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -8,28 +8,23 @@ First, please do a search in open issues to see if the issue or feature request The Iris project is distributed across multiple repositories, try to file the issue against the correct repository, -- [Iris - middleware](https://github.com/iris-contrib/middleware/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) -- [Iris - cli and rizla](https://github.com/kataras/rizla/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) -- [Iris - templates](https://github.com/kataras/go-template/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) -- [Iris - sessions](https://github.com/kataras/go-sessions/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) -- [Iris - websocket](https://github.com/kataras/go-websocket/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) -- [Iris - gitbook](https://github.com/iris-contrib/gitbook/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) -- [Iris - examples](https://github.com/iris-contrib/examples/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) -- [Iris - plugin](https://github.com/iris-contrib/plugin/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) -- [Iris - graceful](https://github.com/iris-contrib/graceful/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) -- [Iris - mail](https://github.com/iris-contrib/mail/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) -- [Iris - core](https://github.com/kataras/iris/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) +- [Community iris-specific middleware](https://github.com/iris-contrib/middleware/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) +- [App reloader and command line tool](https://github.com/kataras/rizla/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) +- [View Engine](https://github.com/kataras/go-template/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) +- [Sessions](https://github.com/kataras/go-sessions/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) +- [About docs.iris-go.com](https://github.com/iris-contrib/gitbook/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) +- [About examples](https://github.com/iris-contrib/examples/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) +- [Iris main(core)](https://github.com/kataras/iris/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) Before post a new issue do an iris upgrade: -- Delete `$GOPATH/src/github.com/kataras` -- Open shell and execute the command: `go get -u github.com/kataras/iris/iris` +- Delete `$GOPATH/src/gopkg.in/kataras` +- Open shell and execute the command: `go get -u gopkg.in/kataras/iris.v6/iris` - Try to re-produce the issue - If the issue still exists, then post the issue with the necessary information. - -If the issue is after an upgrade, please read the [HISTORY.md](https://github.com/kataras/iris/blob/master/HISTORY.md) for any breaking-changes and fixes. +If the issue is after an upgrade, please read the [HISTORY.md](https://github.com/kataras/iris/blob/v6/HISTORY.md) for any breaking-changes and fixes. The author answers the same day, perhaps the same hour you post the issue. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 9843d477..4a9ba5d6 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,62 +1,20 @@ -If you are interested in contributing to the Iris project, please see the document [CONTRIBUTING](https://github.com/kataras/iris/blob/master/.github/CONTRIBUTING.md). +This repository follows the official [golang](https://github.com/golang/go#contributing) guidelines for the contributions: -##### Note that I do not accept pull requests here and that I use the issue tracker for bug reports and proposals only. Please ask questions on the [https://kataras.rocket.chat/channel/iris][Chat] or [http://stackoverflow.com/](http://stackoverflow.com). +##### Note that I do not accept pull requests here and that I use the issue tracker for bug reports and proposals only. Please ask questions on the [https://kataras.rocket.chat/channel/iris][Chat] or http://stackoverflow.com. -> **it's not personal to somebody specific**, I don't want to attack to anyone, I am polite but I had to write these complaints, eventually. +Iris' features are not directly adopted to Iris. A component's feature/change should be tested for some time before being part of the Iris which users install. -I wanted to answer on some users who complaining that I'm not accepting PRs to the main code base. +Do PRs at the iris' components instead of the core. +You can find many repositories to help Iris with your contribution. The [iris-contrib](https://github.com/iris-contrib) is open for any +kind of PR, community is 100% responsible for the whole organisation. -I am accepting PRs to the [iris-contrib](https://github.com/iris-contrib) and I see how things were gone. -As you already know I'm not a native english speaker, the gitbook PRs were **all excellent and I thanks you from my deep of my heart!**. My complain and my personal experience with PRs -you will read below is a fact for all repositories **except the gitbook's PRs and zaplogger middleware**. +- [Community iris-specific middleware](https://github.com/iris-contrib/middleware/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) +- [App reloader and command line tool](https://github.com/kataras/rizla/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) +- [View Engine](https://github.com/kataras/go-template/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) +- [Sessions](https://github.com/kataras/go-sessions/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) +- [For docs.iris-go.com](https://github.com/iris-contrib/gitbook/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) +- [For examples](https://github.com/iris-contrib/examples/issues?utf8=%E2%9C%93&q=is%3Aopen+is%3Aissue) -There weren't many PRs but anyway...some people 'attacking' me because I code all alone, this is what it is. - -Let's take for example the `cors` middleware. -Yes that middleware which didn't worked for you and the 80% of the overral middleware's issues were for this and only this middleware. - - -The guy who did the first PR to the cors middleware didn't answer to your issues, I had to do it that, -I had to answer for another's code. - -It's true that I don't like these situations and this is the reason why I'm always avoid of accepting PRs to the main iris codebase. -Do you imagine what would happen if I was accepting PRs here, to the main iris repository?... I don't. -Not because the community sucks and they are not good as me, maybe they are better but they are not **responsible** for their code -and I'm sorry for telling you that but this is what I received as an experience of letting people to 'help' at the beggining: -better safe and 'alone' rather than a transformation of the framework to a collection of big 'anarchy' and unnecessary features. - -**Let's explain** what I mean by 'anarchy': -Some github users made PRs without even posting a new github issue to describe the idea or the bug before push their code, -surprisingly 100% of the changes to these PRs were for things that already implemented by -the framework but they had no clue because they didn't care or didn't have time to view the code closer, they just wanted to make an one-line PR and disappear. - -We don't have any guarantee that an author of any PR will be here to answer to users or to fix a bug or -to make changes on future iris changes(and as ultimately this proved most of them they didn't care about your questions, your bug reports and you). **I'm responsible to individual developers and companies who using Iris to their projects.** -As you already noticed I'm online almost all the day to answer to your questions (almost instantly), fixing bugs and upgrading the code. -**This is why this Framework is not like others, it has the abiliy to be always synced with the latest trends and features happens around the world.** - - -Yes, I did disappointed by a small group of the community, I was waiting for more activity, I was imaging big work all together and the open-source idea. -After the results I came up, I had to pause this 'idea' on my mind for a very long time. -This is why I never ask you for a PR here, in the main repository, your contribution can be 'only' by -reporting bugs and feature requests, **these are not a small part, things like these made this framework so ideal.** - -**The main repository's flow is: community recommends -> I code. I am not special, just a regular guy who really loves to code and happens to have all the world's time to code for Iris. No, I'm not a rich guy, I had some money from my previous job but over time as you can imagine, they are spending on my body's requirements, hopefuly we have nice guys and women who cares about these things and making donations that helping me to survive, as I left from my job months ago in order to start coding for Iris explicitly.**. - - - -From the beggining, the project, gained incredible popularity in short-time, -the first month we broke all the rules, we were first on global-github-languages trending list. -**I always say 'we' because you're helping a lot, more than you can even imagine, with your bug reports and feature requests.** -So don't try to fool our selves, Iris has unique features and it's really one of the fastest web frameworks in globe, but these alone are nothing. - - -So why people loves and trust this framework for a long time of period? Because - **they know where to find the guy who coded this and can ask anything, they get an answer instantly, they know where to stand if something goes bad to their app.** - - -BUT I'm choosing to give a second chance to people who want to contribution by code. If this time, the people who want to contribute by code do their jobs and mantain their responsibility to iris' users, there are good chances on the future that I will choose some of them, based on the code quality that will be pushed and their activity, in order to ask them for collaboration on the main iris repo too. - -So the **[iris-contrib](https://github.com/iris-contrib) organisation and all its repositories will stay open for PRs.** Hopefully future-authors will be more responsible after this long text I wrote here, let's see how things will go on. **Only one term to future authors**: from now and on I want you to be responsible and answer to the future users' issues that are relative to your code. PR's authors must provide adequate support. + **Only one term to future authors**: A contributor should be responsible and answer to the future users' issues that are relative to her/his code. PR's authors must provide adequate support. **Users is the most important part, we, as software authors, have to respect them. I don't accept any form of contempt to them(users) by anyone.** diff --git a/HISTORY.md b/HISTORY.md index bb34f99c..b0128233 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -5,6 +5,8 @@ ## 6.1.4 -> 6.2.0 (√Νεxτ) +_Last update: 22 Feb 2017_ + > Note: I want you to know that I spent more than 200 hours (16 days of ~10-15 hours per-day, do the math) for this release, two days to write these changes, please read the sections before think that you have an issue and post a new question, thanks! @@ -19,7 +21,7 @@ to adapt the new changes to your application, it contains an overview of the new - Basic middleware, that have been written by me, are transfared to the main repository[/middleware](https://github.com/kataras/iris/tree/v6/middleware) with a lot of improvements to the `recover middleware` (see the next) - `func(http.ResponseWriter, r *http.Request, next http.HandlerFunc)` signature is fully compatible using `iris.ToHandler` helper wrapper func, without any need of custom boilerplate code. So all net/http middleware out there are supported, no need to re-invert the world here, search to the internet and you'll find a suitable to your case. - Developers can use a `yaml` files for the configuration using the `iris.YAML` function: `app := iris.New(iris.YAML("myconfiguration.yaml"))` - +- Add `.Regex` middleware which does path validation using the `regexp` package, i.e `.Regex("param", "[0-9]+$")`. Useful for routers that don't support regex route path validation out-of-the-box. Fixes: diff --git a/adaptors/gorillamux/gorillamux.go b/adaptors/gorillamux/gorillamux.go index e094d105..162ea0f4 100644 --- a/adaptors/gorillamux/gorillamux.go +++ b/adaptors/gorillamux/gorillamux.go @@ -43,6 +43,11 @@ func staticPath(path string) string { return path } +// Name is the name of the router +// +// See $iris_instance.Config.Other for more. +const Name = "gorillamux" + // New returns a new gorilla mux router which can be plugged inside iris. // This is magic. func New() iris.Policies { @@ -52,6 +57,7 @@ func New() iris.Policies { return iris.Policies{ EventPolicy: iris.EventPolicy{Boot: func(s *iris.Framework) { logger = s.Log + s.Set(iris.OptionOther(iris.RouterNameConfigKey, Name)) }}, RouterReversionPolicy: iris.RouterReversionPolicy{ // path normalization done on iris' side diff --git a/adaptors/httprouter/httprouter.go b/adaptors/httprouter/httprouter.go index 4757547b..c67c3997 100644 --- a/adaptors/httprouter/httprouter.go +++ b/adaptors/httprouter/httprouter.go @@ -496,6 +496,11 @@ func formatPath(path string) string { return path } +// Name is the name of the router +// +// See $iris_instance.Config.Other for more. +const Name = "httprouter" + // New returns a new iris' policy to create and attach the router. // It's based on the julienschmidt/httprouter with more features and some iris-relative performance tips: // subdomains(wildcard/dynamic and static) and faster parameters set (use of the already-created context's values) @@ -512,6 +517,7 @@ func New() iris.Policies { EventPolicy: iris.EventPolicy{ Boot: func(s *iris.Framework) { logger = s.Log + s.Set(iris.OptionOther(iris.RouterNameConfigKey, Name)) }, }, RouterReversionPolicy: iris.RouterReversionPolicy{ diff --git a/configuration.go b/configuration.go index 728f6f8c..32e25f99 100644 --- a/configuration.go +++ b/configuration.go @@ -187,10 +187,24 @@ type Configuration struct { // Other are the custom, dynamic options, can be empty. // This field used only by you to set any app's options you want // or by custom adaptors, it's a way to simple communicate between your adaptors (if any) - // Defaults to a non-nil empty map. + // Defaults to a non-nil empty map + // + // Some times is useful to know the router's name in order to take some dynamically runtime decisions. + // So, when router policies are being adapted by a router adaptor, + // a "routeName" key will be(optionally) filled with the name of the Router's features are being used. + // The "routeName" can be retrivied by: + // app := iris.New() + // app.Adapt(routerAdaptor.New()) + // app.Config.Other[iris.RouterNameConfigKey] + // Other map[string]interface{} `yaml:"Other"` } +// RouterNameConfigKey is the optional key that is being registered by router adaptor. +// It's not as a static field because it's optionally setted, it depends of the router adaptor's author. +// Usage: app.Config.Other[iris.RouterNameConfigKey] +const RouterNameConfigKey = "routerName" + // Set implements the OptionSetter func (c Configuration) Set(main *Configuration) { if err := mergo.MergeWithOverwrite(main, c); err != nil { diff --git a/handler_test.go b/handler_test.go index ad54fa46..cf3dda23 100644 --- a/handler_test.go +++ b/handler_test.go @@ -37,7 +37,7 @@ func TestCustomHandler(t *testing.T) { app.Handle("GET", "/custom_handler_1", &myTestCustomHandler{myData}) app.Handle("GET", "/custom_handler_2", &myTestCustomHandler{myData}) - e := httptest.New(app, t, httptest.Debug(true)) + e := httptest.New(app, t) // two times per testRoute param1 := "thisimyparam1" expectedData1 := myData diff --git a/policy_gorillamux_test.go b/policy_gorillamux_test.go index 41d5e206..0c03a686 100644 --- a/policy_gorillamux_test.go +++ b/policy_gorillamux_test.go @@ -119,8 +119,6 @@ func TestGorillaMuxSimpleParty(t *testing.T) { } app.Config.VHost = "0.0.0.0:" + strconv.Itoa(getRandomNumber(2222, 2399)) - // app.Config.Tester.Debug = true - // app.Config.Tester.ExplicitURL = true e := httptest.New(app, t) request := func(reqPath string) { diff --git a/policy_httprouter_test.go b/policy_httprouter_test.go index 5641a1d3..8a7f5675 100644 --- a/policy_httprouter_test.go +++ b/policy_httprouter_test.go @@ -134,8 +134,6 @@ func TestHTTPRouterSimpleParty(t *testing.T) { } app.Config.VHost = "0.0.0.0:" + strconv.Itoa(getRandomNumber(2222, 2399)) - // app.Config.Tester.Debug = true - // app.Config.Tester.ExplicitURL = true e := httptest.New(app, t) request := func(reqPath string) { @@ -199,8 +197,7 @@ func TestHTTPRouterParamDecodedDecodeURL(t *testing.T) { } func TestHTTPRouterRouteURLPath(t *testing.T) { - app := iris.New() - app.Adapt(httprouter.New()) + app := newHTTPRouterApp() app.None("/profile/:user_id/:ref/*anything", nil).ChangeName("profile") app.Boot() @@ -213,8 +210,7 @@ func TestHTTPRouterRouteURLPath(t *testing.T) { } func TestHTTPRouterFireMethodNotAllowed(t *testing.T) { - app := iris.New() - app.Adapt(httprouter.New()) + app := newHTTPRouterApp() app.Config.FireMethodNotAllowed = true h := func(ctx *iris.Context) { ctx.WriteString(ctx.Method()) @@ -235,3 +231,18 @@ func TestHTTPRouterFireMethodNotAllowed(t *testing.T) { e.POST("/mypath").Expect().Status(iris.StatusMethodNotAllowed).Body().Equal("Hello from my custom 405 page") } + +func TestHTTPRouterRegexMiddleware(t *testing.T) { + app := newHTTPRouterApp() + + app.Get("/users/:userid", app.Regex("userid", "[0-9]+$"), func(ctx *iris.Context) {}) + app.Get("/profile/:username", app.Regex("username", "[a-zA-Z]+$"), func(ctx *iris.Context) {}) + + e := httptest.New(app, t) + + e.GET("/users/42").Expect().Status(iris.StatusOK) + e.GET("/users/sarantaduo").Expect().Status(iris.StatusNotFound) + + e.GET("/profile/gerasimosmaropoulos").Expect().Status(iris.StatusOK) + e.GET("/profile/anumberof42").Expect().Status(iris.StatusNotFound) +} diff --git a/router.go b/router.go index e639b00d..a2774e47 100644 --- a/router.go +++ b/router.go @@ -4,6 +4,7 @@ import ( "net/http" "os" "path" + "regexp" "strings" "time" @@ -130,6 +131,66 @@ type Router struct { relativePath string } +// Regex takes pairs with the named path (without symbols) following by its expression +// and returns a middleware which will do a pure but effective validation using the regexp package. +// +// Note: '/adaptors/gorillamux' already supports regex path validation. +// It's useful while the developer uses the '/adaptors/httprouter' instead. +func (s *Framework) Regex(pairParamExpr ...string) HandlerFunc { + srvErr := func(ctx *Context) { + ctx.EmitError(StatusInternalServerError) + } + + wp := s.policies.RouterReversionPolicy.WildcardPath + if wp == nil { + s.Log(ProdMode, "expr cannot be used when a router policy is missing\n"+errRouterIsMissing.Format(s.Config.VHost).Error()) + return srvErr + } + + if len(pairParamExpr)%2 != 0 { + s.Log(ProdMode, + "regexp expr pre-compile error: the correct format is paramName, expression"+ + "paramName2, expression2. The len should be %2==0") + return srvErr + } + pairs := make(map[string]*regexp.Regexp, len(pairParamExpr)/2) + + for i := 0; i < len(pairParamExpr)-1; i++ { + expr := pairParamExpr[i+1] + r, err := regexp.Compile(expr) + if err != nil { + s.Log(ProdMode, "expr: regexp failed on: "+expr+". Trace:"+err.Error()) + return srvErr + } + + pairs[pairParamExpr[i]] = r + i++ + } + + // return the middleware + return func(ctx *Context) { + for k, v := range pairs { + pathPart := ctx.Param(k) + if pathPart == "" { + // take care, the router already + // does the param validations + // so if it's empty here it means that + // the router has label it as optional. + // so we skip it, and continue to the next. + continue + } + // the improtant thing: + // if the path part didn't match with the relative exp, then fire status not found. + if !v.MatchString(pathPart) { + ctx.EmitError(StatusNotFound) + return + } + } + // otherwise continue to the next handler... + ctx.Next() + } +} + var ( // errDirectoryFileNotFound returns an error with message: 'Directory or file %s couldn't found. Trace: +error trace' errDirectoryFileNotFound = errors.New("Directory or file %s couldn't found. Trace: %s")