Skip to content

Calculator server and client

These are the complete example programs compiled by the default CMake build. The website includes their source at build time, so the examples stay in sync with the repository. Follow getting started to run them.

Server

The params and result factories declare the method's Arrow schemas. Each field is a non-nullable float64. The add handler reads the two arguments from the request's array view and builds one result row. The method table connects those factories and the dispatcher to the Calculator protocol.

The default entry point serves stdin/stdout. Passing --http selects the HTTP host while keeping the protocol registration and handler identical.

examples/calculator.c
#include <stdlib.h>
#include <string.h>

#include "vgi_rpc/http.h"

static int params(struct ArrowSchema *schema, struct ArrowError *error) {
    (void)error;
    ArrowSchemaInit(schema);
    int rc = ArrowSchemaSetTypeStruct(schema, 2);
    const char *names[] = {"a", "b"};
    for (int i = 0; !rc && i < 2; ++i) {
        rc = ArrowSchemaSetType(schema->children[i], NANOARROW_TYPE_DOUBLE);
        if (!rc) rc = ArrowSchemaSetName(schema->children[i], names[i]);
        schema->children[i]->flags = 0;
    }
    return rc;
}
static int result(struct ArrowSchema *schema, struct ArrowError *error) {
    (void)error;
    ArrowSchemaInit(schema);
    int rc = ArrowSchemaSetTypeStruct(schema, 1);
    if (!rc) rc = ArrowSchemaSetType(schema->children[0], NANOARROW_TYPE_DOUBLE);
    if (!rc) rc = ArrowSchemaSetName(schema->children[0], "result");
    if (!rc) schema->children[0]->flags = 0;
    return rc;
}
static int add(void *state, struct VgiRpcCall *call, struct VgiRpcReply *reply) {
    (void)state;
    struct ArrowSchema schema = {0};
    int rc = result(&schema, &call->error);
    if (!rc) rc = VgiBatchInit(&reply->value, &schema, &call->error);
    if (!rc) {
        double a = ArrowArrayViewGetDoubleUnsafe(VgiBatchGet(&call->request, "a"), 0);
        double b = ArrowArrayViewGetDoubleUnsafe(VgiBatchGet(&call->request, "b"), 0);
        rc = ArrowArrayAppendDouble(reply->value.array.children[0], a + b);
    }
    if (!rc) rc = VgiBatchFinishRow(&reply->value, &call->error);
    if (schema.release) ArrowSchemaRelease(&schema);
    return rc;
}
int main(int argc, char **argv) {
    static const struct VgiMethod methods[] = {
        {"add", VGI_METHOD_UNARY, params, result, NULL, NULL, VGI_STREAM_UNKNOWN}};
    struct VgiRpcProtocol protocol = {"Calculator", "1.0.0", "", methods, 1, add, NULL, NULL};
    struct ArrowError error = {{0}};
    struct VgiRpcServer *server = VgiRpcServerNew();
    if (!server) return 1;
    int rc = VgiRpcServerAddProtocol(server, &protocol, &error);
    if (!rc && argc > 1 && !strcmp(argv[1], "--http")) {
        struct VgiHttpOptions options;
        VgiHttpOptionsInit(&options);
        options.port = argc > 2 ? (unsigned)strtoul(argv[2], NULL, 10) : 0;
        rc = VgiRpcServerServeHttp(server, &options, &error);
    } else if (!rc)
        rc = VgiRpcServerServe(server, stdin, stdout, &error);
    if (rc) fprintf(stderr, "%s\n", error.message);
    VgiRpcServerFree(server);
    return rc ? 1 : 0;
}

Client

The client chooses a subprocess or HTTP connection from its command line, builds a request with the exact parameter schema, and calls VgiClientUnary. The returned batch belongs to the caller. Both input and output are reset before the connection is freed.

examples/client.c
#include "vgi_rpc/client.h"

#include <string.h>

int main(int argc, char **argv) {
    if (argc < 2) {
        fprintf(stderr, "Usage: %s WORKER | --http URL\n", argv[0]);
        return 1;
    }
    struct VgiClientOptions options;
    VgiClientOptionsInit(&options);
    options.protocol = "Calculator";
    options.protocol_version = "1.0.0";
    const char *command[] = {argv[1], NULL};
    options.argv = command;
    if (argc == 3 && !strcmp(argv[1], "--http")) {
        options.transport = VGI_CLIENT_HTTP;
        options.target = argv[2];
    }
    struct VgiClient *client = NULL;
    struct ArrowError error = {{0}};
    int rc = VgiClientConnect(&options, &client, &error);
    struct ArrowSchema schema = {0};
    struct VgiAnnotatedBatch input = {0}, output = {0};
    if (!rc) {
        ArrowSchemaInit(&schema);
        rc = ArrowSchemaSetTypeStruct(&schema, 2);
    }
    const char *names[] = {"a", "b"};
    for (int i = 0; !rc && i < 2; ++i) {
        rc = ArrowSchemaSetType(schema.children[i], NANOARROW_TYPE_DOUBLE);
        if (!rc) rc = ArrowSchemaSetName(schema.children[i], names[i]);
        schema.children[i]->flags = 0;
    }
    if (!rc) rc = VgiBatchInit(&input.batch, &schema, &error);
    if (!rc) rc = ArrowArrayAppendDouble(input.batch.array.children[0], 2);
    if (!rc) rc = ArrowArrayAppendDouble(input.batch.array.children[1], 3);
    if (!rc) rc = VgiBatchFinishRow(&input.batch, &error);
    if (!rc) rc = VgiClientUnary(client, "add", &input, &output);
    if (!rc) rc = VgiBatchView(&output.batch, &error);
    if (!rc) {
        double value = ArrowArrayViewGetDoubleUnsafe(VgiBatchGet(&output.batch, "result"), 0);
        printf("2 + 3 = %.0f\n", value);
        if (value != 5) rc = 1;
    }
    if (rc) {
        const struct VgiClientError *remote = client ? VgiClientLastError(client) : NULL;
        fprintf(stderr, "%s\n", remote && remote->message ? remote->message : error.message);
    }
    VgiAnnotatedBatchReset(&input);
    VgiAnnotatedBatchReset(&output);
    if (schema.release) ArrowSchemaRelease(&schema);
    VgiClientFree(client);
    return rc ? 1 : 0;
}

Extend the example

Source: calculator.c and client.c.