gRPC API Configuration
SmartWeb exposes a server-to-server gRPC API (HTTP/2) that lets clients invoke D2000 RPC procedures, subscribe to object value changes, stream archive and EDA data, and receive RPCs that D2000 initiates against the client. Multiple independent gRPC server instances can be started on different ports, each with its own TLS configuration and access filter.
The gRPC API is based on a standard gRPC proto contract; the gRPC service is named D2Api.
Methods of the D2Api service
Method | gRPC cardinality | Purpose |
|---|---|---|
| unary | Invoke a single D2000 RPC procedure and receive the result |
| bidirectional streaming | Multi-step RPC conversation between client and D2000 |
| unary | Invoke a Simple Byte Array (binary) RPC |
| server streaming | Real-time value-change subscription for a D2000 object |
| server streaming | Read historical archive rows for a time range |
| server streaming | Read EDA vector values |
| unary | Write timestamped values to an EDA vector |
| server streaming | Receive RPCs that D2000 initiates against the client |
In addition, every gRPC server instance also registers:
grpc.reflection.v1alpha.ServerReflection— enables introspection viagrpc_cli/ Postmangrpc.health.v1.Health— standard gRPC health protocol (see gRPC health checks)
Configuration
The gRPC API is configured under smartweb.application.grpcApi.configItems[]. Each entry starts an independent gRPC server with its own bind address, port, TLS settings, response headers and access filter.
smartweb:
application:
grpcApi:
configItems:
- enabled: true
host: "0.0.0.0" # bind address; "0.0.0.0" listens on all interfaces
port: 9090
socketTimeoutMs: 60000 # keep-alive time on the connection
httpHeaders: # static response metadata injected into every reply
- "X-Server: SmartWeb-gRPC"
- "X-Api-Version: 1.0"
ssl:
enabled: true
keyStore:
path: cert/keystore.p12
password: changeit
keyAlias: smartweb
trustStore:
path: cert/truststore.p12
password: changeit
clientAuth: REQUIRE # NONE | OPTIONAL | REQUIRE
accessFilter:
allowedD2RpcEventNames:
- "E.MyRpc"
allowedD2RpcMethodNames:
- "Calculate"
- "GetStatus"
allowedD2EdaVectorReadCodes:
- "*"
allowedD2EdaVectorUpdateCodes:
- "*"Property | Description | Default |
|---|---|---|
| Enables or disables this gRPC server instance |
|
| Bind address |
|
| Listening port |
|
| keep-alive time, in milliseconds | — |
| Static metadata headers injected into every response, format | none |
| Enable TLS for this instance |
|
| PKCS12 keystore path (absolute or relative to the config directory) | — |
| Keystore password |
|
| Key alias (the first alias in the keystore is used if omitted) | — |
| Truststore path (required for validated mTLS) | — |
| Truststore password |
|
|
|
|
| Wildcard list of allowed D2000 event names |
|
| Wildcard list of allowed RPC method names |
|
| Wildcard list of EDA vector codes that may be read |
|
| Wildcard list of EDA vector codes that may be written |
|
[!NOTE]
Each configItems entry produces an independent gRPC server. You can run, for example, a strict mTLS instance on port 9090 for external partners, an internal TLS-only instance on port 9091, and a plaintext instance on port 9092 bound to a private interface — all in the same SmartWeb process.
Access filter
The access filter is applied to every individual call, including every message inside a bidirectional conversation. Wildcard matching is case-insensitive:
Pattern | Matches |
|---|---|
| Any value |
| Any event name starting with |
| The exact method name |
A denied call returns Status.PERMISSION_DENIED and terminates the stream.
Example — restrict access to a three-step conversation flow (Register("User_01") → Calculate(...) → CloseE()):
accessFilter:
allowedD2RpcEventNames:
- "E.TRAY_testSender1"
allowedD2RpcMethodNames:
- "Register"
- "Calculate"
- "CloseE"Response metadata headers
The httpHeaders list is injected into the response metadata of every gRPC call.
httpHeaders:
- "X-Server: SmartWeb-gRPC"
- "X-Api-Version: 1.0"
- "Access-Control-Allow-Origin: *"[!NOTE] gRPC metadata keys are normalised to lowercase internally.
TLS / mTLS
Each gRPC server instance has its own TLS configuration. When clientAuth is OPTIONAL or REQUIRE, the peer TLS certificate chain is made available to the application's authentication layer.
Self-signed development certificate
keytool -genkeypair -alias smartweb -keyalg RSA -keysize 2048 \
-storetype PKCS12 -keystore keystore.p12 -validity 365 \
-storepass changeit \
-dname "CN=localhost, OU=d2000, O=Ipesoft, L=Zilina, ST=SK, C=SK" \
-ext "SAN=dns:localhost,ip:127.0.0.1"Full mTLS setup (server + CA + client)
# Server keystore
keytool -genkeypair -alias smartweb -keyalg RSA -keysize 2048 -storetype PKCS12 \
-keystore keystore.p12 -validity 365 -storepass changeit \
-dname "CN=localhost, OU=d2000, O=Ipesoft, L=Zilina, ST=SK, C=SK" \
-ext "SAN=dns:localhost,ip:127.0.0.1"
# CA keystore
keytool -genkeypair -alias ca -keyalg RSA -keysize 2048 -storetype PKCS12 \
-keystore ca-keystore.p12 -validity 3650 -storepass changeit \
-dname "CN=MyCA, OU=d2000, O=Ipesoft, L=Zilina, ST=SK, C=SK" \
-ext "BasicConstraints:critical=ca:true"
# Export CA certificate
keytool -exportcert -alias ca -keystore ca-keystore.p12 -storepass changeit -rfc -file ca.crt
# Client keystore
keytool -genkeypair -alias smartweb-client -keyalg RSA -keysize 2048 -storetype PKCS12 \
-keystore client-keystore.p12 -validity 365 -storepass changeit \
-dname "CN=smartweb-client, OU=d2000, O=Ipesoft, L=Zilina, ST=SK, C=SK"
# Client CSR
keytool -certreq -alias smartweb-client -keystore client-keystore.p12 -storepass changeit -file client.csr
# CA signs the client CSR
keytool -gencert -alias ca -keystore ca-keystore.p12 -storepass changeit \
-infile client.csr -outfile client-signed.crt -validity 365 -rfc \
-ext "KeyUsage=digitalSignature" -ext "ExtendedKeyUsage=clientAuth"
# Import CA + signed client cert into the client keystore (trust chain)
keytool -importcert -alias ca -file ca.crt -keystore client-keystore.p12 -storepass changeit -noprompt
keytool -importcert -alias smartweb-client -file client-signed.crt \
-keystore client-keystore.p12 -storepass changeit -noprompt
# Server truststore — used to validate client certificates
keytool -importcert -alias ca -file ca.crt -keystore truststore.p12 \
-storetype PKCS12 -storepass changeit -nopromptFile | Purpose | Needed at runtime |
|---|---|---|
| Server private key + server certificate | Server |
| CA private key + CA certificate (used only for signing) | Offline |
| CA public certificate | Used to build the truststore only |
| Client private key + CA-signed certificate | Client |
| Server truststore containing the CA certificate | Server (mTLS only) |
[!WARNING]
When clientAuth is OPTIONAL or REQUIRE but no truststore is configured, the client certificate is not validated against any CA chain — it is only made available to the application layer for inspection. Do not use this configuration in production.
gRPC health checks
Each gRPC server instance implements the standard grpc.health.v1.Health service. The health status is evaluated dynamically on every Check call.
Service name | Status source |
|---|---|
| Overall — |
| D2000 connection |
| EDA connection (returns |
| Named D2 service connector |
Unknown service names return Status.NOT_FOUND.
Example check via grpc_cli:
grpc_cli call localhost:9090 grpc.health.v1.Health/Check "service: 'd2api'"
grpc_cli call localhost:9090 grpc.health.v1.Health/Check "service: ''"Example configurations
Multi-instance: strict mTLS, internal TLS, and plaintext
server:
port: 8443
smartweb:
connections:
- host: localhost
port: 3120
authentication:
authModes:
- AUTH_CREDENTIALS_IN_SESSION
- AUTH_CERTIFICATE_REMOTELY
apiKeys:
enabled: true
application:
serverSsl:
enabled: true
keyStore:
path: cert/keystore.p12
password: changeit
keyAlias: smartweb
trustStore:
path: cert/truststore.p12
password: changeit
clientAuth: REQUIRE
grpcApi:
configItems:
# Instance 1 — mTLS on port 9090, all events and methods allowed
- enabled: true
host: "0.0.0.0"
port: 9090
socketTimeoutMs: 60000
httpHeaders:
- "X-Server: SmartWeb-gRPC"
- "X-Api-Version: 1.0"
ssl:
enabled: true
keyStore:
path: cert/keystore.p12
password: changeit
keyAlias: smartweb
trustStore:
path: cert/truststore.p12
password: changeit
clientAuth: REQUIRE
accessFilter:
allowedD2RpcEventNames: ["*"]
allowedD2RpcMethodNames: ["*"]
# Instance 2 — one-way TLS on port 9091, restricted to E.TRAY_* events
- enabled: true
host: "0.0.0.0"
port: 9091
socketTimeoutMs: 60000
ssl:
enabled: true
keyStore:
path: cert/keystore.p12
password: changeit
keyAlias: smartweb
clientAuth: OPTIONAL
accessFilter:
allowedD2RpcEventNames:
- "E.TRAY_*"
allowedD2RpcMethodNames:
- "*"
# Instance 3 — plaintext, internal interface only, default filter (all allowed)
- enabled: true
host: "127.0.0.1"
port: 9092
ssl:
enabled: falseTightly scoped filter — single conversation flow
smartweb:
application:
grpcApi:
configItems:
- enabled: true
host: "0.0.0.0"
port: 9090
ssl:
enabled: true
keyStore:
path: cert/keystore.p12
password: changeit
clientAuth: REQUIRE
accessFilter:
allowedD2RpcEventNames:
- "E.TRAY_testSender1"
- "E.RPC_TEST_FUNCTIONS"
allowedD2RpcMethodNames:
- "Register*"
- "Calculate"
- "CloseE"
- "OutputParams"Diagnostic logging for the gRPC layer
logging:
level:
io.grpc.netty: WARN
io.grpc.services: WARN
com.ipesoft.smartweb.core.grpc: DEBUGJava client example
The following example calls the OutputParams RPC procedure, defined in D2000 in an ESL script. The procedure has six output (OUT) parameters, including a structured parameter of type SD.TestSmart:
RPC PROCEDURE OutputParams(BOOL _bool, INT _int, REAL _real, TIME _time, TEXT _text, RECORD NOALIAS (SD.TestSmart) _struct)
_bool := @TRUE
_int := 63
_real := 3.14
_time := SysTime
_text := %GenD2UID()
REDIM _struct[1]
_struct[1]^Bool := @TRUE
_struct[1]^Int := 2
_struct[1]^Real := 2.324
_struct[1]^ATime := SysTime - 60 * 60
_struct[1]^RTime := 1024
_struct[1]^Text := "TEST"
END OutputParamsThe SD.TestSmart structure has six columns: Bool (BOOL), Int (INT), Real (REAL), ATime (absolute time), RTime (relative time) and Text (TEXT).
Since all of the procedure's parameters are output parameters, the client sends a placeholder UnivalValue for each one, with the type (type) set and a name (returnAs) under which the output value will appear in the RpcResponse.values map:
ManagedChannel channel = ManagedChannelBuilder
.forAddress("smartweb.example.com", 9090)
.useTransportSecurity()
.build();
D2ApiGrpc.D2ApiBlockingStub stub = D2ApiGrpc.newBlockingStub(channel)
.withCallCredentials(new ApiKeyCredentials("my-api-key"));
RpcResponse response = stub.rpc(RpcCall.newBuilder()
.setEventName("E.RPC_TEST_FUNCTIONS")
.setName("OutputParams")
.addParameter(UnivalValue.newBuilder().setType(UnivalType.Bool).setReturnAs("bool"))
.addParameter(UnivalValue.newBuilder().setType(UnivalType.Int).setReturnAs("int"))
.addParameter(UnivalValue.newBuilder().setType(UnivalType.Real).setReturnAs("real"))
.addParameter(UnivalValue.newBuilder().setType(UnivalType.Time).setReturnAs("time"))
.addParameter(UnivalValue.newBuilder().setType(UnivalType.Text).setReturnAs("text"))
.addParameter(UnivalValue.newBuilder().setType(UnivalType.Record).setReturnAs("struct"))
.build());
// Scalar output parameters — from the RpcResponse.values map, keyed by the returnAs names
Map<String, UnivalValue> out = response.getValuesMap();
BoolValue bool = out.get("bool").getBoolValue(); // False / True / Oscilate
long i = out.get("int").getIntValue(); // 63
double real = out.get("real").getRealValue(); // 3.14
Timestamp time = out.get("time").getTime();
String text = out.get("text").getText();
// Structured (RECORD) output parameter of type SD.TestSmart
RecordValue struct = out.get("struct").getRecordValue();
RecordRow row = struct.getRow(0); // first row (REDIM _struct[1])
// the column order matches the SD.TestSmart structure definition:
BoolValue colBool = row.getValue(0).getBoolValue(); // Bool
long colInt = row.getValue(1).getIntValue(); // Int
double colReal = row.getValue(2).getRealValue(); // Real
Timestamp colATime = row.getValue(3).getTime(); // ATime — absolute time
Duration colRTime = row.getValue(4).getTimespan(); // RTime — relative time
String colText = row.getValue(5).getText(); // Text