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 使用C#,asp.net读取Excel文件并保存到Oracle数据库
  • ¥15 C# datagridview 单元格显示进度及值
  • ¥15 thinkphp6配合social login单点登录问题
  • ¥15 HFSS 中的 H 场图与 MATLAB 中绘制的 B1 场 部分对应不上
  • ¥15 如何在scanpy上做差异基因和通路富集?
  • ¥20 关于#硬件工程#的问题,请各位专家解答!
  • ¥15 关于#matlab#的问题:期望的系统闭环传递函数为G(s)=wn^2/s^2+2¢wn+wn^2阻尼系数¢=0.707,使系统具有较小的超调量
  • ¥15 FLUENT如何实现在堆积颗粒的上表面加载高斯热源
  • ¥30 虚心请教几个问题,小生先有礼了
  • ¥30 截图中的mathematics程序转换成matlab