External services
Overview
You can map an existing external service to an EMQX Neuron SQL function so a rule can call it at runtime. When a rule uses that function, EMQX Neuron converts the input and output and invokes the service.
Configuration
An external function is defined with two files:
- A JSON file that describes the service. The file name becomes the service name in EMQX Neuron.
- A schema file that describes the API: method names and input/output types. Only Protocol Buffers is supported.
The JSON file has two parts:
about: metadata such as author, description, and help URL. See the sample below.interfaces: a set of service interfaces. Interfaces that share the same address can be grouped. Each interface has:protocol:"grpc"or"rest"."msgpack-rpc"is not in the default EMQX Neuron build; you must add themsgpackbuild tag and compile it yourself.address: a URL, for exampletcp://localhost:50000orhttps://localhost:8000.schemaType: currently only"protobuf".schemaFile: the proto file. REST and msgpack services also use proto.functions: maps schema RPCs to SQL function names. For example{"name":"helloFromMsgpack","serviceName":"SayHello"}exposesSayHelloas SQL functionhelloFromMsgpack. Unmapped RPCs keep their original names.options: protocol-specific options. For REST:headers: HTTP headersinsecureSkipVerify: skip HTTPS certificate checks
Example sample.json:
{
"about": {
"author": {
"name": "EMQ",
"email": "contact@emqx.io",
"company": "EMQ Technologies Co., Ltd",
"website": "https://www.emqx.io"
},
"helpUrl": {
"en_US": "https://github.com/lf-edge/ekuiper/blob/master/docs/en_US/plugins/functions/functions.md",
"zh_CN": "https://github.com/lf-edge/ekuiper/blob/master/docs/zh_CN/plugins/functions/functions.md"
},
"description": {
"en_US": "Sample external services for test only",
"zh_CN": "示例外部函数配置,仅供测试"
}
},
"interfaces": {
"trueno": {
"address": "tcp://localhost:50051",
"protocol": "grpc",
"schemaType": "protobuf",
"schemaFile": "trueno.proto"
},
"tsrest": {
"address": "http://localhost:8090",
"protocol": "rest",
"options": {
"insecureSkipVerify": true,
"headers": {
"Accept-Charset": "utf-8"
}
},
"schemaType": "protobuf",
"schemaFile": "tsrest.proto",
"functions": [
{
"name": "objectDetect",
"serviceName": "object_detection"
}
]
},
"tsrpc": {
"address": "tcp://localhost:9000",
"protocol": "msgpack-rpc",
"schemaType": "protobuf",
"schemaFile": "tsrpc.proto",
"functions": [
{
"name": "getFeature",
"serviceName": "get_feature"
},
{
"name": "getSimilarity",
"serviceName": "get_similarity"
}
]
}
}
}This sample service has three interfaces:
trueno: gRPCtsrest: RESTtsrpc: msgpack-rpc
Each interface is defined by its schema file. For tsrest, tsrest.proto looks like this:
syntax = "proto3";
package ts;
service TSRest { // proto service name is independent of the EMQX Neuron external service name
rpc object_detection(ObjectDetectionRequest) returns(ObjectDetectionResponse) {}
}
message ObjectDetectionRequest {
string cmd = 1;
string base64_img = 2 [json_name="base64_img"];
}
message ObjectDetectionResponse {
string info = 1;
int32 code = 2;
string image = 3;
string result = 4;
string type = 5;
}This file defines one RPC object_detection with protobuf request and response types. Prefer a single service per proto file, with multiple rpc methods if needed.
Protobuf uses proto3. See the proto3 spec.
HTTP options
For finer REST control (method, URL, parameters, body), you can add google.api.http annotations on each RPC.
The following snippet maps the method to POST /v1/computation/object_detection instead of the default /object_detection. body: "*" means the whole ObjectDetectionRequest becomes the request body.
service TSRest {
rpc object_detection(ObjectDetectionRequest) returns(ObjectDetectionResponse) {
option (google.api.http) = {
post: "/v1/computation/object_detection"
body: "*"
};
}
}If different commands use different URLs, put part of the input in the path. Here cmd is in the URL and base64_img is the body:
service TSRest {
rpc object_detection(ObjectDetectionRequest) returns(ObjectDetectionResponse) {
option (google.api.http) = {
post: "/v1/computation/object_detection/{cmd}"
body: "base64_img"
};
}
}Search-style APIs often put parameters in the query string:
service TSRest {
rpc SearchMessage(MessageRequest) returns(Message) {
option (google.api.http) = {
get: "/v1/messages"
};
}
}
message MessageRequest {
string author = 1;
string title = 2;
}With no body, all input fields become query parameters. SearchMessage({"author":"Author","title":"Message1"}) maps to GET /v1/messages?author=Author&title=Message1.
For the full mapping syntax, see transcoding mappings and HttpRule.
Usage
HTTP options require this import in the proto file:
syntax = "proto3";
package yourpackage;
import "google/api/annotations.proto";EMQX Neuron ships the Google API protos under etc/services/schemas/google, so you do not need to bundle them with a custom service.
Mapping
An external service needs one JSON file and at least one schema (.proto) file. Mapping has three layers:
- EMQX Neuron external service: the JSON file name is the service name.
- Interface: the
interfacessection. This virtual layer groups functions that share schema and address. - EMQX Neuron function: each
rpcin the proto file. Theservicein the proto file is unrelated to the EMQX Neuron external service name. By default the SQL function name is the RPC name. Override it withfunctionsin the JSON file.
Example: calling objectDetect in SQL:
- In the
tsrestinterface,{"name": "objectDetect","serviceName": "object_detection"}maps SQLobjectDetectto RPCobject_detection. - In
tsrest.proto, the RPCobject_detectionis found. Address and protocol come from thetsrestinterface.
REST arguments are encoded as JSON. JSON keys come from protobuf message fields and are converted to lower camel case. If the REST API does not use that convention, set json_name on the field.
Notes
REST and msgpack-rpc are not native protobuf, so they have limits.
REST defaults to POST with JSON. Use HTTP options to change method and URL.
- Without HTTP options, the input must be a message or
google.protobuf.StringValue. ForStringValue, pass an encoded JSON string such as"{\"name\":\"name1\",\"size\":1}".
msgpack-rpc:
- Input cannot be empty.
Register and manage
Register an external function in one of two ways:
- Place files in the configuration folder
- Register dynamically through the REST API
On startup, EMQX Neuron reads etc/services and registers services found there:
The file name must be
$serviceName.json. For examplesample.jsonregisters assample.Schema files go in
schemas:etc services schemas sample.proto random.proto ... sample.json other.json ...Changing files after startup does not reload them. Use the REST API for dynamic updates.
For dynamic register and management, see the external service REST API.
Use in SQL
After registration, every mapped function can be used in rules. For object_detection in sample.json, mapped as objectDetect:
SELECT objectDetect(cmd, img) from comandStreamThe REST service must be running at http://localhost:8090 with API http://localhost:8090/object_detection.
Parameter expansion
Proto parameters are usually messages. In EMQX Neuron you can pass either:
- One struct (not expanded)
- Multiple arguments in the order of the message fields
objectDetection takes this message:
message ObjectDetectionRequest {
string cmd = 1;
string base64_img = 2 [json_name="base64_img"];
}You can pass the whole struct, or two strings for cmd and base64_img.