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>
92 lines
2.8 KiB
Plaintext
92 lines
2.8 KiB
Plaintext
// app/bridge_server.prg — 1a.3-3 end-to-end glue.
|
|
//
|
|
// Wires httpserver (Go) → bridge_capi (Go) → bridge_*.prg so a PRG
|
|
// handler written in the mod_harbour AP_* style runs inside the
|
|
// fivenode_go single binary exactly as it would have run inside the
|
|
// koffi/N-API fivenode bridge.
|
|
//
|
|
// Demo routing is hard-coded to two paths so we can curl the binary
|
|
// and see the AP_* surface working end-to-end. File-name dispatch
|
|
// (POST /api/foo.prg → app/api/foo.prg) is sub-phase 1a.4 work.
|
|
|
|
FUNCTION Main()
|
|
LOCAL cErr := HTTP_SERVER_START( ":8090", "BRIDGEDISPATCH" )
|
|
IF cErr != NIL
|
|
? "httpserver:", cErr
|
|
ENDIF
|
|
RETURN NIL
|
|
|
|
// Entry point invoked by httpserver. hReq comes from
|
|
// buildRequestHash in hbrtl_ext/httpserver/server.go.
|
|
FUNCTION BridgeDispatch( hReq )
|
|
LOCAL hCtx
|
|
|
|
// Translate the Go-side request hash into the ctx fields the
|
|
// mod_harbour AP_* PRG layer expects to read.
|
|
hCtx := { ;
|
|
"method" => hReq[ "method" ], ;
|
|
"filename" => hReq[ "path" ], ;
|
|
"query_string" => hReq[ "query" ], ;
|
|
"body" => hReq[ "body" ], ;
|
|
"remote_ip" => RemoteIPOnly( hReq[ "remote_addr" ] ), ;
|
|
"headers_in" => hReq[ "headers" ], ;
|
|
"headers_out" => { => }, ;
|
|
"status" => 200 ;
|
|
}
|
|
_CTX_SET_JSON( hb_jsonEncode( hCtx ) )
|
|
_OUT_CLEAR()
|
|
|
|
// Hard-coded route dispatch. Replace with file-name dispatch in
|
|
// 1a.4 once we want to land the labdb API surface unchanged.
|
|
DO CASE
|
|
CASE hReq[ "path" ] == "/api/hello"
|
|
ApiHello()
|
|
CASE hReq[ "path" ] == "/api/echo"
|
|
ApiEcho()
|
|
OTHERWISE
|
|
ctx_set( "status", 404 )
|
|
AP_JSONRESPONSE( { "error" => "not found", "path" => hReq[ "path" ] } )
|
|
ENDCASE
|
|
|
|
// Assemble hResp from the buffered AP_* output.
|
|
RETURN { ;
|
|
"status" => ctx_get( "status", 200 ), ;
|
|
"headers" => ctx_get( "headers_out", { => } ), ;
|
|
"body" => _OUT_GET() ;
|
|
}
|
|
|
|
FUNCTION ApiHello()
|
|
AP_JSONRESPONSE( { ;
|
|
"ok" => .t., ;
|
|
"msg" => "hello from fivenode_go bridge layer", ;
|
|
"method" => AP_METHOD(), ;
|
|
"ip" => AP_USERIP() ;
|
|
} )
|
|
RETURN NIL
|
|
|
|
FUNCTION ApiEcho()
|
|
LOCAL cBody := AP_BODY()
|
|
LOCAL hPayload := IIF( Empty( cBody ), { => }, hb_jsonDecode( cBody ) )
|
|
AP_JSONRESPONSE( { ;
|
|
"ok" => .t., ;
|
|
"method" => AP_METHOD(), ;
|
|
"query" => AP_ARGS(), ;
|
|
"body_len" => Len( cBody ), ;
|
|
"body_parsed" => hPayload, ;
|
|
"user_agent" => hb_HGetDef( ctx_get( "headers_in", { => } ), "user-agent", "?" ) ;
|
|
} )
|
|
RETURN NIL
|
|
|
|
// Strip the ":port" portion of a Go RemoteAddr so AP_USERIP returns
|
|
// the bare IP the way mod_harbour does.
|
|
FUNCTION RemoteIPOnly( cAddr )
|
|
LOCAL n
|
|
IF Empty( cAddr )
|
|
RETURN ""
|
|
ENDIF
|
|
n := RAt( ":", cAddr )
|
|
IF n > 0
|
|
RETURN Left( cAddr, n - 1 )
|
|
ENDIF
|
|
RETURN cAddr
|