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 乌班图ip地址配置及远程SSH
  • ¥15 怎么让点阵屏显示静态爱心,用keiluVision5写出让点阵屏显示静态爱心的代码,越快越好
  • ¥15 PSPICE制作一个加法器
  • ¥15 javaweb项目无法正常跳转
  • ¥15 VMBox虚拟机无法访问
  • ¥15 skd显示找不到头文件
  • ¥15 机器视觉中图片中长度与真实长度的关系
  • ¥15 fastreport table 怎么只让每页的最下面和最顶部有横线
  • ¥15 java 的protected权限 ,问题在注释里
  • ¥15 这个是哪里有问题啊?