| 
          package main | 
        
        
           | 
          
 | 
        
        
           | 
          import ( | 
        
        
           | 
          	"crypto/rand" | 
        
        
           | 
          	"fmt" | 
        
        
           | 
          	"log" | 
        
        
           | 
          	"net/http" | 
        
        
           | 
          	"sync" | 
        
        
           | 
          	"time" | 
        
        
           | 
          ) | 
        
        
           | 
          
 | 
        
        
           | 
          var sessions = make(map[string]time.Time) | 
        
        
           | 
          var sessionLock sync.RWMutex | 
        
        
           | 
          
 | 
        
        
           | 
          func init() { | 
        
        
           | 
          	go expireSessions() | 
        
        
           | 
          } | 
        
        
           | 
          
 | 
        
        
           | 
          func main() { | 
        
        
           | 
          	http.HandleFunc("/", index) | 
        
        
           | 
          	http.HandleFunc("/login", login) | 
        
        
           | 
          	http.ListenAndServe(":8000", nil) | 
        
        
           | 
          } | 
        
        
           | 
          
 | 
        
        
           | 
          func index(w http.ResponseWriter, r *http.Request) { | 
        
        
           | 
          	sessionID := getSessionID(r) | 
        
        
           | 
          
 | 
        
        
           | 
          	if !sessionValid(sessionID) { | 
        
        
           | 
          		anon(w, r) | 
        
        
           | 
          		return | 
        
        
           | 
          	} | 
        
        
           | 
          
 | 
        
        
           | 
          	w.Write(page(fmt.Sprintf("You are logged in: %q", sessionID))) | 
        
        
           | 
          } | 
        
        
           | 
          
 | 
        
        
           | 
          func anon(w http.ResponseWriter, r *http.Request) { | 
        
        
           | 
          	w.Write(page("You are not logged in.")) | 
        
        
           | 
          } | 
        
        
           | 
          
 | 
        
        
           | 
          func login(w http.ResponseWriter, r *http.Request) { | 
        
        
           | 
          	p := r.FormValue("password") | 
        
        
           | 
          	if p == "secret" { | 
        
        
           | 
          		sessionID, err := UUID4() | 
        
        
           | 
          		if err != nil { | 
        
        
           | 
          			log.Printf("Failed to create UUID: %s\n", err) | 
        
        
           | 
          			http.Error(w, "internal error", http.StatusInternalServerError) | 
        
        
           | 
          			return | 
        
        
           | 
          		} | 
        
        
           | 
          		c := http.Cookie{Name: "SessionID", Value: sessionID} | 
        
        
           | 
          		http.SetCookie(w, &c) | 
        
        
           | 
          		createSession(sessionID) | 
        
        
           | 
          		http.Redirect(w, r, "/", http.StatusFound) | 
        
        
           | 
          		return | 
        
        
           | 
          	} | 
        
        
           | 
          	w.Write(page("Incorrect password.")) | 
        
        
           | 
          } | 
        
        
           | 
          
 | 
        
        
           | 
          func sessionValid(sessionID string) bool { | 
        
        
           | 
          	sessionLock.RLock() | 
        
        
           | 
          	defer sessionLock.RUnlock() | 
        
        
           | 
          
 | 
        
        
           | 
          	_, found := sessions[sessionID] | 
        
        
           | 
          	return found | 
        
        
           | 
          } | 
        
        
           | 
          
 | 
        
        
           | 
          func expireSessions() { | 
        
        
           | 
          	for { | 
        
        
           | 
          		time.Sleep(time.Second) | 
        
        
           | 
          		sessionLock.RLock() | 
        
        
           | 
          		for k, v := range sessions { | 
        
        
           | 
          			if v.Before(time.Now()) { | 
        
        
           | 
          				go deleteSession(k) | 
        
        
           | 
          			} | 
        
        
           | 
          		} | 
        
        
           | 
          		sessionLock.RUnlock() | 
        
        
           | 
          	} | 
        
        
           | 
          } | 
        
        
           | 
          
 | 
        
        
           | 
          func deleteSession(key string) { | 
        
        
           | 
          	sessionLock.Lock() | 
        
        
           | 
          	defer sessionLock.Unlock() | 
        
        
           | 
          	delete(sessions, key) | 
        
        
           | 
          } | 
        
        
           | 
          func createSession(key string) { | 
        
        
           | 
          	sessionLock.Lock() | 
        
        
           | 
          	defer sessionLock.Unlock() | 
        
        
           | 
          	sessions[key] = time.Now().Add(time.Second * 5) | 
        
        
           | 
          } | 
        
        
           | 
          
 | 
        
        
           | 
          func UUID4() (string, error) { | 
        
        
           | 
          	b := make([]byte, 16) | 
        
        
           | 
          
 | 
        
        
           | 
          	_, err := rand.Read(b[:]) | 
        
        
           | 
          	if err != nil { | 
        
        
           | 
          		return "", err | 
        
        
           | 
          	} | 
        
        
           | 
          
 | 
        
        
           | 
          	// Set the two most significant bits (bits 6 and 7) of the | 
        
        
           | 
          	// clock_seq_hi_and_reserved to zero and one, respectively. | 
        
        
           | 
          	b[8] = (b[8] | 0x40) & 0x7F | 
        
        
           | 
          
 | 
        
        
           | 
          	// Set the four most significant bits (bits 12 through 15) of the | 
        
        
           | 
          	// time_hi_and_version field to the 4-bit version number. | 
        
        
           | 
          	b[6] = (b[6] & 0xF) | (4 << 4) | 
        
        
           | 
          
 | 
        
        
           | 
          	// Return unparsed version of the generated UUID sequence. | 
        
        
           | 
          	return fmt.Sprintf("%x-%x-%x-%x-%x", | 
        
        
           | 
          		b[0:4], b[4:6], b[6:8], b[8:10], b[10:]), nil | 
        
        
           | 
          } | 
        
        
           | 
          
 | 
        
        
           | 
          func getSessionID(r *http.Request) string { | 
        
        
           | 
          	c, err := r.Cookie("SessionID") | 
        
        
           | 
          	if err != nil { | 
        
        
           | 
          		log.Printf("Error getting session cookie: %s\n", err) | 
        
        
           | 
          		return "" | 
        
        
           | 
          	} | 
        
        
           | 
          	return c.Value | 
        
        
           | 
          } | 
        
        
           | 
          
 | 
        
        
           | 
          var template = `<!DOCTYPE html> | 
        
        
           | 
          <html> | 
        
        
           | 
          	<head> | 
        
        
           | 
          		<meta charset="UTF-8"> | 
        
        
           | 
          		<title>Login demo</title> | 
        
        
           | 
          		<meta name="viewport" content="width-device-width, initial-scale=1"> | 
        
        
           | 
          	</head> | 
        
        
           | 
          	<body> | 
        
        
           | 
          		<div> | 
        
        
           | 
          			<p>%s</p> | 
        
        
           | 
          			<p><a href="/">home</a></p> | 
        
        
           | 
          			<p><a href="/login?password=secret">log in</a></p> | 
        
        
           | 
          		</div> | 
        
        
           | 
          	</body> | 
        
        
           | 
          </html> | 
        
        
           | 
          ` | 
        
        
           | 
          
 | 
        
        
           | 
          func page(content string) []byte { | 
        
        
           | 
          	return []byte(fmt.Sprintf(template, content)) | 
        
        
           | 
          } |