go実践21クッキーとセッションの使用

10319 ワード

goクッキーとセッションを使う 
ディレクトリ構造は次のとおりです. 
プロジェクトルートディレクトリはgowebです
gowebのディレクトリ構造├——session                                # セッション構成ディレクトリ     ├── memorysession.go       # メモリセッション実装ファイル‖     ├── session.go                    # session底├——testsession.go                     # 業務制御—————————————————————— 
次に、ディレクトリ構造に基づいて、フォルダとファイルを上から下に作成します.
フォルダとファイルの作成 goweb/session/memorysession.go ,memorysession.goの内容は次のとおりです.
package session

import (
	"container/list"
	"sync"
	"time"
)

type ProviderStruct struct {
	lock sync.Mutex  //   
	sessions map[string]*list.Element //       
	list *list.List //   gc
}

var ps = &ProviderStruct{list:list.New()}

type SessionStore struct {
	sid string //session id     
	timeAccessed time.Time //      
	value map[interface{}]interface{} //session       
}

func (st *SessionStore) Set(key,value interface{}) error{
	st.value[key] = value
	ps.SessionUpdate(st.sid)
	return nil
}

func (st *SessionStore) Get(key interface{}) interface{}{
	ps.SessionUpdate(st.sid)
	if v,ok := st.value[key]; ok{
		return v
	}else{
		return nil
	}
	return nil
}

func (st *SessionStore) Delete(key interface{}) error{
	delete(st.value, key)
	ps.SessionUpdate(st.sid)
	return nil
}

func (st *SessionStore) SessionID() string{
	return st.sid
}

func (ps *ProviderStruct) SessionInit(sid string) (Session,error){
	ps.lock.Lock()
	defer ps.lock.Unlock()
	v := make(map[interface{}]interface{},0)
	newsess := &SessionStore{sid:sid,timeAccessed:time.Now(),value:v}
	element := ps.list.PushBack(newsess)
	ps.sessions[sid] = element
	return newsess,nil
}

func (ps *ProviderStruct) SessionRead(sid string) (Session,error){
	if element,ok := ps.sessions[sid];ok{
		return element.Value.(*SessionStore),nil
	}else{
		sess,err := ps.SessionInit(sid)
		return sess,err
	}
	return nil,nil
}

func (ps *ProviderStruct) SessionDestroy(sid string) error{
	if element,ok := ps.sessions[sid];ok{
		delete(ps.sessions,sid)
		ps.list.Remove(element)
		return nil
	}
	return nil
}

func (ps *ProviderStruct) SessionGC(maxlifetime int64){
	ps.lock.Lock()
	defer ps.lock.Unlock()
	for{
		element := ps.list.Back()
		if element == nil{
			break
		}
		if (element.Value.(*SessionStore).timeAccessed.Unix() + maxlifetime) < time.Now().Unix(){
			ps.list.Remove(element)
			delete(ps.sessions,element.Value.(*SessionStore).sid)
		}else{
			break
		}
	}
}

func (ps *ProviderStruct) SessionUpdate(sid string) error{
	ps.lock.Lock()
	defer ps.lock.Unlock()
	if element,ok := ps.sessions[sid];ok{
		element.Value.(*SessionStore).timeAccessed = time.Now()
		ps.list.MoveToFront(element)
		return nil
	}
	return nil
}

func init(){
	ps.sessions = make(map[string]*list.Element,0)
	Register("memory",ps)
}

フォルダとファイルの作成 goweb/session/session.go ,session.goの内容は以下の通りです.
package session

import (
	"sync"
	"time"
	"encoding/base64"
	"math/rand"
	"net/http"
	"net/url"
)
/*
session     
·  session    
·  sessionid       
·         session
·session    (       、  、    )
·session     
*/

//session   
type Manager struct {
	cookieName string //cookie   
	lock sync.Mutex //cookie  
	provider Provider //cookie    
	maxLifeTime int64 //cookie       
}

//   Provider   ,    session          
type Provider interface {
	//SessionInit     Session     ,    Session   
	SessionInit(sid string)(Session,error)
	//SessionRead     sid    Session   ,     ,    sid      SessionInit            Session   
	SessionRead(sid string)(Session,error)
	//SessionDestroy       sid    Session   
	SessionDestroy(sid string) error
	//SessionGC   maxLifeTime         
	SessionGC(maxLifeTime int64)
}

//Session           、   、      sessionid
type Session interface {
	Set(key,value interface{}) error
	Get(key interface{}) interface{}
	Delete(key interface{}) error
	SessionID() string
}

var provides = make(map[string]Provider)

//  manager
func NewManager(provideName,cookieName string,maxLifeTime int64) (*Manager,error){
	provider,ok := provides[provideName]
	if !ok {
		panic("session: unknown provide "+provideName+"(forgotten import?)")
	}
	return &Manager{provider:provider,cookieName:cookieName,maxLifeTime:maxLifeTime},nil
}

//    ,    session    
//           ,     nil ,     
func Register(name string,provider Provider){
	if provider == nil{
		panic("session:Register provider is nil")
	}
	if _,dup := provides[name];dup{
		panic("session:Register called twice for provider "+name)
	}
	provides[name] = provider

}

//      SessionID
func (manager *Manager) sessionId() string{
	b := make([]byte,32)
	if _,err := rand.Read(b);err != nil{
		return ""
	}
	return base64.URLEncoding.EncodeToString(b)
}

func (manager *Manager) SessionStart(w http.ResponseWriter,r *http.Request)(session Session){
	manager.lock.Lock()
	defer manager.lock.Unlock()
	cookie,err := r.Cookie(manager.cookieName)
	if err != nil || cookie.Value == ""{
		sid := manager.sessionId()
		session,_ = manager.provider.SessionInit(sid)
		cookie := http.Cookie{
			Name:manager.cookieName,
			Value:url.QueryEscape(sid),
			Path:"/",
			HttpOnly:true,
			//  httpOnly  (  :Cookie HttpOnly  ,         HTTP(  HTTPS)      Cookie。
			//    HttpOnly   Cookie,     HTTP     ,      JavaScript(  ,   document.cookie),
			//   ,         (           )     Cookie。   Facebook   Google        HttpOnly  。)
			MaxAge: int(manager.maxLifeTime),
		}
		http.SetCookie(w,&cookie)
	}else{
		sid,_ := url.QueryUnescape(cookie.Value)
		session,_ = manager.provider.SessionRead(sid)
	}
	return
}

func (manager *Manager) SessionDestroy(w http.ResponseWriter,r *http.Request){
	cookie,err := r.Cookie(manager.cookieName)
	if err != nil || cookie.Value == ""{
		return
	}else{
		manager.lock.Lock()
		defer manager.lock.Unlock()
		manager.provider.SessionDestroy(cookie.Value)
		expiration := time.Now()
		cookie := http.Cookie{
			Name:manager.cookieName,
			Path:"/",
			HttpOnly:true,
			Expires:expiration,
			MaxAge:-1,
		}
		http.SetCookie(w,&cookie)
	}
}

func (manager *Manager) GC(){
	manager.lock.Lock()
	defer manager.lock.Unlock()
	manager.provider.SessionGC(manager.maxLifeTime)
	time.AfterFunc(time.Duration(manager.maxLifeTime),func(){
		manager.GC()
	})
}

フォルダとファイルの作成 goweb/testsession.go ,testsession.goの内容は次のとおりです.
package main

import (
	"crypto/md5"
	"io"

	"fmt"
	"log"
	"time"
	"net/http"
	"html/template"

	"goweb/session"
)

/*
session     
·  session    
·  sessionid       
·         session
·session    (       、  、    )
·session     
*/

var globalSessions *session.Manager

//     ,  main    
func init(){
	//   session ,  memory   session ,
	globalSessions,_ = session.NewManager("memory","gosessionid",8)
	fmt.Printf("globalSessions: %+v 
",globalSessions) // Gc session go globalSessions.GC() } // func sayhelloName(w http.ResponseWriter,r *http.Request) { // w fmt.Fprintf(w,"hello testsession.go") } // session func testSession(w http.ResponseWriter, r *http.Request) { fmt.Printf("globalSessions: %+v
",globalSessions) sess := globalSessions.SessionStart(w, r) r.ParseForm() if r.Method == "GET" { t, _ := template.New("foo").Parse(`{{define "T"}}hello,{{.}}!{{end}} username: {{.UserName}} token: {{.Token}} `) err := t.ExecuteTemplate(w,"T",template.HTML("alert('you have been testSession')")) if err != nil{ log.Println(err) } w.Header().Set("Content-Type", "text/html") // token , session token := uniqueToken() sess.Set("token",token) // type Result struct { UserName interface{} Token interface{} } res := Result{ UserName: sess.Get("username"), Token: token, } // t.Execute(w,res) } else { sess_token := sess.Get("token") //token := r.Form["token"] // token token := uniqueToken() if sess_token!=token{ // fmt.Fprintf(w,"token ") return } sess.Set("username", "testsession") http.Redirect(w, r, "/", 302) } } func countSession(w http.ResponseWriter,r *http.Request){ sess := globalSessions.SessionStart(w, r) createtime := sess.Get("createtime") if createtime == nil{ sess.Set("createtime",time.Now().Unix()) }else if(createtime.(int64) + 3600) < (time.Now().Unix()){ // SID // 3600 session , globalSessions.SessionDestroy(w,r) sess = globalSessions.SessionStart(w,r) } ct := sess.Get("countnum") if ct == nil{ sess.Set("countnum",1) }else { sess.Set("countnum",(ct.(int) + 1)) } t, _ := template.New("foo").Parse(`{{define "T"}}hello,{{.}}!{{end}} refresh count:{{.}} `) err := t.ExecuteTemplate(w,"T",template.HTML("alert('you have been countSession')")) if err != nil{ log.Println(err) } w.Header().Set("Content-Type","text/html") t.Execute(w,sess.Get("countnum")) } // token func uniqueToken() string{ h := md5.New() salt:="sessionss%^7&8888" io.WriteString(h,salt+time.Now().String()) token:=fmt.Sprintf("%x",h.Sum(nil)) return token } func main() { http.HandleFunc("/",sayhelloName) // http.HandleFunc("/test",testSession) // session http.HandleFunc("/count",countSession) // session err := http.ListenAndServe(":6665",nil) // if err != nil{ log.Fatal("ListenAndServe:",err) } }

サーバの起動:
[root@izj6c4jirdug8kh3uo6rdez goweb]# go mod init goweb
[root@izj6c4jirdug8kh3uo6rdez goweb]# go run testsession.go
globalSessions: &{cookieName:gosessionid lock:{state:0 sema:0} provider:0xaf94a0 maxLifeTime:8} 

トップページへのアクセス:http://daily886.com:6665/
セッションへのアクセス:http://daily886.com:6665/test
セッション統計へのアクセス:http://daily886.com:6665/count
 
参照先:https://www.golang123.com/book/9?chapterID=171
参照先:https://github.com/astaxie/session