❔ Request¶
Una request è l'insieme dei dati che il client invia al server in ogni chiamata HTTP: l'URL, il metodo (GET/POST/PUT/...), gli header, i cookie, i parametri della query string, il body (form, JSON, multipart), i file caricati, l'IP del client...
In HIX, la request è rappresentata internamente con un oggetto THixRequest, ma il
modo idiomatico per leggerla dentro un controller non è accedere direttamente all'oggetto:
è usare gli helper U*.
✨ Regola d'oro: dentro un controller, usa sempre gli helper
U*per leggere qualsiasi dato della request. Non hai bisogno (e non dovresti) catturareoReqnel codeblock - il dispatcher rende automaticamente disponibile la request del thread corrente agli helper.
// CORRETTO - usa gli helper U*
oSrv:AddRouteGet( "user", "/user/:id", {|| USendJson( { "id" => UParam("id") } ) } )
// ERRATO - oReq non è disponibile nella closure
oSrv:AddRouteGet( "user", "/user/:id", {|oReq| oReq:Respond( {=>}, 200, "json" ) } )
Anatomia di una request HTTP¶
POST /customer/update?lang=it HTTP/1.1 ← metodo + path + query + versione
Host: app.example.com ← header
Content-Type: application/x-www-form-urlencoded ←
Cookie: session_id=abc123; pref=dark ←
X-Requested-With: XMLHttpRequest ←
Content-Length: 42 ←
← riga vuota = fine degli header
first=Carles&last=Aubia&age=42 ← body
Ognuna di queste parti ha un helper corrispondente:
| Parte della request | Helper |
|---|---|
Metodo (POST) |
UMethod() |
Path (/customer/update) |
UPath() |
Query string grezza (lang=it) |
UQuery() |
Header (Content-Type) |
UHeader( "content-type" ) |
Cookie (session_id) |
UCookie( "session_id" ) |
| Body grezzo | UBody() |
| Body analizzato (form/JSON) | UPost( "first" ) / UJson() |
1. Riga di request¶
Metodo HTTP¶
UMethod() // -> "GET", "POST", "PUT", "DELETE", "PATCH"
UIsGet() // -> .T. se GET
UIsPost() // -> .T. se POST
Utile per action che accettano più metodi sulla stessa route:
oSrv:AddRoute( "form", "/contact", {||
IF UIsPost()
_ProcessForm()
ELSE
USendView( "contact.view.html" )
ENDIF
}, "GET,POST" )
Path e query string¶
UPath() ritorna l'URL senza la query string. Per leggere la query, usa UGet.
Scheme, host e porta¶
UScheme() // -> "http" / "https"
UIsHttps() // -> .T. / .F.
UHost() // -> "app.example.com" (header Host)
UPort() // -> 443
UIP() // -> "192.168.1.10" (rispetta X-Forwarded-For se mode=proxied)
2. Variabili di route - UParam¶
Quando una route dichiara segmenti variabili con :name, catturi il valore con
UParam.
oSrv:AddRouteGet( "user.show", "/users/:id", {||
LOCAL nId := Val( UParam( "id" ) )
USendJson( _UserRepoFind( nId ) )
} )
| Forma | Comportamento |
|---|---|
UParam( cKey ) |
Legge la variabile; se non esiste solleva errore 400 |
UParam( cKey, xDef ) |
Legge con un valore di default se non esiste |
UParam( 1 ) |
Cattura dal wildcard * (equivalente a UParam("_1")) |
UParam() |
Hash con tutte le variabili di route |
// Route: /products/:id([0-9]+)/edit
nId := Val( UParam( "id", "0" ) ) // con default sicuro
// Route: /docs/:section! (opzionale)
cSection := UParam( "section", "intro" )
// Route: /static/*
cFile := UParam( 1 ) // equivalente a UParam("_1")
📖 Pattern URL completi in Route.
3. Query string - UGet¶
Legge parametri dalla query string (?key=value&...).
// URL: /search?q=carles&page=2&limit=20
cQ := UGet( "q" ) // "carles"
nPage := Val( UGet( "page", "1" ) ) // 2
nLimit := Val( UGet( "limit", "20" ) ) // 20
// Senza argomenti -> hash completo
hAll := UGet() // { "q"=>"carles", "page"=>"2", "limit"=>"20" }
Ritorna sempre una stringa. Per gli interi, converti con Val().
Valida la query string in blocco¶
Quando ci sono più variabili, valida con UValidateGet o UValidateParams
(query + variabili di route unite).
oVal := UValidateParams( { ;
"id" => { "required|number|min:0", "Id" }, ;
"lang" => { "in:it,en,es", "Lang" } ;
} )
IF ! oVal:Make()
RETU URedirect( URoute( 'home' ) )
ENDIF
nId := oVal:Get( "id" )
cLang := oVal:Get( "lang" )
4. Body POST¶
Form HTML - UPost¶
Quando il client invia application/x-www-form-urlencoded o multipart/form-data:
// POST /login first=Carles&password=secret
cUser := UPost( "username" )
cPass := UPost( "password" )
// Con default
cRole := UPost( "role", "viewer" )
// Hash completo
hAll := UPost()
UPost legge anche dal body se arriva come JSON (auto-rileva il Content-Type).
Body JSON grezzo - UJson¶
Quando il client invia Content-Type: application/json:
// POST /api/users Content-Type: application/json
// Body: { "name": "Carles", "email": "x@y.com", "tags": ["admin","editor"] }
LOCAL hData := UJson()
IF hData == NIL
RETURN USendError( 400, "JSON non valido" )
ENDIF
cName := hb_HGetDef( hData, "name", "" )
aTags := hb_HGetDef( hData, "tags", {} )
UJson() ritorna un hash, un array, o NIL se il body non è JSON valido.
Body grezzo - UBody¶
Per integrazioni speciali (webhook firmati, payload binari, XML...):
LOCAL cRaw := UBody()
LOCAL cSig := UHeader( "X-Signature" )
IF ! _VerifyHmac( cRaw, cSig, cSecret )
RETURN USendError( 401, "Firma non valida" )
ENDIF
_ProcessWebhook( cRaw )
Dimensione e tipo del body¶
UContentType() // "application/json"
UContentLength() // 42 (byte)
UIsJson() // .T. se Content-Type è application/json
UIsForm() // .T. se application/x-www-form-urlencoded
UIsMultipart() // .T. se multipart/form-data
5. Header - UHeader¶
Lettura case-insensitive di qualsiasi header HTTP.
cAuth := UHeader( "Authorization", "" ) // "Bearer abc123"
cAccept := UHeader( "Accept", "" ) // "application/json"
cUA := UHeader( "User-Agent", "" )
// Content negotiation
UWantsJson() // .T. se il client preferisce JSON (Accept o AJAX)
UIsAjax() // .T. se X-Requested-With: XMLHttpRequest
Pattern tipico per rispondere con JSON o HTML a seconda del client:
IF UWantsJson() .OR. UIsAjax()
USendJson( { "error" => "Non autenticato" }, 401 )
ELSE
URedirect( "/login" )
ENDIF
6. Cookie - UCookie¶
I cookie vengono analizzati una sola volta e messi in cache nella request (lazy). Chiamare
UCookie ripetutamente non comporta penalità.
📖 Come scrivere cookie nella risposta (
USetCookie) in Response > Cookies.
7. Upload multipart - UFiles¶
Quando un form carica file con enctype="multipart/form-data":
LOCAL aFiles := UFiles()
LOCAL hFile
FOR EACH hFile IN aFiles
// hFile["name"] -> nome del campo form
// hFile["data"] -> contenuto binario
// hFile["mime"] -> Content-Type del file
// hFile["size"] -> dimensione in byte
IF hFile["size"] > 5 * 1024 * 1024
RETURN USendError( 413, "File troppo grande" )
ENDIF
hb_MemoWrit( "uploads/" + hFile["name"], hFile["data"] )
NEXT
I campi di testo dello stesso form si leggono con il normale UPost.
8. Dati del client e della sessione¶
Identità del client¶
UIP() // IP reale (rispetta X-Forwarded-For in modalità proxy)
UHost() // header Host
UScheme() // "http" / "https"
UIsHttps() // .T. se la connessione è sicura
Sessione e utente autenticato¶
Quando il middleware di sessione e/o autenticazione è attivo:
// Sessione
cUser := USession( "user" ) // valore o NIL
USession():Set( "role", "admin" )
USession():Save()
// Utente autenticato (middleware HIX_MwAuth)
hUser := UAuthUser() // hash utente completo
cMail := UAuthUser( "email", "" )
IF ! UHasRole( "admin" )
USendError( 403, "Solo admin" )
RETURN
ENDIF
// JWT (middleware HixMwJwt)
cSub := UJwt( "sub" )
IF ! UHasScope( "read:products" )
USendError( 403, "Scope insufficiente" )
RETURN
ENDIF
📖 Dettagli in Sessioni, Autenticazione e JWT.
9. Accesso low-level - URequest e UContext¶
Per casi speciali, puoi ottenere l'oggetto request o il contesto middleware.
URequest() - l'oggetto THixRequest¶
LOCAL oReq := URequest()
// Accesso diretto a hData (dati condivisi tra middleware e controller)
hUser := hb_HGetDef( oReq:hData, "user", NIL )
// Proprietà grezze
? oReq:cMethod, oReq:cPath, oReq:cQuery
Esempio reale (Fenix main.prg):
PROCEDURE Main(...)
LOCAL oReq := URequest()
LOCAL hUser := hb_HGetDef( oReq:hData, "user", { "name" => "Sconosciuto", "roles" => {=>} } )
LOCAL cName := hUser['name']
// ... usa hUser per costruire la view
RETU UView( 'main.view.html', cName, hUser )
UContext() - il contesto middleware¶
Quando il middleware è attivo, i MW passano i dati al controller tramite oCtx:hData["key"].
Dal controller, li leggi con UContext():
LOCAL oCtx := UContext()
LOCAL hJwt := oCtx:hData[ "jwt" ] // payload impostato da HixMwJwt
LOCAL cSid := oCtx:hData[ "_sid" ] // SID impostato da HixMwSession
Nella maggior parte dei casi, non hai bisogno di toccare oCtx direttamente: gli helper
USession(), UJwt(), UAuthUser() leggono già da lì.
10. Validazione della request¶
Una volta raccolti i dati, il passo successivo è validarli con il validator integrato.
| Helper | Fonte dati |
|---|---|
UValidate( hRules ) |
POST (form o JSON) |
UValidatePost( hRules ) |
POST esplicito |
UValidateGet( hRules ) |
Query string |
UValidateParams( hRules ) |
Query string + variabili di route |
UValidateJson( hRules ) |
Body JSON esplicito |
UValidateOrFail( hRules ) |
POST - risponde automaticamente 422 se fallisce e ritorna NIL |
Pattern PRG (Post / Redirect / Get) tipico per form web:
oVal := UValidatePost( { ;
"username" => { "required|min:3|max:30", "Username", "" }, ;
"password" => { "required|min:4", "Password", "" } ;
} )
IF ! oVal:Make()
UFlash( "login" ):Set( { ;
"error" => oVal:GetFirstError(), ;
"user" => oVal:Get( "username" ) ;
} )
URedirect( "/login" )
RETURN
ENDIF
// Dati validati disponibili via oVal:Get( cKey ) o oVal:DataFields()
Pattern tipico per API JSON:
oVal := UValidateOrFail( { ;
"name" => "required|string|max:100", ;
"email" => "required|string|email", ;
"age" => "required|integer|min:18" ;
} )
IF oVal == NIL ; RETURN NIL ; ENDIF // ha già risposto 422
USendJson( _UserCreate( oVal:DataFields() ), 201 )
📖 Regole, modificatori e casi avanzati in Validator.
11. Riferimento rapido helper¶
Lettura della request¶
UMethod() UPath() UQuery()
UGet(k,d) UPost(k,d) UParam(k,d)
UHeader(k,d) UCookie(k,d) UBody()
UJson() UContentType() UContentLength()
UFiles() URequest() UContext()
Rileva tipo di request¶
UIsGet() UIsPost() UIsAjax() UIsHttps()
UIsJson() UIsForm() UIsMultipart() UWantsJson()
UScheme() UIP() UHost() UPort()
Validazione input¶
UValidate(h) UValidatePost(h) UValidateGet(h)
UValidateParams(h) UValidateJson(h) UValidateOrFail(h)
UIsMail(s) UIsNumeric(v) UIsInteger(v) UIsUrl(s) UIsIp(s)
Identità / sessione¶
USession() USession(k) USession(k,d)
UAuthUser(k,d) UCurrentUser() UHasRole(r,op) UGetRoles()
UJwt(k,d) UHasScope(s)
Anti-pattern - cosa evitare¶
❌ Catturare oReq nel codeblock¶
// NO - oReq non è nella closure
oSrv:AddRouteGet( "x", "/x", {|oReq| oReq:Respond( {=>}, 200, "json" ) } )
// SÌ - usa gli helper U*
oSrv:AddRouteGet( "x", "/x", {|| USendJson( {=>} ) } )
❌ Mescolare lettura manuale e validazione¶
// NO - verboso, errori 400 inconsistenti
cName := UPost( "name", "" )
IF Empty( cName ) ; USendError( 422, "name obbligatorio" ) ; RETURN ; ENDIF
IF Len( cName ) > 100 ; USendError( 422, "name troppo lungo" ) ; RETURN ; ENDIF
// SÌ - dichiarativo, errori raggruppati, sintassi standard
oVal := UValidateOrFail( { "name" => "required|string|max:100" } )
IF oVal == NIL ; RETURN NIL ; ENDIF
cName := oVal:Get( "name" )
❌ Confrontare stringhe con !=¶
Harbour usa SET EXACT OFF di default — != confronta fino alla lunghezza della
stringa più corta. Usa sempre ==.