Date: 2025-12-18 Report ID: 002 Subject: APC WebSocket Handler for RFC/BAPI Calls Status: Complete
ZADT_VSP is an optional SAP-side WebSocket handler that enables stateful operations not available through standard ADT REST APIs. The primary use case is RFC/BAPI function module calls with full parameter support.
┌─────────────────────────────────────────────────────────────┐
│ vsp CLI / MCP Client │
└─────────────────────────────────────────────────────────────┘
│
│ WebSocket (ws://)
▼
┌─────────────────────────────────────────────────────────────┐
│ SAP APC (ABAP Push Channel) │
│ /sap/bc/apc/sap/zadt_vsp │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ ZCL_VSP_APC_HANDLER │
│ - Session management │
│ - Message routing │
│ - Service registry │
└─────────────────────────────────────────────────────────────┘
│
┌───────────────┼───────────────┐
▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ ZCL_VSP_RFC_ │ │ ZCL_VSP_DEBUG_ │ │ Future services │
│ SERVICE │ │ SERVICE (TODO) │ │ │
│ domain: "rfc" │ │ domain: "debug" │ │ │
└─────────────────┘ └─────────────────┘ └─────────────────┘
| Object | Type | Description |
|---|---|---|
$ZADT_VSP |
Package | Container package |
ZIF_VSP_SERVICE |
Interface | Service contract |
ZCL_VSP_APC_HANDLER |
Class | Main WebSocket handler |
ZCL_VSP_RFC_SERVICE |
Class | RFC domain service |
ZADT_VSP |
APC Application | WebSocket endpoint config |
{
"id": "unique-request-id",
"domain": "rfc",
"action": "call",
"params": {
"function": "BAPI_USER_GET_DETAIL",
"USERNAME": "TESTUSER"
},
"timeout": 30000
}{
"id": "unique-request-id",
"success": true,
"data": { ... }
}{
"id": "unique-request-id",
"success": false,
"error": {
"code": "ERROR_CODE",
"message": "Human readable message"
}
}Execute any RFC-enabled function module with parameters.
Request:
{
"id": "1",
"domain": "rfc",
"action": "call",
"params": {
"function": "BAPI_USER_GET_DETAIL",
"USERNAME": "TESTUSER"
}
}Response:
{
"id": "1",
"success": true,
"data": {
"subrc": 0,
"exports": {
"ADDRESS": {
"FIRSTNAME": "Alice",
"LASTNAME": "V",
...
},
"LOGONDATA": { ... }
},
"tables": {
"PROFILES": [
{"BAPIPROF": "SAP_ALL", "BAPIPTEXT": "All SAP System authorizations", ...},
{"BAPIPROF": "S_A.SYSTEM", ...}
],
"RETURN": []
}
}
}Supported parameter types:
- Scalar types (CHAR, STRING, INT, etc.) ✅
- Structure EXPORTING params → JSON objects ✅
- TABLE params → arrays of JSON objects ✅
- Nested structures →
[complex]placeholder ✅
Search for function modules by pattern.
Request:
{
"id": "2",
"domain": "rfc",
"action": "search",
"params": {"pattern": "BAPI_USER*"}
}Get parameter information for a function module.
Request:
{
"id": "3",
"domain": "rfc",
"action": "getMetadata",
"params": {"function": "BAPI_USER_GET_DETAIL"}
}Simple ping/pong for connection health.
Request:
{
"id": "4",
"domain": "rfc",
"action": "ping"
}{
"id": "5",
"domain": "system",
"action": "ping"
}- Create package
$ZADT_VSP(local, no transport) - Create interface
ZIF_VSP_SERVICEfromembedded/abap/zif_vsp_service.intf.abap - Create class
ZCL_VSP_RFC_SERVICEfromembedded/abap/zcl_vsp_rfc_service.clas.abap - Create class
ZCL_VSP_APC_HANDLERfromembedded/abap/zcl_vsp_apc_handler.clas.abap - Create APC application
ZADT_VSP:- Transaction:
SAPC - Application ID:
ZADT_VSP - Handler Class:
ZCL_VSP_APC_HANDLER - Connection Type: WebSocket
- URL Path:
/sap/bc/apc/sap/zadt_vsp
- Transaction:
# Deploy all WebSocket handler objects
vsp deploy-handler
# Or deploy individual objects
vsp import --file embedded/abap/zif_vsp_service.intf.abap --package $ZADT_VSP
vsp import --file embedded/abap/zcl_vsp_rfc_service.clas.abap --package $ZADT_VSP
vsp import --file embedded/abap/zcl_vsp_apc_handler.clas.abap --package $ZADT_VSP# Via MCP tools
vsp WriteSource --object_type INTF --name ZIF_VSP_SERVICE --package $ZADT_VSP --source "$(cat embedded/abap/zif_vsp_service.intf.abap)"After deploying objects, activate the APC application:
-
Create APC Application (SAPC transaction):
- Application ID:
ZADT_VSP - Description:
VSP WebSocket Handler - Handler Class:
ZCL_VSP_APC_HANDLER - Subprotocol: (leave empty)
- State: Stateful
- Application ID:
-
Activate ICF Service:
- Transaction:
SICF - Path:
/sap/bc/apc/sap/zadt_vsp - Activate the service node
- Transaction:
-
Test Connection:
# Simple WebSocket test wscat -c "ws://host:port/sap/bc/apc/sap/zadt_vsp?sap-client=001" \ -H "Authorization: Basic $(echo -n user:pass | base64)"
import (
"github.com/gorilla/websocket"
)
// Connect
wsURL := "ws://host:port/sap/bc/apc/sap/zadt_vsp?sap-client=001"
header := http.Header{}
header.Set("Authorization", "Basic "+base64Auth)
conn, _, err := websocket.DefaultDialer.Dial(wsURL, header)
// Read welcome message
_, msg, _ := conn.ReadMessage()
fmt.Printf("Welcome: %s\n", msg)
// Call RFC
callMsg := `{"id":"1","domain":"rfc","action":"call","params":{"function":"RFC_SYSTEM_INFO"}}`
conn.WriteMessage(websocket.TextMessage, []byte(callMsg))
_, response, _ := conn.ReadMessage()
fmt.Printf("Response: %s\n", response)The RFC service uses dynamic function calls with PARAMETER-TABLE:
" Key insight: kind values are from CALLER perspective
" Function IMPORTS → Caller EXPORTS (abap_func_exporting)
" Function EXPORTS → Caller IMPORTS (abap_func_importing)
" Function TABLES → abap_func_tables
DATA lt_ptab TYPE abap_func_parmbind_tab.
" For function's IMPORT params
ls_ptab-kind = abap_func_exporting. " We export TO the function
" For function's EXPORT params
ls_ptab-kind = abap_func_importing. " We import FROM the function
CALL FUNCTION lv_function
PARAMETER-TABLE lt_ptab
EXCEPTION-TABLE lt_etab.To avoid dumps when serializing complex types:
DATA(lo_type) = cl_abap_typedescr=>describe_by_data( <fs_data> ).
IF lo_type->kind = cl_abap_typedescr=>kind_elem.
" Safe to convert to string
lv_str = <fs_data>.
ELSE.
" Structure/table - need component-level serialization
...
ENDIF.For STRING type, empty string "" is NOT INITIAL:
" Wrong - returns TRUE for empty string
IF lv_string IS NOT INITIAL.
" Correct
IF strlen( lv_string ) > 0.The architecture supports adding a debug domain for stateful debugging:
{
"id": "1",
"domain": "debug",
"action": "setBreakpoint",
"params": {
"program": "ZTEST",
"line": 42
}
}Planned actions:
setBreakpoint- Set breakpointlisten- Wait for debuggeeattach- Attach to debuggeestep- Step into/over/outgetVariables- Get variable valuescontinue- Continue execution
- WebSocket endpoint requires SAP authentication
- Uses same authorization as user's SAP session
- RFC calls execute with user's authorizations
- No special privileges required beyond standard RFC auth
- Nested structures beyond 2 levels show
[complex] - CHANGING parameters not fully supported yet
- Binary data (XSTRING) serialized as hex string
- Large responses may impact performance
adt-abap-internals-documentation.md- ADT API details2025-12-05-014-external-debugger-scripting-vision.md- Debug domain designcookie-auth-implementation-guide.md- Authentication options