This commit is contained in:
parent
f24b1d2ed5
commit
e68bc648ca
@ -3,4 +3,5 @@ pipeline:
|
|||||||
image: golang
|
image: golang
|
||||||
commands:
|
commands:
|
||||||
- go get github.com/oxtoacart/bpool
|
- go get github.com/oxtoacart/bpool
|
||||||
|
- go get github.com/google/uuid
|
||||||
- go build
|
- go build
|
@ -15,10 +15,17 @@ Le fichier de configuration est passé dans `Configure()` et doit être au forma
|
|||||||
"ServerPort": ":8000",
|
"ServerPort": ":8000",
|
||||||
"Domain": "http://git.an",
|
"Domain": "http://git.an",
|
||||||
"MainTemplate": "{{define \"main\" }} {{ template \"base\" . }} {{ end }}",
|
"MainTemplate": "{{define \"main\" }} {{ template \"base\" . }} {{ end }}",
|
||||||
"Debug": false
|
"Debug": false,
|
||||||
|
"SessionProvider": "FileProvider"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Des paramètres par défaut sont utilisé dans tous les cas.
|
||||||
|
|
||||||
|
Le système de session utilise un dossier `.session` généré automatiquement, attention aux permissions !
|
||||||
|
|
||||||
|
Il est possible de rajouter ses propres backend pour le système de session en les enregistrant grâce à la fonction `SessionProviderRegister`. Les backends doivent respecter l'interface `SessionProvider`.
|
||||||
|
|
||||||
Le MainTemplate est utilisé pour charger tous les templates, il peut être utilisé tel quel ou modifié si vous savez ce que fous faites. Dans le doute, laissez celui par défaut.
|
Le MainTemplate est utilisé pour charger tous les templates, il peut être utilisé tel quel ou modifié si vous savez ce que fous faites. Dans le doute, laissez celui par défaut.
|
||||||
|
|
||||||
On lance enfin le serveur en utilisant `Start()`
|
On lance enfin le serveur en utilisant `Start()`
|
||||||
|
22
cookie.go
Normal file
22
cookie.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* @Author: Bartuccio Antoine
|
||||||
|
* @Date: 2018-07-16 12:22:24
|
||||||
|
* @Last Modified by: klmp200
|
||||||
|
* @Last Modified time: 2018-07-17 23:27:27
|
||||||
|
*/
|
||||||
|
|
||||||
|
package gowebframework
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func AddCookie(w http.ResponseWriter, name string, data string, lifetime time.Duration) {
|
||||||
|
cookie := http.Cookie{
|
||||||
|
Name: name,
|
||||||
|
Value: data,
|
||||||
|
Expires: time.Now().Add(lifetime),
|
||||||
|
}
|
||||||
|
http.SetCookie(w, &cookie)
|
||||||
|
}
|
157
file-session-provider.go
Normal file
157
file-session-provider.go
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
/*
|
||||||
|
* @Author: Bartuccio Antoine
|
||||||
|
* @Date: 2018-07-16 15:37:29
|
||||||
|
* @Last Modified by: klmp200
|
||||||
|
* @Last Modified time: 2018-07-17 23:47:56
|
||||||
|
*/
|
||||||
|
|
||||||
|
package gowebframework
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var session_folder_path string = ".session"
|
||||||
|
var folder_perm os.FileMode = 0770
|
||||||
|
|
||||||
|
type FileSessionProvider struct{}
|
||||||
|
|
||||||
|
// Used to hide FileSession variables and still be able to write inside json with json.Marshal
|
||||||
|
type unexportedFileSession struct {
|
||||||
|
UUID string
|
||||||
|
LastUse time.Time
|
||||||
|
Data map[string]interface{}
|
||||||
|
}
|
||||||
|
type FileSession struct {
|
||||||
|
uuid string
|
||||||
|
lastUse time.Time
|
||||||
|
data map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert a unexportedFileSession to a FileSession
|
||||||
|
func (session unexportedFileSession) fileSession() FileSession {
|
||||||
|
return FileSession{
|
||||||
|
uuid: session.UUID,
|
||||||
|
lastUse: session.LastUse,
|
||||||
|
data: session.Data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider FileSessionProvider) checkOrCreateFolder() {
|
||||||
|
folder, err := os.Open(session_folder_path)
|
||||||
|
if err != nil {
|
||||||
|
// Auto create folder if doesn't exist
|
||||||
|
log.Println("No session folder found, creating " + session_folder_path)
|
||||||
|
err = os.Mkdir(session_folder_path, folder_perm)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Could not create session directory, exiting")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
folder_infos, err := folder.Stat()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if !folder_infos.IsDir() {
|
||||||
|
log.Fatal(session_folder_path + " is not a directory, exiting")
|
||||||
|
}
|
||||||
|
if !isWritable(session_folder_path) {
|
||||||
|
log.Fatal(session_folder_path + " directory is not writable, exiting")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider FileSessionProvider) SessionInit(uuid string) (Session, error) {
|
||||||
|
provider.checkOrCreateFolder()
|
||||||
|
provider.SessionDestroy(uuid)
|
||||||
|
session := FileSession{}
|
||||||
|
session.uuid = uuid
|
||||||
|
session.data = make(map[string]interface{})
|
||||||
|
session.lastUse = time.Now()
|
||||||
|
return session, session.writeSession()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider FileSessionProvider) SessionRead(uuid string) (Session, error) {
|
||||||
|
provider.checkOrCreateFolder()
|
||||||
|
data, err := ioutil.ReadFile(filepath.Join(session_folder_path, uuid+".json"))
|
||||||
|
if err != nil {
|
||||||
|
return provider.SessionInit(uuid)
|
||||||
|
}
|
||||||
|
unexported_session := unexportedFileSession{}
|
||||||
|
err = json.Unmarshal(data, &unexported_session)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
session := unexported_session.fileSession()
|
||||||
|
return session, session.writeSession()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider FileSessionProvider) SessionDestroy(uuid string) error {
|
||||||
|
provider.checkOrCreateFolder()
|
||||||
|
return os.Remove(filepath.Join(session_folder_path, uuid+".json"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (provider FileSessionProvider) SessionClearExpired(maxLifetime time.Duration) {
|
||||||
|
provider.checkOrCreateFolder()
|
||||||
|
directory, _ := ioutil.ReadDir(session_folder_path)
|
||||||
|
for _, file := range directory {
|
||||||
|
session := unexportedFileSession{}
|
||||||
|
data, err := ioutil.ReadFile(filepath.Join(session_folder_path, file.Name()))
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
} else {
|
||||||
|
err = json.Unmarshal(data, &session)
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
} else {
|
||||||
|
if session.LastUse.Add(maxLifetime).Sub(time.Now()) <= 0 {
|
||||||
|
// Session expired
|
||||||
|
provider.SessionDestroy(session.UUID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (session FileSession) unexportedFileSession() unexportedFileSession {
|
||||||
|
return unexportedFileSession{
|
||||||
|
UUID: session.uuid,
|
||||||
|
LastUse: session.lastUse,
|
||||||
|
Data: session.data,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write a session on the disk
|
||||||
|
func (session FileSession) writeSession() error {
|
||||||
|
session.lastUse = time.Now()
|
||||||
|
data, _ := json.Marshal(session.unexportedFileSession())
|
||||||
|
err := ioutil.WriteFile(filepath.Join(session_folder_path, session.uuid+".json"), data, folder_perm)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (session FileSession) Set(key string, value interface{}) error {
|
||||||
|
session.data[key] = value
|
||||||
|
return session.writeSession()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (session FileSession) Get(key string) interface{} {
|
||||||
|
value, ok := session.data[key]
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (session FileSession) Delete(key string) error {
|
||||||
|
delete(session.data, key)
|
||||||
|
return session.writeSession()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (session FileSession) SessionUUID() string {
|
||||||
|
return session.uuid
|
||||||
|
}
|
@ -2,7 +2,7 @@
|
|||||||
* @Author: Bartuccio Antoine
|
* @Author: Bartuccio Antoine
|
||||||
* @Date: 2018-07-14 11:32:11
|
* @Date: 2018-07-14 11:32:11
|
||||||
* @Last Modified by: klmp200
|
* @Last Modified by: klmp200
|
||||||
* @Last Modified time: 2018-07-14 13:18:53
|
* @Last Modified time: 2018-07-17 23:55:22
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package gowebframework
|
package gowebframework
|
||||||
@ -16,6 +16,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var templates map[string]*template.Template
|
var templates map[string]*template.Template
|
||||||
@ -28,6 +29,7 @@ type Config struct {
|
|||||||
StaticFolderPath string
|
StaticFolderPath string
|
||||||
TemplateExtensionPattern string
|
TemplateExtensionPattern string
|
||||||
ServerPort string
|
ServerPort string
|
||||||
|
SessionProvider string
|
||||||
Domain string
|
Domain string
|
||||||
Debug bool
|
Debug bool
|
||||||
}
|
}
|
||||||
@ -38,6 +40,15 @@ var ServerConfig Config
|
|||||||
func Configure(config_file_name string, custom_config_file_name string) {
|
func Configure(config_file_name string, custom_config_file_name string) {
|
||||||
loadConfiguration(config_file_name, custom_config_file_name)
|
loadConfiguration(config_file_name, custom_config_file_name)
|
||||||
loadTemplates()
|
loadTemplates()
|
||||||
|
// initialise sessions
|
||||||
|
SessionProviderRegister(FileSessionProvider{}, "FileProvider")
|
||||||
|
// Put a session of 1 day duration
|
||||||
|
ses, err := newSessionManager(ServerConfig.SessionProvider, "GOSESSID", time.Hour*24)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
SESSION = ses
|
||||||
|
go clearExpiredSessionsCron()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Starts server
|
// Starts server
|
||||||
@ -47,16 +58,30 @@ func Start() {
|
|||||||
log.Fatal(http.ListenAndServe(ServerConfig.ServerPort, nil))
|
log.Fatal(http.ListenAndServe(ServerConfig.ServerPort, nil))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func loadDefaultConfiguration() {
|
||||||
|
ServerConfig.StaticFolderPath = "statics/"
|
||||||
|
ServerConfig.TemplateIncludePath = "templates/"
|
||||||
|
ServerConfig.TemplateLayoutPath = "templates/layouts/"
|
||||||
|
ServerConfig.TemplateExtensionPattern = "*.gohtml"
|
||||||
|
ServerConfig.ServerPort = ":8000"
|
||||||
|
ServerConfig.Domain = "localhost"
|
||||||
|
ServerConfig.MainTemplate = `{{define "main" }} {{ template "base" . }} {{ end }}`
|
||||||
|
ServerConfig.Debug = false
|
||||||
|
ServerConfig.SessionProvider = "FileProvider"
|
||||||
|
}
|
||||||
|
|
||||||
func loadConfiguration(config_file_name string, custom_config_file_name string) {
|
func loadConfiguration(config_file_name string, custom_config_file_name string) {
|
||||||
|
loadDefaultConfiguration()
|
||||||
default_settings, err := ioutil.ReadFile(config_file_name)
|
default_settings, err := ioutil.ReadFile(config_file_name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("No " + config_file_name + " found, exiting")
|
log.Println("No " + config_file_name + " found, using default conf instead")
|
||||||
}
|
} else {
|
||||||
log.Println("Importing settings")
|
log.Println("Importing settings")
|
||||||
err = json.Unmarshal(default_settings, &ServerConfig)
|
err = json.Unmarshal(default_settings, &ServerConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Malformed " + config_file_name)
|
log.Fatal("Malformed " + config_file_name)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
custom_settings, err := ioutil.ReadFile(custom_config_file_name)
|
custom_settings, err := ioutil.ReadFile(custom_config_file_name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
46
is-writable.go
Normal file
46
is-writable.go
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/*
|
||||||
|
* @Author: Bartuccio Antoine
|
||||||
|
* @Date: 2018-07-17 18:37:38
|
||||||
|
* @Last Modified by: klmp200
|
||||||
|
* @Last Modified time: 2018-07-17 19:35:18
|
||||||
|
*/
|
||||||
|
// +build !windows
|
||||||
|
|
||||||
|
package gowebframework
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Check if a file or directory is writable
|
||||||
|
func isWritable(path string) bool {
|
||||||
|
os_stats, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
// Error reading file or folder
|
||||||
|
log.Println(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if os_stats.Mode().Perm()&(1<<(uint(7))) == 0 {
|
||||||
|
// Check if user bit is enabled
|
||||||
|
log.Println("User bit on " + path + " is not enabled")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var syscall_stats syscall.Stat_t
|
||||||
|
err = syscall.Stat(path, &syscall_stats)
|
||||||
|
if err != nil {
|
||||||
|
// Error getting stats
|
||||||
|
log.Println(err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if uint32(os.Getuid()) != syscall_stats.Uid {
|
||||||
|
// User doesn't have permission to write
|
||||||
|
log.Println("No permission to write on " + path)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
30
is-writable_windows.go
Normal file
30
is-writable_windows.go
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/*
|
||||||
|
* @Author: Bartuccio Antoine
|
||||||
|
* @Date: 2018-07-17 18:51:12
|
||||||
|
* @Last Modified by: klmp200
|
||||||
|
* @Last Modified time: 2018-07-17 18:54:20
|
||||||
|
*/
|
||||||
|
|
||||||
|
// WARNING : Not tested !
|
||||||
|
|
||||||
|
package gowebframework
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func isWritable(path string) bool {
|
||||||
|
stats, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
// Folder or file doesn't exist
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if stats.Mode().Perm()&(1<<(uint(7))) == 0 {
|
||||||
|
// Check if the user bit is enabled in file permission
|
||||||
|
// It's the only thing we can do on windows
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
105
session.go
Normal file
105
session.go
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* @Author: Bartuccio Antoine
|
||||||
|
* @Date: 2018-07-16 14:07:07
|
||||||
|
* @Last Modified by: klmp200
|
||||||
|
* @Last Modified time: 2018-07-17 23:57:10
|
||||||
|
*/
|
||||||
|
|
||||||
|
package gowebframework
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var SESSION *SessionManager
|
||||||
|
var providers = make(map[string]Provider)
|
||||||
|
|
||||||
|
type SessionManager struct {
|
||||||
|
cookieName string // Name of the cookie on the client side
|
||||||
|
lock sync.Mutex
|
||||||
|
provider Provider
|
||||||
|
maxLifetime time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type Session interface {
|
||||||
|
Set(key string, value interface{}) error // set a session value
|
||||||
|
Get(key string) interface{} // get session value
|
||||||
|
Delete(key string) error // delete session value
|
||||||
|
SessionUUID() string // get the current session UUID
|
||||||
|
}
|
||||||
|
|
||||||
|
type Provider interface {
|
||||||
|
SessionInit(uuid string) (Session, error)
|
||||||
|
SessionRead(uuid string) (Session, error)
|
||||||
|
SessionDestroy(uuid string) error
|
||||||
|
SessionClearExpired(maxLifetime time.Duration)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *SessionManager) generateUUID() string {
|
||||||
|
return uuid.New().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Launch a Clear Expired Sessions task at fixed rate
|
||||||
|
func clearExpiredSessionsCron() {
|
||||||
|
SESSION.lock.Lock()
|
||||||
|
lifetime := SESSION.maxLifetime
|
||||||
|
SESSION.lock.Unlock()
|
||||||
|
for {
|
||||||
|
log.Println("pute")
|
||||||
|
SESSION.lock.Lock()
|
||||||
|
SESSION.provider.SessionClearExpired(lifetime)
|
||||||
|
SESSION.lock.Unlock()
|
||||||
|
time.Sleep(lifetime)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SessionProviderRegister(provider Provider, providerName string) error {
|
||||||
|
if provider == nil {
|
||||||
|
return fmt.Errorf("No provider found for provider name %s", providerName)
|
||||||
|
}
|
||||||
|
if _, exists := providers[providerName]; exists {
|
||||||
|
return fmt.Errorf("Provider with name %s already exists", providerName)
|
||||||
|
}
|
||||||
|
providers[providerName] = provider
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (manager *SessionManager) GetSession(w http.ResponseWriter, r *http.Request) Session {
|
||||||
|
var session Session
|
||||||
|
manager.lock.Lock()
|
||||||
|
defer manager.lock.Unlock()
|
||||||
|
cookie, err := r.Cookie(manager.cookieName)
|
||||||
|
if err != nil || cookie.Value == "" {
|
||||||
|
// Create a new session
|
||||||
|
session_id := manager.generateUUID()
|
||||||
|
s, err := manager.provider.SessionInit(session_id)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
session = s
|
||||||
|
AddCookie(w, manager.cookieName, url.QueryEscape(session_id), manager.maxLifetime)
|
||||||
|
} else {
|
||||||
|
// Get the session
|
||||||
|
session_id, _ := url.QueryUnescape(cookie.Value)
|
||||||
|
s, err := manager.provider.SessionRead(session_id)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
session = s
|
||||||
|
}
|
||||||
|
return session
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSessionManager(providerName string, cookieName string, maxLifetime time.Duration) (*SessionManager, error) {
|
||||||
|
provider, ok := providers[providerName]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("Session error: unknown provider %s", providerName)
|
||||||
|
}
|
||||||
|
return &SessionManager{provider: provider, cookieName: cookieName, maxLifetime: maxLifetime}, nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user