19.0.0 released Nov 08, 2023
FastCGI API

You can use Vely C API client library to connect to FastCGI application servers, including Vely:
See Examples section below for detailed examples.
Sending a request to FastCGI server
The following function is used to make a FastCGI call using C API:
int vv_fc_request (vv_fc *req);

All input and output is contained in a single variable of type "vv_fc", the pointer to which is passed to "vv_fc_request()" function that sends a request to the server. A variable of type "vv_fc" must be initialized to zero before using it (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));
...
// Set members of 'req' variable (see below)
...
// Make FastCGI call
int result = vv_fc_request (&req);

Type "vv_fc" is defined as (i.e. public members of it):
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
    int data_len; // length of response from server
    int error_len; // length of error from server
    char *errm; // error message when vv_fc_request returns other than VV_OKAY
    vv_fc_out_hook out_hook; // hook to get data output as soon as it arrives
    vv_fc_err_hook err_hook; // hook get error output as soon as it arrives
    vv_fc_done_hook done_hook; // get all data when request is complete
    int thread_id; // custom ID when executing in a multithreaded fashion
    volatile char done; // indicator that the request has completed
    int return_code; // the return code from vv_fc_request()
} vv_fc;


- Mandatory input
The following members of "vv_fc" type must be supplied in order to make a FastCGI call:
- Request URL payload
"url_payload" is the URL payload, meaning 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.

- Request body (content)
"req_body" is the request body, which can be any text or binary data. "content_type" is the content type of request body (for instance "application/json" or "image/jpg"). "content_len" is the length of request body in bytes. A request body is sent only if "content_type" and "req_body" are not NULL and not empty, and if "content_len" is greater than zero.

- Passing environment to server
"env" is any environment variables that should be passed along to the request executing on a 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
"timeout" is the number of seconds a call to the server 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 to wait for a reply. 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.

Even if timeout is set to 0, a server call may eventually timeout due to underlying socket and network settings. Note that even if your server call times out, the actual request executing on the server may continue until it's done.
- Thread ID
"thread_id" is an integer that you can set and use when your program is multithreaded. By default it's 0. This number is set by you and passed to hooks (your functions called when request is complete or data available). You can use this number to differentiate the data with regards to which thread it belongs to.

- Completion indicator and return code
When your program is multithreaded, it may be useful to know when (and if) a request has completed. "done" is set to to "true" when a request completes, and "return_code" is the return value from vv_fc_request(). In a single-threaded program, this information is self-evident, but if you are running more than one request at the same time (in different threads), you can use these to check on each request executing in parallel (for instance in a loop in the main thread).

Note that "done" is "true" specifically when all the results of a request are available and the request is about to be completed. In a multithreaded program, it means the thread is very soon to terminate or has already terminated; it does not mean that thread has positively terminated. Use standard "pthread_join()" function to make sure the thread has terminated if that is important to you.
Return value
The following are possible return values from "vv_fc_request()" (available in "return_code" member of "vv_fc" type):
You can obtain the error message in "errm" member of "vv_fc" type.
Server reply
The server reply is split in two. One part is the actual result of processing (called "stdout" or standard output), and that is "data". The other is the error messages (called "stderr" or standard error), and that's "error". If the server is Vely, all its output goes to "data", except from report-error and pf-out/pf-url/pf-web (with "to-error" clause) which goes to "error". Note that "data" and "error" streams can be co-mingled when output by the server, but they will be obtained separately. This allows for clean separation of output from any error messages.

You can obtain server reply when it's ready in its entirety (likely most often used), or as it comes alone bit by bit (see more about asynchronous hooks futher here).
Status of request execution
"req_status" member of "vv_fc" type is the request status when a request had executed; it is somewhat similar to an exit status of a program. This is a part of FastCGI specification, and the particular server software you are using may or may not return this status. A Vely server request returns status by means of exit-code statement. Note that "req_status" is valid only if "vv_fc_request()" returned VV_OKAY (or if "return_code" is VV_OKAY for multi-threaded programs).
Getting data reply (stdout)
Data returned from a request is valid only if "vv_fc_request()" returned VV_OKAY (or if "return_code" is VV_OKAY for multi-threaded programs). In that case, use "vv_fc_data()" function, for example:
// Declare and initialize request variable
vv_fc req = {0};
// Setup the req variable
...
// Execute request
if (vv_fc_request (&req) == VV_OKAY) {
    char *data = vv_fc_data (req); // data response
    int data_len = req->data_len; // length of data response in bytes
}

"data_len" member of "vv_fc" type will have the length of data response in bytes. The reply is always null-terminated as a courtesy, and "data_len" does not include the terminating null byte.

"vv_fc_data()" returns the actual response (i.e. data output) from server as passed to "data" stream. If you are using Vely server, any output will go there, 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 output.
Getting error reply (stderr)
An error reply returned from a request is valid only if "vv_fc_request()" returned VV_OKAY (or if "return_code" is VV_OKAY for multi-threaded programs). In that case, use "vv_fc_error()" function, for example:
// Declare and initialize request variable
vv_fc req = {0};
// Setup the req variable
...
// Execute request
if (vv_fc_request (&req) == VV_OKAY) {
    char *err = vv_fc_error (req); // error response
    int err_len = req->error_len; // length of error response in bytes
}

"vv_fc_error()" returns any error messages from a server response, i.e. data passed to "error" stream. For a Vely server, 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" member (of "vv_fc" type above) will have the length of error response in bytes. The response is always null-terminated as a courtesy, and "error_len" does not include the terminating null byte.
Freeing the result of a request
Once you have obtained the result of a request, and when no longer needed, you should free it by using "vv_fc_delete()":
// Declare and initialize request variable
vv_fc req = {0};
// Setup the req variable
...
// Execute request
vv_fc_request (&req);
// .. Use the result ..
// Free request output (data and error streams)
vv_fc_delete (&req);

If you do not free the result, 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.

You can use "vv_fc_delete()" regardless of whether "vv_fc_request()" returned VV_OKAY or not.
Completion hook
A function you wrote can be called when a request has completed. This is useful in multithreaded invocations, where you may want to receive complete request's results as they are available. To specify a completion hook, you must write a C function with the following signature and assign it to "done_hook" member of "vv_fc" typed variable:
typedef void (*vv_fc_done_hook)(char *recv, int recv_len, char *err, int err_len, vv_fc *req);

"recv" is the request's data output, "recv_len" is its length in bytes, "err" is the request's error output, and "err_len" is its length in bytes. "req" is the request itself which you can use to obtain any other information about the request. In a single threaded environment, these are available as members of the request variable of "vv_fc" type used in the request, and there is not much use for a completion hook.

See an example with asynchronous hooks.
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 data output (i.e. from stdout), you must write a C function with the following signature and assign it to "out_hook":
typedef void (*vv_fc_out_hook)(char *recv, int recv_len, vv_fc *req);

"recv" is the data received and "recv_len" is its length.

To specify a hook for error output (i.e. from stderr), you must write a C function with the following signature and assign it to "err_hook":
typedef void (*vv_fc_err_hook)(char *err, int err_len, vv_fc *req);

"err" is the error received and "err_len" is its length.

"req" (in both hooks) is the request itself which you can use to obtain any other information about the request.

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" respectively. 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 server output (including error) has been received.

For example, functions "get_output()" and "get_err()" will capture data as it arrives and print it out, and get_complete() will print the final result:
// Output hook
void get_output(char *d, int l, vv_fc *req)
{
    printf("Got output of [%.*s] of length [%d] in thread [%d]", l, d, l, req->thread_id);
}

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

// Completion hook
void get_complete(char *data, int data_len, char *err, int err_len, vv_fc *req)
{
    printf("Got data [%.*s] of length [%d] and error of [%.*s] of length [%d], status [%d], thread [%d]\n", data_len, data, data_len, err_len, err, err_len, req->req_status, req->thread_id);
}

...

vv_fc req = {0};
...
// Register output and error hooks, as well as a completion hook
req.out_hook = &get_output;
req.err_hook = &get_err;
req.done_hook = &get_complete;

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.
Usage
Do not use this API directly with Vely - use call-server instead which is made specifically for use in .vely files. Otherwise, you can use this API with any program.
Using API without Vely
You can use FastCGI API without installing Vely. To do that:
Note that you do not need to install any other dependencies, as FastCGI API is entirely contained in the aforementioned source files.
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", vv_fc_data(&req));

    // Free up resources so there are no memory leaks
    vv_fc_delete(&req);
}

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 demonstrates 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;
    // Initialize request
    memset (&req, 0, sizeof(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", vv_fc_data(&req));
       // Any error message from server
       printf("Error [%s]\n", vv_fc_error(&req));
    }

    // Free up resources so there are no memory leaks
    vv_fc_delete(&req);
}

Note that the URL payload (i.e. "req.url_payload") could have been written as a combination of a path segment and query string (see request-URL):
req.url_payload = "/par1/val1?par2=91";

or just as a path segment:
req.url_payload = "/par1/val1/par2/91";

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

    // Input params
    input-param par1
    input-param par2

    // 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 par1>>] [<<p-out par2>>] <<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 78530
Len of error 35
Data [Hello World! [John] [SOME
VALUE] [1000] [val1] [91] 263002 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 data output, and other information 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
Client
FastCGI-API  
FastCGI-command-line-client    
See all
documentation


You are free to copy, redistribute and adapt this web page (even commercially), as long as you give credit and provide a dofollow link back to this page - see full license at CC-BY-4.0. Copyright (c) 2019-2023 Dasoftver LLC. Vely and elephant logo are trademarks of Dasoftver LLC. The software and information on this web site are provided "AS IS" and without any warranties or guarantees of any kind. Icons from table-icons.io copyright PaweĊ‚ Kuna, licensed under MIT license.