How can I create separate route groups with different middleware in Goji (Golang)?

huangapple go评论81阅读模式
英文:

How can I create separate route groups with different middleware in Goji (Golang)?

问题

我正在使用Goji(https://github.com/zenazn/goji),并希望定义具有自己中间件的路由组。例如,所有位于/company下的路径应使用LDAP身份验证,并定义一个中间件来执行此操作。所有位于/external下的路径使用不同类型的身份验证,因此它们有不同的中间件定义。但这是一个在同一端口上提供的单个应用程序,所以我不想完全创建单独的Web服务 - 只有路径(和一些特定的路由)可能使用不同的中间件。

我看到的所有Goji示例都是为所有路由使用单一的中间件,所以我不确定如何以清晰的方式实现这一点。此外,如果我可以为路由组内的所有路由指定基本路径,类似于我在其他一些路由框架中看到的方式,那将是很好的。

我是否在Goji库(或通过扩展的net/http)中错过了允许我将路由分组并使每个组使用自己的中间件堆栈的功能?

我想要实现的效果类似于以下伪代码:

// 使用LDAP身份验证器:
// GET /company/employees
// 和
// POST /company/records
companyGroup = &RouteGroup{"basePath": "/company"}
companyGroup.Use(LDAPAuthenticator)
companyGroup.Add(goji.Get("/employees", Employees.ListAll))
companyGroup.Add(goji.Post("/records", Records.Create))

// 使用特殊的外部用户身份验证器:
// GET /external/products
externalGroup = &RouteGroup{"basePath": "/external"}
externalGroup.Use(ExternalUserAuthenticator)
externalGroup.Add(goji.Get("/products", Products.ListAll))

英文:

I am using Goji (https://github.com/zenazn/goji) and would like to define groups of routes that have their own middleware. For example, all paths under /company should use an LDAP authentication and have a middleware defined to do this. All paths under /external use a different type of authentication so they have a different middleware definition. But this is a single application served on the same port, so I don't want to create separate web services altogether -- just the paths (and some specific routes) may use different middleware.

All the examples I've seen with Goji are using a single set of middleware for all routes, so I am not sure how to accomplish this in a clean way. Additionally it would be nice if I could specify a base path for all routes within a route group, similar to how I've seen in some other routing frameworks.

Am I missing this functionality in the Goji library (or net/http by extension) that allows me to group routes together and have each group use its own middleware stack?

What I would like to achieve is something like this (psedocode):

// Use an LDAP authenticator for:
// GET /company/employees
// and
// POST /company/records
companyGroup = &RouteGroup{"basePath": "/company"}
companyGroup.Use(LDAPAuthenticator)
companyGroup.Add(goji.Get("/employees", Employees.ListAll))
companyGroup.Add(goji.Post("/records", Records.Create))

// Use a special external user authenticator for: GET /external/products
externalGroup = &RouteGroup{"basePath": "/external"}
externalGroup.Use(ExternalUserAuthenticator)
externalGroup.Add(goji.Get("/products", Products.ListAll))

答案1

得分: 8

你可以使用类似以下代码来解决你的问题:

// 使用LDAP认证器
companyGroup := web.New()
companyGroup.Use(LDAPAuthenticator)
companyGroup.Get("/company/employees", Employees.ListAll)
companyGroup.Post("/company/records", Records.Create)
goji.Handle("/company/*", companyGroup)

// 使用特殊的外部用户认证器来处理:GET /external/products
externalGroup := web.New()
externalGroup.Use(ExternalUserAuthenticator)
externalGroup.Get("/external/products", Products.ListAll)
goji.Handle("/external/*", externalGroup)

你需要为每个组分配一个独立的 web 实例。请记住,在组成员中需要指定完整的路径。

英文:

You should be able to solve your problem with something like this:

// Use an LDAP authenticator 
companyGroup := web.New()
companyGroup.Use(LDAPAuthenticator)
companyGroup.Get("/company/employees", Employees.ListAll)
companyGroup.Post("/company/records", Records.Create)
goji.Handle("/company/*", companyGroup)

// Use a special external user authenticator for: GET /external/products
externalGroup := web.New()
externalGroup.Use(ExternalUserAuthenticator)
externalGroup.Get("/external/products", Products.ListAll)
goji.Handle("/external/*", externalGroup)

You need to give each group its own web. Just keep in mind you need to specify the full path within the group members.

答案2

得分: 4

Greg R的回答总结得很好(也是正确的答案),但我将向您展示一种方法,让您可以“避免”(作弊!)指定完整的路由。

Goji的路由器之所以快速,部分原因是它在启动时编译了所有内容,所以路由需要知道它们的完整路径 - 但是我们可以通过编写接受“前缀”并返回路由器的函数在更高的级别提供它。

package main

import (
	"github.com/zenazn/goji/graceful"
	"github.com/zenazn/goji/web"
	"net/http"
)

func GetCompanyRoutes(prefix string) http.Handler {
	comp := web.New()
	comp.Use(SomeMiddleware)
	comp.Get(prefix+"/products", Products.ListAll)
	comp.Get(prefix+"/product/:id", Products.JustOne)
	comp.Get(prefix+"/product/delete", Products.Delete)

	return comp
}

// ... and a GetExternalRoutes with the same pattern

func main() {
	r := web.New()

	r.Get("/", IndexHandler)
	r.Handle("/company/*", GetCompanyRoutes("/company"))
	r.Handle("/external/*", GetExternalRoutes("/external"))

	graceful.Serve("localhost:8000", r)
}

由于所有内容都在启动时编译,所以不必担心字符串连接会影响路由性能。

我使用类似的模式,因为我的处理程序位于单独的包中 - 我的main包只是调用r.Handle("/admin/*", handlers.GetAdminRoutes("/admin"))。如果我想在以后更改URL结构,我只需将其更改为r.Handle("/newadminlocation/*", handlers.GetAdminRoutes("/newadminlocation"))即可。

英文:

Greg R's response sums it up nicely (and is the correct answer), but I'll show you an approach that lets you 'avoid' (cheat!) having to specify the full route.

Part of why Goji's router is fast is that it compiles everything on start-up, so routes need to know their full path - but we can provide that at a higher level by writing functions that take a "prefix" and return a router.

package main

import (
	"github.com/zenazn/goji/graceful"
	"github.com/zenazn/goji/web"
	"net/http"
)

func GetCompanyRoutes(prefix string) http.Handler {
	comp := web.New()
	comp.Use(SomeMiddleware)
	comp.Get(prefix+"/products", Products.ListAll)
	comp.Get(prefix+"/product/:id", Products.JustOne)
	comp.Get(prefix+"/product/delete", Products.Delete)

	return comp
}

// ... and a GetExternalRoutes with the same pattern

func main() {
	r := web.New()

	r.Get("/", IndexHandler)
	r.Handle("/company/*", GetCompanyRoutes("/company"))
	r.Handle("/external/*", GetExternalRoutes("/external"))

	graceful.Serve("localhost:8000", r)
}

Since this is all compiled at startup, there's no concern about the string concatenation impacting routing performance.

I use a similar pattern as my handlers reside in a separate package - my package main just calls r.Handle("/admin/*", handlers.GetAdminRoutes("/admin"). If I wanted to change the URL structure at a later date, I can just change it to r.Handle("/newadminlocation/*", handlers.GetAdminRoutes("/newadminlocation") instead.

答案3

得分: 3

根据Goji作者在这个已关闭的问题1上的建议,你可以创建一个SubRouter结构体,它扩展了web.Mux,允许你提供与web.Mux相同的API,并且使用一个中间件来去除子路由器的前缀,该中间件调用了Go的http.StripPrefix()函数。

上面的代码可以重写如下:

func GetCompanyRoutes() http.Handler {
    comp := web.New()
    comp.Use(SomeMiddleware)
    comp.Get("/products", Products.ListAll)
    comp.Get("/product/:id", Products.JustOne)
    comp.Get("/product/delete", Products.Delete)

    return comp
}

func main() {
    r := web.New()
    r.Get("/", IndexHandler)
    companySubRouter := NewSubRouter("/company", r)
    companySubRouter.Handle("/*", GetCompanyRoutes())
    externalSubRouter := NewSubRouter("/external", r)
    externalSubrouter.Handle("/*", GetExternalRoutes())

    graceful.Serve("localhost:8000", r)
}

NewSubRouter()的一个可能实现:

type SubRouter struct {
    *web.Mux
    prefix string
}

func NewSubRouter(prefix string, parent *web.Mux) *SubRouter {
    mux := web.New()
    // 我们希望前缀在调用http.StripPrefix()之前不带有"/*"后缀。
    prefix = strings.TrimRight(prefix, "/*")
    // 然而,我们将父路由器绑定到两个匹配项上:
    // 不带有"/*"后缀的前缀(精确匹配)
    parent.Handle(prefix, mux)
    // 并且匹配带有"/*"后缀的前缀,匹配"prefix/*",例如"prefix/subpath/a"
    parent.Handle(prefix+"/*", mux)
    mux.Use(func(c *web.C, handler http.Handler) http.Handler {
        return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
            // TODO 记录前缀和可能的祖先前缀..

            // 从请求的URL中去除前缀,以便后续的中间件处理
            strippedHandler := http.StripPrefix(prefix, handler)
            strippedHandler.ServeHTTP(rw, req)
        })
    })
    return &SubRouter{
        Mux:    mux,
        prefix: prefix,
    }
}

编辑:

  1. 我已经更新了上面的prefix <-> prefix+"/*"的方法,使其更加合理。请注意,调用此函数的调用者需要提供一个不带有尾部斜杠和星号的前缀。前导斜杠是可以的。

  2. 与上述方法相比,另一种方法是直接返回一个web.Mux(即return mux而不是return &SubRouter{...}),并且完全丢弃SubRouter结构体。这取决于调用此函数的调用者是否需要prefix字符串的值。

英文:

Following Goji's author suggestion on this [closed issue] 1, you can create a SubRouter struct which extends web.Mux, allowing you to offer the same API as web.Mux does, and in addition strip the prefix for your subrouter using a middleware which calls go's http.StripPrefix().

The code above could be re-written:

func GetCompanyRoutes() http.Handler {
    comp := web.New()
    comp.Use(SomeMiddleware)
    comp.Get(&quot;/products&quot;, Products.ListAll)
    comp.Get(&quot;/product/:id&quot;, Products.JustOne)
    comp.Get(&quot;/product/delete&quot;, Products.Delete)

    return comp
}

func main() {
    r := web.New()
    r.Get(&quot;/&quot;, IndexHandler)
    companySubRouter := NewSubRouter(&quot;/company&quot;, r)
    companySubRouter.Handle(&quot;/*&quot;, GetCompanyRoutes())
    externalSubRouter := NewSubRouter(&quot;/external&quot;, r)
    externalSubrouter.Handle(&quot;/*&quot;, GetExternalRoutes())

    graceful.Serve(&quot;localhost:8000&quot;, r)
}

A possible implementation for NewSubRouter():

type SubRouter struct {
    *web.Mux
    prefix string
}

func NewSubRouter(prefix string, parent *web.Mux) *SubRouter {
	mux := web.New()
	// we want prefix to be &#39;/*&#39;-suffix-free for http.StripPrefix() below.
	prefix = strings.TrimRight(prefix, &quot;/*&quot;)
	// however, we bind parent to match both:
	// *-free prefix (exact match)
	parent.Handle(prefix, mux)
	// and match with a &#39;/*&#39; suffix, matching &quot;prefix/*&quot;, e.g. &quot;prefix/subpath/a&quot;
	parent.Handle(prefix+&quot;/*&quot;, mux)
	mux.Use(func(c *web.C, handler http.Handler) http.Handler {
		return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
            // TODO record the prefix and possibly ancestors prefixes..

			// strip the prefix from the URLs in the request for the following middleware
			strippedHandler := http.StripPrefix(prefix, handler)
			strippedHandler.ServeHTTP(rw, req)
		})
	})
	return &amp;SubRouter{
		Mux:    mux,
		prefix: prefix,
	}
}

Edit:

  1. I've updated the prefix <-> prefix+&quot;/*&quot; approach above to be a bit sane-r. Note that callers to this function need to provide a trailing-slash and asterisk free prefix. leading slashes are ok.

  2. An alternative to the above is to return a straight web.Mux (i.e. return mux instead of return &amp;SubRouter{...} and discard the SubRouter struct altogether). It depends on whether the prefix string is of any value to the caller of this function.

huangapple
  • 本文由 发表于 2014年8月14日 09:21:04
  • 转载请务必保留本文链接:https://go.coder-hub.com/25298646.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定