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 SDO如何更改STM32的波特率
  • ¥15 求红米或者任意工作安卓机ro,ot之后劫c ,h ,i摄像头, 实现无人直播瞒过抖音直播助手的教程, 或者远程 协助也可以。
  • ¥15 elasticsearch
  • ¥15 uniapp的uni-datetime-picker组件在ios端不适配
  • ¥15 前端 uniapp App端在离线状态如何使用modbus 连接手机蓝牙进行读写操控机器?
  • ¥15 SQL语句根据字段自动生成行
  • ¥500 “掌声响起来”软件(不确定性人工智能)
  • ¥500 我要找大模型一体机产品手册和应用案例
  • ¥20 关于游戏c++语言代码问题
  • ¥15 如何制作永久二维码,最好是微信也可以扫开的。(相关搜索:管理系统)