Files
fivenode_go/app/bridge/bridge_session.prg
Charles KWON OhJun c72c2ff58d feat(bridge): port bridge_*.prg + wire HTTP↔bridge dispatcher
Pulls bridge_context.prg, bridge_request.prg, bridge_session.prg, and
bridge_cookie.prg from upstream fivenode into app/bridge/ so the
mod_harbour AP_* surface (AP_METHOD, AP_BODY, AP_ARGS, AP_USERIP,
AP_RPUTS, AP_JSONRESPONSE, AP_SETCONTENTTYPE, ctx_get, ctx_set, ...)
runs unchanged on top of fivenode_go's Go RTL.

bridge_main.prg deliberately omitted — its REQUEST sweep pulls in
TDrMySQL / hbct / hbcurl symbols fivenode_go neither has nor needs.
fivenode_go runs ahead-of-time with the Five compiler, so the
REQUEST trick that keeps fnb-runtime symbols alive isn't required.

One upstream patch was unavoidable: AP_RPUTS / AP_ECHO were variadic
(`( ... )`) and used PValue() to walk caller args. Five's PValue
returns the caller's LOCAL slot (not the actual variadic args, which
aren't copied into locals when declared params is 0), so the body
came back as "1" instead of the JSON payload. Collapsed both to a
single-argument form; every call site in fivenode_go already passes
exactly one value. Patched-out spots are marked with a TODO so we
can revert once Five gains real variadic PValue support.

app/bridge_server.prg ties it all together: starts httpserver on
:8090 with BRIDGEDISPATCH as the handler, hand-translates the Go
request hash into the ctx fields the AP_* layer reads, dispatches by
URL path (hard-coded /api/hello and /api/echo for now — file-name
dispatch lands in 1a.4), and assembles the response from the buffered
AP_* output + ctx_get("status") + ctx_get("headers_out").

Verified end-to-end:
  GET  /api/hello                       -> 200 JSON, method/ip echoed
  POST /api/echo?lang=ko (16-byte body) -> 200 JSON, body_parsed,
                                           query, user-agent
  GET  /api/nope                        -> 404 JSON

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 10:54:12 +09:00

267 lines
4.7 KiB
Plaintext

/*
* bridge_session.prg - FiveNode
*
* Copyright (c) 2026 Charles KWON ( Charles KWON Ohjun, charleskwonohjun@gmail.com )
* All rights reserved.
*/
#include "hbclass.ch"
// bridge_session.prg
// Session management - mod_harbourv2 MH_Sessions 100% compatible
THREAD STATIC ts_oSession
// ── Public API ──
FUNCTION fn_SessionInit( cName, nExpired )
hb_default( @cName, "MHSID" )
hb_default( @nExpired, 3600 )
ts_oSession := MH_Sessions():New( cName, nExpired )
ts_oSession:Init()
RETURN NIL
FUNCTION fn_Session( cKey, uValue )
IF ts_oSession == NIL
RETURN NIL
ENDIF
IF cKey == NIL
RETURN ts_oSession:hData
ENDIF
IF uValue != NIL
ts_oSession:hData[ cKey ] := uValue
RETURN uValue
ENDIF
IF hb_HHasKey( ts_oSession:hData, cKey )
RETURN ts_oSession:hData[ cKey ]
ENDIF
RETURN NIL
FUNCTION fn_SessionWrite()
IF ts_oSession != NIL
ts_oSession:Write()
ENDIF
RETURN NIL
FUNCTION fn_SessionEnd()
IF ts_oSession != NIL
ts_oSession:End()
ts_oSession := NIL
ENDIF
RETURN NIL
FUNCTION fn_SessionActive()
RETURN ts_oSession != NIL
FUNCTION fn_oSession( o )
IF o != NIL
ts_oSession := o
ENDIF
RETURN ts_oSession
FUNCTION fn_Garbage()
IF ts_oSession != NIL
ts_oSession:Garbage()
ENDIF
RETURN NIL
// ── MH_Sessions class ──
CREATE CLASS MH_Sessions
VAR cName
VAR cId
VAR nExpired
VAR hData
VAR cSessionPath
VAR lDestroy
METHOD New( cName, nExpired )
METHOD Init()
METHOD SetId()
METHOD SessionFile()
METHOD Read_CSID()
METHOD Read()
METHOD Write()
METHOD Data( cKey, uValue )
METHOD End()
METHOD Garbage( dMaxDate )
METHOD IsFile()
METHOD Info()
ENDCLASS
METHOD New( cName, nExpired ) CLASS MH_Sessions
::cName := cName
::nExpired := nExpired
::hData := { => }
::cId := ""
::lDestroy := .F.
::cSessionPath := ctx_get( "document_root", "." ) + "/.sessions"
IF ! hb_DirExists( ::cSessionPath )
hb_DirCreate( ::cSessionPath )
ENDIF
RETURN Self
METHOD Init() CLASS MH_Sessions
::Read_CSID()
IF Empty( ::cId )
::SetId()
fn_SetCookie( ::cName, ::cId, ::nExpired, "/", NIL, .F., .T. )
ELSE
::Read()
ENDIF
RETURN NIL
METHOD SetId() CLASS MH_Sessions
::cId := hb_MD5( hb_TToS( hb_DateTime() ) + hb_ntos( hb_RandomInt( 100000, 999999 ) ) )
RETURN ::cId
METHOD SessionFile() CLASS MH_Sessions
RETURN ::cSessionPath + "/" + ::cId + ".session"
METHOD Read_CSID() CLASS MH_Sessions
LOCAL cCookieVal := fn_GetCookies( ::cName )
IF ! Empty( cCookieVal )
::cId := cCookieVal
ENDIF
RETURN ::cId
METHOD Read() CLASS MH_Sessions
LOCAL cFile := ::SessionFile()
LOCAL cContent
IF hb_FileExists( cFile )
cContent := hb_MemoRead( cFile )
IF ! Empty( cContent )
hb_jsonDecode( cContent, @::hData )
IF ::hData == NIL
::hData := { => }
ENDIF
ENDIF
ENDIF
RETURN NIL
METHOD Write() CLASS MH_Sessions
LOCAL cFile
IF ::lDestroy
cFile := ::SessionFile()
IF hb_FileExists( cFile )
FErase( cFile )
ENDIF
RETURN NIL
ENDIF
cFile := ::SessionFile()
hb_MemoWrit( cFile, hb_jsonEncode( ::hData ) )
RETURN NIL
METHOD Data( cKey, uValue ) CLASS MH_Sessions
IF cKey == NIL
RETURN ::hData
ENDIF
IF uValue != NIL
::hData[ cKey ] := uValue
RETURN uValue
ENDIF
IF hb_HHasKey( ::hData, cKey )
RETURN ::hData[ cKey ]
ENDIF
RETURN NIL
METHOD End() CLASS MH_Sessions
::lDestroy := .T.
::Write()
fn_SetCookie( ::cName, "", 0, "/" )
RETURN NIL
METHOD Garbage( dMaxDate ) CLASS MH_Sessions
LOCAL aFiles, cFile
hb_default( @dMaxDate, Date() - 1 )
aFiles := Directory( ::cSessionPath + "/*.session" )
FOR EACH cFile IN aFiles
IF cFile[ 3 ] < dMaxDate
FErase( ::cSessionPath + "/" + cFile[ 1 ] )
ENDIF
NEXT
RETURN NIL
METHOD IsFile() CLASS MH_Sessions
RETURN hb_FileExists( ::SessionFile() )
METHOD Info() CLASS MH_Sessions
RETURN { ;
"id" => ::cId, ;
"name" => ::cName, ;
"expired" => ::nExpired, ;
"path" => ::cSessionPath, ;
"file" => ::SessionFile(), ;
"data" => ::hData, ;
"destroy" => ::lDestroy ;
}
// -- mod_harbour compatibility wrappers --
FUNCTION mh_SessionInit( cName, nExpired )
RETURN fn_SessionInit( cName, nExpired )
FUNCTION mh_Session( cKey, uValue )
RETURN fn_Session( cKey, uValue )
FUNCTION mh_SessionWrite()
RETURN fn_SessionWrite()
FUNCTION mh_SessionEnd()
RETURN fn_SessionEnd()
FUNCTION mh_SessionActive()
RETURN fn_SessionActive()
FUNCTION mh_oSession( o )
RETURN fn_oSession( o )
FUNCTION mh_Garbage()
RETURN fn_Garbage()