This commit is contained in:
parent
f24b1d2ed5
commit
e68bc648ca
@ -3,4 +3,5 @@ pipeline:
|
||||
image: golang
|
||||
commands:
|
||||
- go get github.com/oxtoacart/bpool
|
||||
- go get github.com/google/uuid
|
||||
- go build
|
@ -15,10 +15,17 @@ Le fichier de configuration est passé dans `Configure()` et doit être au forma
|
||||
"ServerPort": ":8000",
|
||||
"Domain": "http://git.an",
|
||||
"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.
|
||||
|
||||
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
|
||||
* @Date: 2018-07-14 11:32:11
|
||||
* @Last Modified by: klmp200
|
||||
* @Last Modified time: 2018-07-14 13:18:53
|
||||
* @Last Modified time: 2018-07-17 23:55:22
|
||||
*/
|
||||
|
||||
package gowebframework
|
||||
@ -16,6 +16,7 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
"path/filepath"
|
||||
"time"
|
||||
)
|
||||
|
||||
var templates map[string]*template.Template
|
||||
@ -28,6 +29,7 @@ type Config struct {
|
||||
StaticFolderPath string
|
||||
TemplateExtensionPattern string
|
||||
ServerPort string
|
||||
SessionProvider string
|
||||
Domain string
|
||||
Debug bool
|
||||
}
|
||||
@ -38,6 +40,15 @@ var ServerConfig Config
|
||||
func Configure(config_file_name string, custom_config_file_name string) {
|
||||
loadConfiguration(config_file_name, custom_config_file_name)
|
||||
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
|
||||
@ -47,15 +58,29 @@ func Start() {
|
||||
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) {
|
||||
loadDefaultConfiguration()
|
||||
default_settings, err := ioutil.ReadFile(config_file_name)
|
||||
if err != nil {
|
||||
log.Fatal("No " + config_file_name + " found, exiting")
|
||||
}
|
||||
log.Println("Importing settings")
|
||||
err = json.Unmarshal(default_settings, &ServerConfig)
|
||||
if err != nil {
|
||||
log.Fatal("Malformed " + config_file_name)
|
||||
log.Println("No " + config_file_name + " found, using default conf instead")
|
||||
} else {
|
||||
log.Println("Importing settings")
|
||||
err = json.Unmarshal(default_settings, &ServerConfig)
|
||||
if err != nil {
|
||||
log.Fatal("Malformed " + config_file_name)
|
||||
}
|
||||
}
|
||||
|
||||
custom_settings, err := ioutil.ReadFile(custom_config_file_name)
|
||||
|
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