π Sessioni¶
Una sessione Γ¨ uno spazio di storage sul server dove HIX conserva i dati associati a uno specifico client (tipicamente l'utente loggato). CiΓ² che lega il client alla sua sessione Γ¨ un cookie che viaggia in ogni request e contiene un identificativo opaco (SID).
Client Server
β β
β GET /login β
ββββββββββββββββββββββββββββββββββββ>β no SID -> crea nuova sessione
β β SID = "abc...123"
β Set-Cookie: HIXSID=abc...123 β
β<ββββββββββββββββββββββββββββββββββββ€
β β
β POST /auth β
β Cookie: HIXSID=abc...123 β riconosce il SID -> recupera i dati
ββββββββββββββββββββββββββββββββββββ>β USession():Set("user", hUser)
Le sessioni HIX sono idempotenti: toccare oCtx:hData["session"] da un
middleware o chiamare USession():Set() da un controller modifica lo stesso
hash di dati per quel client.
Quando usarle¶
| Caso d'uso | Sessioni |
|---|---|
| App web tradizionale con login | β SΓ¬ - pattern canonico |
| SPA con autenticazione basata su cookie | β SΓ¬ |
| API REST stateless | β No - usa JWT |
| Microservizi / app mobile | β No - usa JWT |
| Carrello, wizard multi-schermata | β SΓ¬ |
| Messaggi flash tra redirect | β
Sì (UFlash) |
Le sessioni sono stateful: il server ricorda il client attraverso le richieste. Rendono la programmazione piΓΉ facile ma legano il client a un'istanza (o richiedono session affinity / storage condiviso in un cluster).
Setup¶
Da hix.json¶
storage: "memory" | "file".
lifetime: durata del cookie di sessione in minuti (0 = indefinita).
gc_days: giorni per la GC dei file orfani (solo storage="file").
seed: chiave segreta per la cifratura (richiesta se crypt=true).
{
"session": {
"storage": "memory",
"prefix": "sess_",
"crypt": false,
"seed": "",
"lifetime": 60,
"gc_days": 3
}
}
Da codice¶
HIX_MwSessionSetup( ;
"HIXSID", ; // nome del cookie
3600, ; // TTL in secondi (1 ora)
60, ; // GC: pulisci gli scaduti ogni N chiamate
"memory", ; // storage: "memory" | "file"
"sessions/", ; // path (solo se storage="file")
"sess_", ; // prefisso file
.F., ; // cifra
"", ; // seed di cifratura
7 ) // durata del cookie in giorni
Dall'app - convenzione Fenix¶
Fenix espone i parametri in www/middlewares/config.json per tenerli vicini
alla logica dell'app:
Questi valori si leggono con UMwConfig("session", "cookie") da qualsiasi
controller o middleware.
Abilitare la sessione su una route¶
HIX_MwSession Γ¨ il middleware che carica/crea la sessione. Va aggiunto
alla catena di middleware della route - direttamente o dentro un gruppo di middleware dell'app.
Singola route¶
{ "name": "dashboard", "url": "/dashboard", "action": "controllers/dash.prg",
"middleware": "HIX_MwSession" }
Pattern Fenix - gruppo di middleware riutilizzabile¶
In Fenix definisci una volta un gruppo che combina sessione + autenticazione e lo applichi a tutte le route che ne hanno bisogno:
// www/middlewares/myappauth.prg
FUNCTION MyAppAuth( oCtx )
LOCAL o := UBaseMiddleware():New( oCtx )
o:Add( UMiddleware():New( "HIX_MwSession" ) )
o:Add( UMiddleware():New( "HIX_MwIsAuth" ) )
RETURN o:Run()
π Dettagli del pattern in Middleware.
Leggere e scrivere da un controller¶
Con la sessione attiva, gli helper USession() e UFlash() permettono di
accedere ai dati senza toccare oCtx.
Leggere¶
cUser := USession( "user" ) // valore o NIL
cRole := USession( "role", "viewer" ) // valore con default
Scrivere¶
LOCAL oSess := USession() // proxy con Set/Save/Destroy
oSess:Set( "user", hUser )
oSess:Set( "role", "admin" )
oSess:Save() // persiste + rinnova TTL + emette cookie
Distruggere¶
Esempio reale - auth.prg da Fenix¶
// POST /auth - validazione credenziali e avvio sessione
FUNCTION Main()
LOCAL oVal, oSess, hUser
oVal := UValidatePost( { ;
"username" => { "required|min:3|max:30", "Username", "" }, ;
"password" => { "required|min:4", "Password", "" } ;
} )
IF ! oVal:Make()
UFlash( "login" ):Set( { "error" => oVal:GetFirstError() } )
URedirect( "/login" )
RETURN
ENDIF
hUser := ModelUser( oVal:Get( "username" ), oVal:Get( "password" ) )
IF ValType( hUser ) == "H"
// Salva l'utente con la chiave configurata
oSess := USession()
oSess:Set( UMwConfig( "auth", "session_user_key" ), hUser )
oSess:Save()
URedirect( UMwConfig( "auth", "redirect_accept" ) )
ELSE
UFlash( "login" ):Set( { "error" => "Username o password errati" } )
URedirect( UMwConfig( "auth", "redirect_login" ) )
ENDIF
RETURN
Esempio reale - logout.prg da Fenix¶
Storage: memory vs file¶
| Storage | Persistenza | Cluster | Restart | Uso tipico |
|---|---|---|---|---|
memory |
RAM del processo | β singola istanza | Persa | Sviluppo, monolitici |
file |
Disco | β con session affinity | Sopravvive | Produzione, load balancer |
Memory¶
Sessioni veloci, niente scrittura su disco. Tutte le sessioni si perdono al riavvio. In un cluster, il client perde la sessione se il load balancer lo manda su un'altra istanza.
File¶
Ogni sessione Γ¨ un file in sessions/<prefix><SID>.dat. Sopravvivono ai riavvii
e permettono a piΓΉ istanze di condividere lo stesso storage.
Cluster con Apache + stickysession¶
Quando fai il deploy dietro un load balancer Apache, chiama HIX_MwSessionSetRoute( "i1" )
con la route= del tuo BalancerMember. HIX accoda il suffisso al SID così
Apache puΓ² mantenere il client sticky alla stessa istanza con
stickysession=HIXSID.
Cifratura opzionale¶
Se crypt=1 nel config (o lCrypt=.T. in HIX_MwSessionSetup), i file di sessione
sono cifrati con il seed. Senza il seed corretto non possono essere letti.
HIX_MwSessionSetup( "HIXSID", 3600, 60, "file", "sessions/", "sess_", ;
.T., "chiave_segreta_di_app", 7 )
β οΈ Cambiare il seed invalida tutte le sessioni esistenti.
Pattern utili¶
Recuperare l'utente in qualsiasi controller¶
PROCEDURE Main(...)
LOCAL oReq := URequest()
LOCAL hUser := hb_HGetDef( oReq:hData, "user", { "name" => "Sconosciuto" } )
// hUser Γ¨ stato messo da HIX_MwIsAuth dopo aver letto la sessione
RETURN UView( "main.view.html", hUser["name"], hUser )
I middleware di auth leggono giΓ
USession( cKey )per te e mettono l'hash utente inoReq:hData["user"].
Messaggi flash (messaggi one-time)¶
UFlash usa la sessione sotto il cofano. Essenziale per portare messaggi
attraverso un URedirect.
// POST con errore -> flash + redirect
UFlash( "login" ):Set( { ;
"error" => "Username o password errati", ;
"user" => cUserInserito ;
} )
URedirect( "/login" )
// GET /login -> consuma il flash una sola volta
oFlash := UFlash( "login" )
cError := oFlash:Get( "error" ) // cancellato dopo la lettura
cUser := oFlash:Get( "user" )
oFlash:Save()