Vely logo install | documentation | examples | articles | changelog
16.6.0 released on Mar 08, 2023 | articles updated on Mar 20, 2023

Client API


You can use Vely C API client library to connect to a FastCGI application server, including Vely:
See Examples section below for detailed examples.

Sending a request

int vv_fc_request (vv_fc *req);
All input and output parameters are contained in a single variable of type "vv_fc", the pointer to which is passed to "vv_fc_request()" function that runs a server request. This also has the advantage of forward compatibility because any future changes are contained in this type, and is also simple to use.

A variable of type "vv_fc" must be initialized to zero before using (such as with {0} initialization, "memset()" or "calloc()"), or otherwise some of its members may have random values:
// Define and initialize request variable
vv_fc req = {0};
// You could also do:
// memset ((char*)&req, 0, sizeof(vv_fc));
...
int result = vv_fc_request (&req);
- Return value
The following are possible return values from "vv_fc_request()":

- Server reply
The server replies with data that is split in two. One part is the actual result of processing (called "stdout" or standard output), and the other is the error messages (called "stderr" or standard error).

All Vely output goes to "stdout", except from report-error and pf-out/pf-url/pf-web (with "to-error" clause) which goes to "stderr". The two outputs can be comingled in any way; the client will properly separate the two and present them as two messages in reply.

Request type (vv_fc)

Type "vv_fc" is defined as:
typedef struct {
    const char *fcgi_server; // the IP:port/socket_path to server
    const char *req_method; // request method
    const char *app_path; // application path
    const char *req; // request name
    const char *url_payload; // URL payload (path+query string)
    const char *content_type; // content type
    int content_len; // content len
    const char *req_body; // request body (i.e. content)
    char **env; // environment to pass into request on server side
    int timeout; // timeout for request
    int req_status; // status of request from server
    char *data; // actual response from server
    int data_len; // length of response from server
    char *error; // error message from server
    int error_len; // length of error from server
    // other elements used internally by Vely client
    vv_fc_out_hook out_hook; // hook to get output data as soon as it arrives
    vv_fc_err_hook err_hook; // hook get error data as soon as it arrives
} vv_fc;
Here is the more detailed explanation:

"fcgi_server" represents either a Unix socket or a TCP socket and is:
"req_method" is a request method, such as GET, POST, PUT, DELETE or any other. Request method must be specified.

"app_path" is an application path (see request_URL). By default it's "/<application name>". Application path must be specified.

"req" is the request name preceded by a forward slash, as in "/<request name>" (see request_URL). Request name must be specified.

"url_payload" is the input parameters (as path segments and query string, see request_URL). URL payload can be NULL or empty, in which case it is not used.

"content_type" is the content type of request body (for instance it may be "application/json" or "image/jpg").

"content_len" is the length of request body in bytes.

"req_body" is the request body, which can be any text or binary data of length "content len". A request body is sent only if content type and request body are not NULL and not empty and content length is greater than zero.

"env" is any environment variables that should be passed along to the server. You can access those in Vely via "web-environment" clause of get-sys statement. This is an array of strings, where name/value pairs are specified one after the other, and which always must end with NULL. For example, if you want to use variable "REMOTE_USER" with value "John" and variable "MY_VARIABLE" with value "8000", then it might look like this:
char *env[5];
env[0] = "REMOTE_USER";
env[1] = "John"
env[2] = "MY_VARIABLE";
env[3] = "8000"
env[4] = NULL;
Thus, if you are passing N environment variables to the server, you must size "env" as "char*" array with 2*N+1 elements.

"timeout" is the number of seconds a request should not exceed. For instance if the remote server is taking too long or if the network connection is too slow, you can limit how long a request may take. If there is no timeout, then "timeout" value should be zero. Note that DNS resolution of the host name (in case you are using a TCP socket) is not counted in timeout. Maximum value for timeout is 86400.

"req_status" is the return status of server execution. This is a part of FastCGI specification and the particular server software you are using may or may not return the status. Vely server request returns status by means of exit-code statement.

"data" is the actual response from server as passed to stdout stream. In Vely, any output will go to "data", except when "to-error" clause is used in pf-out, pf-url and pf-web - use these constructs to output errors without stopping the request's execution. Additionaly, the output of report-error will also not go to "data".

"data_len" is the length of "data" response from server in bytes. The response is always null-terminated as a courtesy, and "data_len" does not include the terminating null byte.

"error" is any error messages from a server response, i.e. data passed to stderr stream. It is comprised of any output when "to-error" clause is used in pf-out, pf-url and pf-web, as well as any output from report-error.

"error_len" is the length of "error" response from server in bytes. The response is always null-terminated as a courtesy, and "data_len" does not include the terminating null byte.

Note that stdout and stderr streams can be co-mingled, but the client will obtain them separately. This allows for clean separation of output from any error messages.

Getting data reply (stdout)

See "data" and "data_len" members of "vv_fc" type above.

Getting error reply (stderr)

See "error" and "error_len" members of "vv_fc" type above.

Getting the exit status of request

See "req_status" member of "vv_fc" type above.

Freeing the results of a request

Once you have obtained the results of a request, and when no longer needed, you should free them:
// Declare and initialize request variable
vv_fc req = {0};
// Setup the req variable
...
// Execute request
vv_fc_request (&req);
// Free request output (data and error streams)
free (req.data);
free (req.error);
If you do not free the results of a request, your program may experience a memory leak. If your program exits right after issuing any request(s), you may skip freeing results as that is automatically done on exit by the Operating System. If you wish to save the results of multiple executions and process them later as a group, you can omit freeing them and save "req.data" (and/or "req.error") in some kind of an array for later processing; this way you do not have to copy the results unnecessarily.

Asynchronous hooks

You can obtain the server's reply as it arrives by specifying read hooks. This is useful if the server supplies partial replies over a period of time, and your application can get those partial replies as they become available.

To specify a hook for output data (i.e. from stdout), you must write a C function with the following signature:
typedef void (*vv_fc_out_hook)(char *recv, int recv_len);
To specify a hook for error data (i.e. from stderr), you must write a C function with the following signature:
typedef void (*vv_fc_err_hook)(char *recv, int recv_len);
"recv" is the data received and "recv_len" is its length. To register these functions with "vv_fc_request()" function, assign their pointers to "out_hook" and "err_hook" members of request variable of type "vv_fc". Note that the output hook (i.e. hook function of type "vv_fc_out_hook") will receive empty string ("") in "recv" and "recv_len" will be 0 when the request has completed, meaning all data (including error) has been received.

For example, functions "get_output()" and "get_err()" will capture data as it arrives and print it out:
// Output hook
void get_output(char *d, int l)
{
    printf("Got output of [%.*s] of length [%d]", l, d, l);
}

// Error hook
void get_err(char *d, int l)
{
    printf("Got error of [%.*s] of length [%d]", l, d, l);
}

...

vv_fc req = {0};
...
// Register output and error hooks
req.out_hook = &get_out;
req.err_hook = &get_err;

Multithreading

The FastCGI client is MT-safe, meaning you can use it both in single-threaded and multi-threaded programs. Note that each thread must have its own copy of "vv_fc" request variable, since it provides both input and output parameters to a request call and as such cannot be shared between the threads.

Examples

Simple example

The following example is a simple demonstration, with minimum of options used. Copy the C code to file "cli.c":
#include "vfcgi.h"

void main ()
{
  // Request type vv_fc - create a request variable and zero it out
  vv_fc req = {0};

  req.fcgi_server = "/var/lib/vv/helloworld/sock/sock"; // Unix socket
  req.req_method = "GET"; // GET HTTP method
  req.app_path = "/helloworld"; // application path
  req.req = "/hello-simple"; // request name

  // Make a request
  int res = vv_fc_request (&req);

  // Check return status, and if there's an error, display error code and the
  // corresponding error message. Otherwise, print out server response.
  if (res != VV_OKAY) printf ("Request failed [%d] [%s]\n", res, req.errm);
  else printf ("%s", req.data);

  // Free up resources so there are no memory leaks
  free (req.data);
  free (req.error);
}
To make this client application:
gcc -o cli cli.c -g $(vv -i)
In this case, you're using a Unix socket to communicate with the FastCGI server. To test with a Vely request handler, copy the following code to "hello_simple.vely" file:
#include "vely.h"
void hello_simple()
{
   silent-header
   out-header default

   @Hi there!
}
Create and make the Vely server application and run it via local Unix socket:
sudo vf -i -u $(whoami) helloworld
vv -q
vf -m quit helloworld
vf -w 1 helloworld
Run the FastCGI client:
./cli
The output is, as expected:
Hi there!

Example with more options

This example demonstrate using multiple options, including using TCP sockets connecting to a host and port number, environment variables, query string, request body and request execution timeout. It will also show the separation of "data" and "error" (i.e. stdout and stderr) streams from the server.

Copy this to file "cli1.c" - note that in this example FastCGI server will run on localhost (127.0.0.1) and TCP port 2301:
#include "vfcgi.h"

void main ()
{
   // Request type vv_fc - create a request variable
   vv_fc req;

   // Add 3 environment variables (in the form of name, value, name, value, ... , NULL)
   char *env[] = { "REMOTE_USER", "John", "SOME_VAR", "SOME\nVALUE", "NEW_VAR", "1000", NULL };

   // Create a request
   // Environment variables to pass to server request
   req.env = env;
   // Server IP and port
   req.fcgi_server = "127.0.0.1:2301";
   // Request method
   req.req_method = "GET";
   // Application path
   req.app_path = "/helloworld";
   // Request
   req.req = "/hello";
   // URL payload (path and query string)
   req.url_payload = ""par1=val1&par2=91"";
   // Content type
   req.content_type = "application/json";
   // Content (i.e. request body)
   req.req_body = "This is request body";
   // Content length
   req.content_len = strlen (req.req_body);
   // No timeout (set to 0)
   req.timeout = 0;

   // Make a request
   int res = vv_fc_request (&req);

   // Check return status, and if there's an error, display error code and the
   // corresponding error message
   if (res != VV_OKAY) printf("Request failed [%d] [%s]\n", res, req.errm);
   else {
       // If successful, display request results
       // Exit code from the server processing
       printf("Server status %d\n", req.req_status);
       // Length of reply from server
       printf("Len of data %d\n", req.data_len);
       // Length of any error from server
       printf("Len of error %d\n", req.error_len);
       // Reply from server
       printf("Data [%s]\n", req.data);
       // Any error message from server
       printf("Error [%s]\n", req.error);
   }

   // Free up resources so there are no memory leaks
  free (req.data);
  free (req.error);
}
To make this client application:
gcc -o cli1 cli1.c -g $(vv -i)
To test it, you can create a Vely server application. Copy this to "hello.vely" file in a separate directory:
#include "vely.h"

void hello()
{
    silent-header
    out-header default

    // Get request body
    request-body rb

    // Get environment variables passed on from the client
    get-sys web-environment "REMOTE_USER" to define ruser
    get-sys web-environment "SOME_VAR" to define somev
    get-sys web-environment "NEW_VAR" to define newv

    // Output, print the environment variables, the PID of server process and the request body received from the client
    @Hello World! [<<p-out ruser>>] [<<p-out somev>>] [<<p-out newv>>] [<<p-out newv>>] [<<p-out par1>>] <<pf-out "%d", getpid()>> <<p-out rb>>

    // Output back a number of lines, generally as "Output line #<num of line>"
    // After line #1418, print out "Line 1419 has an error" to stderr
    // After line #4418, report an error and exit
    // This demostrates outputting data to both stdout and stderr
    num i;
    for (i = 0; i < 8000; i++) {
        @Output line #<<p-num i>>
        if (i == 1419) {
            pf-out to-error "Line %lld has an error\n", i
        }
        if (i == 4419) {
            // Exit code of the server execution
            exit-code 82
            report-error "%s", "Some error!"
        }
    }
}
Create and make the Vely server application and run it on local TCP port 2301 to match the client above:
sudo vf -i -u $(whoami) helloworld
vv -q
vf -m quit helloworld
vf -w 1 -p 2301 helloworld
Run the FastCGI client:
./cli1
The output:
Server status 82
Len of data 78517
Len of error 35
Data [Hello World! [John] [SOME
VALUE] [val1] [91] [1000] 11187 This is request body
Output line #0
Output line #1
Output line #2
Output line #3
Output line #4
Output line #5
Output line #6
Output line #7
...
Output line #4413
Output line #4414
Output line #4415
Output line #4416
Output line #4417
Output line #4418
Output line #4419
]
Error [Line 1419 has an error
Some error!
]
The output shows server exit code (82, see exit-code in the Vely code above), length of output data, the output data which includes environment variables passed to the server from client, the PID of server process, the request body from the client, and then the error output. Note that the data output (stdout) and the error output (stderr) are separated, since FastCGI protocol does use separate streams over the same connection. This makes working with the output easy, while the data transfer is fast at the same time.

See also

Running application ( application_setup   CGI   Client_API   command_line   containerize_application   FastCGI   FastCGI_client   plain_C_FCGI  )  SEE ALL (documentation)


Copyright (c) 2017-2023 DaSoftver LLC. Vely is a trademark of Dasoftver LLC. The software and information herein are provided "AS IS" and without any warranties or guarantees of any kind. Vely elephant logo (c) 2022 DaSoftver LLC. This web page is licensed under CC-BY-SA-4.0. Contact email vely@vely.dev.