github.com/bitcubate/cryptojournal@v1.2.5-0.20171102134152-f578b3d788ab/src/lib/stats/stats.go (about)

     1  package stats
     2  
     3  import (
     4  	"crypto/sha1"
     5  	"encoding/base64"
     6  	"fmt"
     7  	"net/http"
     8  	"strings"
     9  	"sync"
    10  	"time"
    11  )
    12  
    13  // Put this in a separate package called stats
    14  
    15  // PurgeInterval is the interval at which users are purged from the current list
    16  var PurgeInterval = time.Minute * 5
    17  
    18  // identifiers holds a hash of anonymised user records
    19  // obviously an in-memory store is not suitable for very large sites
    20  // but for smaller sites with a few hundred concurrent users it's fine
    21  var identifiers = make(map[string]time.Time)
    22  var mu sync.RWMutex
    23  
    24  // RegisterHit registers a hit and ups user count if required
    25  func RegisterHit(r *http.Request) {
    26  
    27  	// Use UA as well as ip for unique values per browser session
    28  	ua := r.Header.Get("User-Agent")
    29  	// Ignore obvious bots (Googlebot etc)
    30  	if strings.Contains(ua, "bot") {
    31  		return
    32  	}
    33  	// Ignore requests for xml (assumed to be feeds or sitemap)
    34  	if strings.HasSuffix(r.URL.Path, ".xml") {
    35  		return
    36  	}
    37  
    38  	// Extract the IP from the address
    39  	ip := r.RemoteAddr
    40  	forward := r.Header.Get("X-Forwarded-For")
    41  	if len(forward) > 0 {
    42  		ip = forward
    43  	}
    44  
    45  	// Hash for anonymity in our store
    46  	hasher := sha1.New()
    47  	hasher.Write([]byte(ip))
    48  	hasher.Write([]byte(ua))
    49  	id := base64.URLEncoding.EncodeToString(hasher.Sum(nil))
    50  
    51  	// Insert the entry with current time
    52  	mu.Lock()
    53  	identifiers[id] = time.Now()
    54  	mu.Unlock()
    55  }
    56  
    57  // HandleUserCount serves a get request at /stats/users/count
    58  func HandleUserCount(w http.ResponseWriter, r *http.Request) error {
    59  
    60  	// Render json of our count for the javascript to display
    61  	mu.RLock()
    62  	json := fmt.Sprintf("{\"users\":%d}", len(identifiers))
    63  	mu.RUnlock()
    64  	_, err := w.Write([]byte(json))
    65  	return err
    66  }
    67  
    68  // UserCount returns a count of users in the last 5 minutes
    69  func UserCount() int {
    70  	mu.RLock()
    71  	defer mu.RUnlock()
    72  	return len(identifiers)
    73  }
    74  
    75  // Clean up users list at intervals
    76  func init() {
    77  	purgeUsers()
    78  }
    79  
    80  // purgeUsers clears the users list of users who last acted PurgeInterval ago
    81  func purgeUsers() {
    82  
    83  	mu.Lock()
    84  	for k, v := range identifiers {
    85  		purgeTime := time.Now().Add(-PurgeInterval)
    86  		if v.Before(purgeTime) {
    87  			delete(identifiers, k)
    88  		}
    89  	}
    90  	mu.Unlock()
    91  
    92  	time.AfterFunc(time.Second*60, purgeUsers)
    93  
    94  	//	fmt.Printf("Purged users:%d", UserCount())
    95  }