dongqiyou0303 2015-04-17 02:54
浏览 42
已采纳

通过会话变量在Golang layout.tpl中有条件地呈现HTML

I use Gorilla sessions (via negroni-sessions) to store my user sessions in cookies. I also use github.com/unrolled/render for my HTML template rendering:

main.go:

    package main

    import (
        ...
        "github.com/codegangsta/negroni"
        "github.com/goincremental/negroni-sessions"
        "github.com/goincremental/negroni-sessions/cookiestore"
        "github.com/julienschmidt/httprouter"
        "github.com/unrolled/render"
        ...
    )

    func init() {
        ...
        ren = render.New(render.Options{
            Directory:     "templates",
            Layout:        "layout",
            Extensions:    []string{".html"},
            Funcs:         []template.FuncMap{TemplateHelpers}, 
            IsDevelopment: false,
        })
        ...
    }

    func main() {
        ...
        router := httprouter.New()
        router.GET("/", HomeHandler)

        // Add session store
        store := cookiestore.New([]byte("my password"))
        store.Options(sessions.Options{
            //MaxAge: 1200,
            Domain: "",
            Path:   "/",
        })

        n := negroni.New(
            negroni.NewRecovery(),
            sessions.Sessions("cssession", store),
            negroni.NewStatic(http.Dir("../static")), 
        )

        n.UseHandler(router)
        n.Run(":9000")

    }

As you can see above, I use a layout.html master HTML template which is included when any page renders, like my home page:

    package main

    import (
        "html/template"
        "github.com/julienschmidt/httprouter"
    )

    func HomeHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {

        var model = struct {
            CatalogPicks   []PromotionalModelList
            ClearanceItems []Model
        }{
            CatalogPicks:   GetCatalogPicks(),
            ClearanceItems: GetClearanceItems(),
        }

        ren.HTML(w, http.StatusOK, "home", model)
    }

In my layout.html master HTML template, I want to render an admin menu but only if the current user is an admin:

layout.html:

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
      <title>{{ template "title" . }}</title>
        ...
  </head>

  <body>
    ...
    <!--Main Menu-->
      <nav class="menu">
         <ul class="catalog">
             <li class="has-submenu">
                {{ RenderMenuCategories }}
             </li>
             <li><a href="javascript:void(0)">Blog</a></li>
             <li><a href="javascript:void(0)">Company</a></li>

             {{ RenderAdminMenu }}

         </ul>
      </nav>

...

My issue is that the above template helper function RenderAdminMenu() doesn't have access to the HTTP Request object and therefore cannot access the User session object to determine if the user is admin.

I can pass the User object into the template context via the Home page handler, and use an if statement RenderAdminMenu() function, like this

{{ if .User.IsAdmin }}
   {{ RenderAdminMenu }}
{{ end }}

...but since I am using a master template, I would have to do that from every web page on the site. Is there a more efficient way?

I was thinking perhaps there might be a way to access some kind of global Context object from within RenderAdminMenu() (or layout.html) which contains the Request details (like you can in ASP.NET)

  • 写回答

2条回答 默认 最新

  • dousa1630 2015-04-17 04:36
    关注

    There's a few things you need to do to tie this together. I'm not going to show a complete example as it would be both fairly lengthy and may not match your code (which you haven't posted). It will contain the basic building blocks though: if you get stuck, come back with a direct question and a code snippet and you'll get a more direct answer :)

    1. Write some middleware or logic in a [login] handler that saves the user data in the session when a user logs in. A userID, email and admin boolean value would be sufficient. e.g.

      // In your login handler, once you've retrieved the user &
      // matched their password hash (scrypt, of course!) from the DB.
      session.Values["user"] = &youruserobject
      err := session.Save(r, w)
      if err != nil {
          // Throw a HTTP 500
      }
      

    Note: remember that you need to gob.Register(&youruserobject{}) as per the gorilla/sessions docs if you want to store your own types.

    1. Write a helper to type-assert your type when you pull it out of the session, e.g.

      var ErrInvalidUser= errors.New("invalid user stored in session")
      
      func GetUser(session *sessions.Session) (*User, error) {
          // You can make the map key a constant to avoid typos/errors
          user, ok := session.Values["user"].(*User)
          if !ok || user == nil {
              return nil, ErrInvalidUser
          }
          return user, nil
      }
      
      
      // Use it like this in a handler that serves user content
      session, err := store.Get("yoursessionname", r)
      if err != nil {
          // Throw a HTTP 500
      }
      
      user, err := GetUser(session)
         if err != nil {
          // Re-direct back to the login page or
          // show a HTTP 403 Forbidden, etc.
      }
      
    2. Write something to check if the returned user is an admin:

      func IsAdmin(user *User) bool {
              if user.Admin == true && user.ID != "" && user.Email != "" {
                      return true
              }
      
              return false
      }
      
    3. Pass that to the template:

       err := template.Execute(w, "sometemplate.html", map[string]interface{}{
              "admin": IsAdmin(user),
              "someotherdata": someStructWithData,
          }
      
       // In your template...
       {{ if .admin }}{{ template "admin_menu" }}{{ end }}
      

    Also make sure you're setting an authentication key for your session cookies (read the gorilla docs), preferably an encryption key, and that you're serving your site over HTTPS with the Secure: true flag set as well.

    Keep in mind that the above method is also simplified: if you de-flag a user as admin in your DB, the application will continue to detect them as an administrator for as long as their session lasts. By default this can be 7 days, so if you're in a risky environment where admin churn is a real problem, it may pay to have really short sessions OR hit the DB inside the IsAdmin function just to be safe. If it's a personal blog and it's just you, not so much.


    Added: If you want to pass the User object directly to the template, you can do that too. Note that it's more performant to do it in your handler/middleware than it is in the template logic. You also get the flexibility of more error handling, and the option of "bailing out" earlier - i.e. if the session contains nothing, you can fire up a HTTP 500 error rather than rendering half a template or having to put lots of logic in your template to handle nil data.

    You still need to store your User object (or equivalent) in the session, and retrieve it from session.Values before you can pass it to the template.

     func GetUser(r *http.Request) *User {
            session, err := store.Get("yoursessionname", r)
            if err != nil {
                // Throw a HTTP 500
            }
            if user, ok := session.Values["user"].(*User); ok {
                return user
            }
    
            return nil
     }
    
     // In the handler itself
     err := template.Execute(w, "sometemplate.html", map[string]interface{}{
            "user": GetUser(r),
            "someotherdata": someStructWithData,
            }
    
    
     // In your template...
     {{ if .User.admin }}{{ template "admin_menu" }}{{ end }}
    
    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 一个服务器已经有一个系统了如果用usb再装一个系统,原来的系统会被覆盖掉吗
  • ¥15 使用esm_msa1_t12_100M_UR50S蛋白质语言模型进行零样本预测时,终端显示出了sequence handled的进度条,但是并不出结果就自动终止回到命令提示行了是怎么回事:
  • ¥15 前置放大电路与功率放大电路相连放大倍数出现问题
  • ¥30 关于<main>标签页面跳转的问题
  • ¥80 部署运行web自动化项目
  • ¥15 腾讯云如何建立同一个项目中物模型之间的联系
  • ¥30 VMware 云桌面水印如何添加
  • ¥15 用ns3仿真出5G核心网网元
  • ¥15 matlab答疑 关于海上风电的爬坡事件检测
  • ¥88 python部署量化回测异常问题