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>
267 lines
4.7 KiB
Plaintext
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()
|