Browse Source

Initial commit

Vova Tkach 6 years ago
parent
commit
ff59f89f6a
9 changed files with 390 additions and 0 deletions
  1. 4 0
      .gitignore
  2. 6 0
      Makefile
  3. 45 0
      main.go
  4. 22 0
      session/bool.go
  5. 22 0
      session/int.go
  6. 90 0
      session/session.go
  7. 179 0
      session/session_test.go
  8. 22 0
      session/string.go
  9. 0 0
      tmp/.keep

+ 4 - 0
.gitignore

@@ -10,3 +10,7 @@
 
 # Output of the go coverage tool, specifically when used with LiteIDE
 *.out
+
+golang-server-sessions
+tmp/*
+!tmp/.keep

+ 6 - 0
Makefile

@@ -0,0 +1,6 @@
+VERSION="1.0.0"
+
+default: test
+
+test:
+	@go test -v ./...

+ 45 - 0
main.go

@@ -0,0 +1,45 @@
+package main
+
+import (
+	"fmt"
+	"net/http"
+
+	"golang-server-sessions/session"
+)
+
+func main() {
+	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+		// Init session
+		sess := session.New(w, r, "./tmp")
+		defer sess.Close()
+
+		if r.URL.Path == "/" {
+			var counter int
+
+			// Get value or set default
+			if sess.IsSetInt("counter") {
+				counter = sess.GetInt("counter", 0)
+			} else {
+				counter = 0
+			}
+
+			// Increment value
+			counter++
+
+			// Update
+			sess.SetInt("counter", counter)
+
+			w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
+			w.Header().Set("Content-Type", "text/html")
+			w.Write([]byte(`
+				<div>Hello World!</div>
+				<div>Counter: ` + fmt.Sprintf("%d", counter) + `</div>
+			`))
+		} else {
+			w.WriteHeader(http.StatusNotFound)
+			w.Write([]byte(`<div>Error 404!</div>`))
+		}
+	})
+
+	http.ListenAndServe(":8080", nil)
+}

+ 22 - 0
session/bool.go

@@ -0,0 +1,22 @@
+package session
+
+func (this *session) IsSetBool(name string) bool {
+	if _, ok := this.v.Bool[name]; ok {
+		return true
+	} else {
+		return false
+	}
+}
+
+func (this *session) GetBool(name string, def bool) bool {
+	if v, ok := this.v.Bool[name]; ok {
+		return v
+	} else {
+		return def
+	}
+}
+
+func (this *session) SetBool(name string, value bool) {
+	this.v.Bool[name] = value
+	this.c = true
+}

+ 22 - 0
session/int.go

@@ -0,0 +1,22 @@
+package session
+
+func (this *session) IsSetInt(name string) bool {
+	if _, ok := this.v.Int[name]; ok {
+		return true
+	} else {
+		return false
+	}
+}
+
+func (this *session) GetInt(name string, def int) int {
+	if v, ok := this.v.Int[name]; ok {
+		return v
+	} else {
+		return def
+	}
+}
+
+func (this *session) SetInt(name string, value int) {
+	this.v.Int[name] = value
+	this.c = true
+}

+ 90 - 0
session/session.go

@@ -0,0 +1,90 @@
+package session
+
+import (
+	"crypto/sha1"
+	"encoding/json"
+	"fmt"
+	"math/rand"
+	"net/http"
+	"os"
+	"time"
+)
+
+type vars struct {
+	Bool   map[string]bool
+	Int    map[string]int
+	String map[string]string
+}
+
+type session struct {
+	w http.ResponseWriter
+	r *http.Request
+	d string
+	v *vars
+	c bool
+	i string
+}
+
+func New(w http.ResponseWriter, r *http.Request, tmpdir string) *session {
+	sess := session{w: w, r: r, d: tmpdir, v: &vars{}, c: false, i: ""}
+
+	cookie, err := r.Cookie("session")
+	if err == nil && len(cookie.Value) == 40 {
+		// Load from file
+		sess.i = cookie.Value
+		f, err := os.Open(sess.d + string(os.PathSeparator) + sess.i)
+		if err == nil {
+			defer f.Close()
+			dec := json.NewDecoder(f)
+			err = dec.Decode(&sess.v)
+			if err == nil {
+				return &sess
+			}
+		}
+	} else {
+		// Create new
+		rand.Seed(time.Now().Unix())
+
+		sign := r.RemoteAddr + r.Header.Get("User-Agent") + fmt.Sprintf("%d", int64(time.Now().Unix())) + fmt.Sprintf("%d", int64(rand.Intn(9999999-99)+99))
+		sess.i = fmt.Sprintf("%x", sha1.Sum([]byte(sign)))
+
+		http.SetCookie(w, &http.Cookie{
+			Name:     "session",
+			Value:    sess.i,
+			Path:     "/",
+			Expires:  time.Now().Add(365 * 24 * time.Hour),
+			HttpOnly: true,
+		})
+	}
+
+	// Init empty
+	sess.v = &vars{
+		Bool:   map[string]bool{},
+		Int:    map[string]int{},
+		String: map[string]string{},
+	}
+	sess.c = true
+
+	return &sess
+}
+
+func (this *session) Close() bool {
+	if !this.c {
+		return false
+	}
+
+	r, err := json.Marshal(this.v)
+	if err == nil {
+		f, err := os.Create(this.d + string(os.PathSeparator) + this.i)
+		if err == nil {
+			defer f.Close()
+			_, err = f.Write(r)
+			if err == nil {
+				this.c = false
+				return true
+			}
+		}
+	}
+
+	return false
+}

+ 179 - 0
session/session_test.go

@@ -0,0 +1,179 @@
+package session
+
+import (
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"net/http/httptest"
+	"os"
+	"testing"
+)
+
+var SessionId string = ""
+
+func TestSessionBool(t *testing.T) {
+	// Set value
+	request, err := http.NewRequest("GET", "/set", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	recorder := httptest.NewRecorder()
+	http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		sess := New(w, r, "./../tmp")
+		defer sess.Close()
+		if r.URL.Path == "/set" {
+			sess.SetBool("some_bool", true)
+			w.Write([]byte(`ok`))
+		} else {
+			w.WriteHeader(http.StatusNotFound)
+			w.Write([]byte(`404`))
+		}
+	}).ServeHTTP(recorder, request)
+	if recorder.Body.String() != "ok" {
+		t.Fatalf("bad body response, not match")
+	}
+
+	// Remember session id
+	if SessionId == "" && len(recorder.Result().Cookies()) > 0 {
+		SessionId = recorder.Result().Cookies()[0].Value
+	}
+
+	// Get value
+	request, err = http.NewRequest("GET", "/get", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	request.Header.Set("Cookie", "session="+SessionId)
+	recorder = httptest.NewRecorder()
+	http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		sess := New(w, r, "./../tmp")
+		defer sess.Close()
+		if r.URL.Path == "/get" {
+			w.Write([]byte(fmt.Sprintf("%v", sess.GetBool("some_bool", false))))
+		} else {
+			w.WriteHeader(http.StatusNotFound)
+			w.Write([]byte(`404`))
+		}
+	}).ServeHTTP(recorder, request)
+	if recorder.Body.String() != "true" {
+		t.Fatalf("bad body response, not match")
+	}
+}
+
+func TestSessionInt(t *testing.T) {
+	// Set value
+	request, err := http.NewRequest("GET", "/set", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	request.Header.Set("Cookie", "session="+SessionId)
+	recorder := httptest.NewRecorder()
+	http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		sess := New(w, r, "./../tmp")
+		defer sess.Close()
+		if r.URL.Path == "/set" {
+			sess.SetInt("some_int", 5)
+			w.Write([]byte(`ok`))
+		} else {
+			w.WriteHeader(http.StatusNotFound)
+			w.Write([]byte(`404`))
+		}
+	}).ServeHTTP(recorder, request)
+	if recorder.Body.String() != "ok" {
+		t.Fatalf("bad body response, not match")
+	}
+
+	// Remember session id
+	if SessionId == "" && len(recorder.Result().Cookies()) > 0 {
+		SessionId = recorder.Result().Cookies()[0].Value
+	}
+
+	// Get value
+	request, err = http.NewRequest("GET", "/get", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	request.Header.Set("Cookie", "session="+SessionId)
+	recorder = httptest.NewRecorder()
+	http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		sess := New(w, r, "./../tmp")
+		defer sess.Close()
+		if r.URL.Path == "/get" {
+			w.Write([]byte(fmt.Sprintf("%d", sess.GetInt("some_int", 0))))
+		} else {
+			w.WriteHeader(http.StatusNotFound)
+			w.Write([]byte(`404`))
+		}
+	}).ServeHTTP(recorder, request)
+	if recorder.Body.String() != "5" {
+		t.Fatalf("bad body response, not match")
+	}
+}
+
+func TestSessionString(t *testing.T) {
+	// Set value
+	request, err := http.NewRequest("GET", "/set", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	request.Header.Set("Cookie", "session="+SessionId)
+	recorder := httptest.NewRecorder()
+	http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		sess := New(w, r, "./../tmp")
+		defer sess.Close()
+		if r.URL.Path == "/set" {
+			sess.SetString("some_str", "test")
+			w.Write([]byte(`ok`))
+		} else {
+			w.WriteHeader(http.StatusNotFound)
+			w.Write([]byte(`404`))
+		}
+	}).ServeHTTP(recorder, request)
+	if recorder.Body.String() != "ok" {
+		t.Fatalf("bad body response, not match")
+	}
+
+	// Remember session id
+	if SessionId == "" && len(recorder.Result().Cookies()) > 0 {
+		SessionId = recorder.Result().Cookies()[0].Value
+	}
+
+	// Get value
+	request, err = http.NewRequest("GET", "/get", nil)
+	if err != nil {
+		t.Fatal(err)
+	}
+	request.Header.Set("Cookie", "session="+SessionId)
+	recorder = httptest.NewRecorder()
+	http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		sess := New(w, r, "./../tmp")
+		defer sess.Close()
+		if r.URL.Path == "/get" {
+			w.Write([]byte(fmt.Sprintf("%s", sess.GetString("some_str", ""))))
+		} else {
+			w.WriteHeader(http.StatusNotFound)
+			w.Write([]byte(`404`))
+		}
+	}).ServeHTTP(recorder, request)
+	if recorder.Body.String() != "test" {
+		t.Fatalf("bad body response, not match")
+	}
+}
+
+func TestSessionActualFile(t *testing.T) {
+	if SessionId == "" {
+		t.Fatal("SessionId is empty")
+	}
+	fname := "./../tmp" + string(os.PathSeparator) + SessionId
+	bytes, err := ioutil.ReadFile(fname)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if string(bytes) != `{"Bool":{"some_bool":true},"Int":{"some_int":5},"String":{"some_str":"test"}}` {
+		t.Fatal("actual file content, not match")
+	}
+	err = os.Remove(fname)
+	if err != nil {
+		t.Fatal(err)
+	}
+}

+ 22 - 0
session/string.go

@@ -0,0 +1,22 @@
+package session
+
+func (this *session) IsSetString(name string) bool {
+	if _, ok := this.v.String[name]; ok {
+		return true
+	} else {
+		return false
+	}
+}
+
+func (this *session) GetString(name string, def string) string {
+	if v, ok := this.v.String[name]; ok {
+		return v
+	} else {
+		return def
+	}
+}
+
+func (this *session) SetString(name string, value string) {
+	this.v.String[name] = value
+	this.c = true
+}

+ 0 - 0
tmp/.keep