18.4.0 released Sep 25, 2023
How to connect to Vely with API: multi-threaded and single-threaded calls



Vely

Vely and FastCGI
The way Vely receives client requests is via FastCGI, which is a high-performance binary protocol for communication between servers, clients, and in general programs of all kinds. Originally it was created to rectify shortcomings of CGI (Common Gateway Interface) protocol, in order to dramatically increase its performance. Over time, FastCGI emerged as a great protocol for high performance server applications.

The reason for this is that it allows client requests to be handled without starting up and shutting down server threads or processes; the inter-process communication is especially fast with Unix sockets. In addition, the protocol is binary and very lean, meaning it provides high performance. Typically it's used on secure networks as it doesn't have any security built-in (which is one of the reasons for high performance), such as behind front-facing web server(s), on secure intranets etc.

Here you will learn how to connect to any FastCGI server (including Vely and PHP FPM) from virtually any programming language (that has C linkage, which is most of them), using FastCGI-API; that's the reason examples are written in C.
Prerequisites
This example uses API that comes with Vely framework (at least 17.1.3 or later should be installed to run these examples). The examples are for Ubuntu 22, so you can install Vely with apt packager or from source. You can also install it for other Linux distros.

Alternatively, if you are using API to connect to a non-Vely server (i.e. PHP or some other), you can do so without installing Vely - see "Using API without Vely" in FastCGI-API; in this case you cannot run the Vely server example below. Note that in this case you also must install gcc beforehand.
What will you do here
You will connect to a Vely server (and to PHP FPM too) using API, and run code on those servers on behalf of a client, meaning send data and receive a reply. Both single- and multi-threaded examples are included.
Setup a Vely server
In order to run a client example with a Vely server, you need to setup and run a server first. First, create Vely application named "example" in a new directory:
mkdir client
cd client
sudo vf -i -u $(whoami) example

To see highlighting for Vely code in vim, run this just one time:
vv -m

Then create source Vely file:
vi echo.vely

and copy this:
#include "vely.h"

void echo() {
    out-header default
    input-param par
    @Input is <<p-out par>>
}

Build the application:
vv -q

And start a server, in this case with 5 worker processes:
vf -w 5 example

Once you've done this, you can proceed to build a client and call the server.
Simple example with Vely
Create a file:
vi api.c

and copy this:
#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/example/sock/sock"; // Unix socket
    req.req_method = "GET"; // GET HTTP method
    req.app_path = "/example"; // application path
    req.req = "/echo"; // request name
    req.url_payload = "par=99";

    // 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, vv_fc_error(&req));
    else printf ("%s", vv_fc_data(&req));

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

The example is very simple and fairly self-explanatory. A few things you must always have (see FastCGI-API):
Also provided is "url_payload", which in terms of HTTP environment variables is really the rest of PATH_INFO (divided by forward slashes "/", or up to "?" if there's one), plus QUERY_STRING (in the form of "name=value" pairs, or after "?", if there's one). Finally "vv_fc_request()" calls the server and you can use "vv_fc_error()" to get any error response and "vv_fc_data()" to get the data reply. Note that error and data may be interwoven; not to worry, the API will separate the two as proper streams. To delete memory used by this call, use "vv_fc_delete()". That's all for a simple example!

To build the client, execute:
gcc -o api api.c $(vv -i)

Run it:
./api

The result is:
Content-type: text/html;charset=utf-8
Cache-Control: max-age=0, no-cache
Pragma: no-cache
Status: 200 OK

Input is 99

This is the expected result.
Multi-threaded example with Vely
The next example is a MT (multi-threaded) client. You will make 100 simultaneous calls to a Vely server. The code that does this is very similar to the simple example, with one addition: you'll pass along an extra environment variable, in this case "VV_SILENT_HEADER" with value "yes", which will suppress HTTP header output from the server. Otherwise, the "url_payload" is dynamically constructed, so that you can display input "0" through "99".

In the "main()" function, you will create 100 threads and call "call_server()" function that many times in parallel, then wait for all of them to finish. The result of each thread (i.e. if a call to the server was successful) is passed to "main()" as a return value of "call_server()".

Create client C file:
vi api_mt.c

Copy the following:
#include "pthread.h"
#include "assert.h"
#include "vfcgi.h"
#define REQ_LEN 200
#define MT_RUNS 100

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

    req.fcgi_server = "/var/lib/vv/example/sock/sock"; // Unix socket
    req.req_method = "GET"; // GET HTTP method
    req.app_path = "/example"; // application path
    req.req = "/echo"; // request name
    char *env[3];
    env[0]="VV_SILENT_HEADER";
    env[1]="yes";
    env[2]=NULL;
    req.env = env;

    req.url_payload = (char*)malloc (REQ_LEN); assert(req.url_payload);
    snprintf (req.url_payload, REQ_LEN, "par=%ld", (off_t)inp);

    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) {
        fprintf (stderr, "Request failed [%d] [%s]\n", res, vv_fc_error(&req));
    }
    else {
        printf ( "%s", vv_fc_data(&req));
    }

    // Free up resources so there are no memory leaks
    vv_fc_delete(&req);
    return (void*)(off_t)res;
}

int main ()
{
    // Make a request
    pthread_t thread_id[MT_RUNS+1];
    int i;
    for (i = 0; i < MT_RUNS; i++){
        pthread_create(&(thread_id[i]), NULL, call_server, (void*)(off_t)i);
    }
    int bad = 0;
    void *thread_res[MT_RUNS+1];
    for (i = 0; i < MT_RUNS; i++){
        pthread_join(thread_id[i], &(thread_res[i]));
        int r = (int)(off_t)(thread_res[i]);
        if (r != VV_OKAY) bad++;
    }
    if (bad!=0) {
        fprintf (stderr, "Total [%d] bad\n", bad);
        return -1;
    } else {
        printf ("All okay return value\n");
        return 0;
    }
}

To build the client, execute:
gcc -o api_mt api_mt.c $(vv -i) -lpthread

Run it:
./api_mt

The result is, as expected (the input numbers are somewhat randomly dispersed since all clients work truly in parallel):
Input is 0
Input is 3
Input is 4
Input is 6
Input is 5
Input is 2
Input is 1
Input is 7
Input is 8
...
Input is 71
Input is 74
Input is 75
Input is 78
Input is 77
Input is 70
Input is 65
Input is 76
Input is 67
All okay return value

Setup PHP FPM server
The PHP example is for Ubuntu with Apache, however you can adapt it to your particular distribution. The PHP FPM version used is 8.1; if you're using a different version, replace "8.1" with your own.

First, if you don't already have Apache web server server installed:
sudo apt install apache2

Then, if you don't already have PHP FPM server installed:
sudo apt install php-fpm

In order for your client program to be able to write PHP FPM's Unix socket, you can change the configuration for it - in this case you'd make the group be the same as yours, thus giving you permission to write to the socket. To do this, edit file:
sudo vi /etc/php/8.1/fpm/pool.d/www.conf

and change line with "listen.group" to your group name (which is normally the same as your OS user name):
listen.group = <your login user>

Then restart PHP FPM service:
sudo service php8.1-fpm restart

First create a simple PHP program to just output what the input parameter was. Create file:
sudo vi /var/www/html/example.php

and copy this:
<?php
echo "Input is " . $_GET['par'] . "\n";
?>

Make sure PHP file is accessible to PHP FPM (which runs in the same ownership context as the web server, meaning "www-data" user):
sudo chown www-data:root /var/www/html/example.php

Simple example with PHP FPM
The C program to connect and call the above PHP file is simple. Note that the version of PHP used is "8.1", so if you're using a different one, replace "php8.1" with your own. You're also using a Unix socket to connect, the request method is "GET", and the rest is setup for a very simple invocation of a PHP script. "url_payload" is in the form of a query string, and PHP also expects SCRIPT_FILENAME to be set to where you saved the "example.php" file; this demonstrates usage of environment variables. Overall, you can take this simple example and adjust it to serve your needs.

Create client C file:
vi php_api.c

and copy this:
#include "vfcgi.h"

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

    req.fcgi_server = "/run/php/php8.1-fpm.sock"; // Unix socket
    req.req_method = "GET"; // GET HTTP method
    req.app_path = "/"; // application path
    req.req = "/example.php"; // request name
    req.url_payload = "par=99";

    char *env[3];
    env[0]="SCRIPT_FILENAME";
    env[1]="/var/www/html/example.php";
    env[2]=NULL;
    req.env = env;

    // 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, vv_fc_error(&req));
    else printf ("%s", vv_fc_data(&req));

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

The example is very similar to Vely one - see the discussion there. To build the client, execute:
gcc -o php_api php_api.c $(vv -i)

Run it:
./php_api

The result is, as expected:
Content-type: text/html; charset=UTF-8

Input is 99

Multithreaded example with PHP FPM
The next example is a MT (multi-threaded) client connecting to PHP FPM. You will make 100 simultaneous calls to PHP FPM server. The code that does this is very similar to the simple example, with the exception that the "url_payload" is dynamically constructed, so that you can display input from "0" through "99".

In the "main()" function, you will create 100 threads and call "call_server()" function that many times in parallel, then wait for all of them to finish. The result of each thread (i.e. if a call to the server was successful) is passed to "main()" as a return value of "call_server()".

Create client C file:
vi php_api_mt.c

Copy the following:
#include "pthread.h"
#include "assert.h"
#include "vfcgi.h"
#define REQ_LEN 200
#define MT_RUNS 100

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

    req.fcgi_server = "/run/php/php8.1-fpm.sock"; // Unix socket
    req.req_method = "GET"; // GET HTTP method
    req.app_path = "/"; // application path
    req.req = "/example.php"; // request name

    char *env[3];
    env[0]="SCRIPT_FILENAME";
    env[1]="/var/www/html/example.php";
    env[2]=NULL;
    req.env = env;

    req.url_payload = (char*)malloc (REQ_LEN); assert(req.url_payload);
    snprintf (req.url_payload, REQ_LEN, "par=%ld", (off_t)inp);

    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) {
        fprintf (stderr, "Request failed [%d] [%s]\n", res, vv_fc_error(&req));
    }
    else {
        printf ( "%s", vv_fc_data(&req));
    }

    // Free up resources so there are no memory leaks
    vv_fc_delete(&req);
    return (void*)(off_t)res;
}

int main ()
{
    // Make a request
    pthread_t thread_id[MT_RUNS+1];
    int i;
    for (i = 0; i < MT_RUNS; i++){
        pthread_create(&(thread_id[i]), NULL, call_server, (void*)(off_t)i);
    }
    int bad = 0;
    void *thread_res[MT_RUNS+1];
    for (i = 0; i < MT_RUNS; i++){
        pthread_join(thread_id[i], &(thread_res[i]));
        int r = (int)(off_t)(thread_res[i]);
        if (r != VV_OKAY) bad++;
    }
    if (bad!=0) {
        fprintf (stderr, "Total [%d] bad\n", bad);
        return -1;
    } else {
        printf ("All okay return value\n");
        return 0;
    }
}

To build the client, execute:
gcc -o php_api_mt php_api_mt.c $(vv -i) -lpthread

Run it:
./php_api_mt

The result is, as expected (note that since clients work truly in parallel, the numbers are somewhat randomly dispersed):
Content-type: text/html; charset=UTF-8

Input is 0
Content-type: text/html; charset=UTF-8

Input is 3
Content-type: text/html; charset=UTF-8

Input is 5
Content-type: text/html; charset=UTF-8

Input is 4
Content-type: text/html; charset=UTF-8

Input is 1
Content-type: text/html; charset=UTF-8

Input is 2
Content-type: text/html; charset=UTF-8

Input is 6
Content-type: text/html; charset=UTF-8

...

Input is 93
Content-type: text/html; charset=UTF-8

Input is 80
Content-type: text/html; charset=UTF-8

Input is 95
Content-type: text/html; charset=UTF-8

Input is 97
Content-type: text/html; charset=UTF-8

Input is 99
Content-type: text/html; charset=UTF-8

Input is 98
Content-type: text/html; charset=UTF-8

Input is 96
Content-type: text/html; charset=UTF-8

Input is 94
All okay return value

Conclusion
You have learned how to connect to any FastCGI server (in this case Vely and PHP FPM) from a program with C linkage (which can be used with any programming language that has it), using FastCGI-API. You have also learned how to make many parallel API calls using Linux threads. You can use this knowledge to call server code from any other application and receive results.
See also
Examples
example-client-API  
example-cookies  
example-create-table  
example-distributed-servers  
example-docker  
example-encryption  
example-file-manager  
example-form  
example-hash-server  
example-hello-world  
example-how-to-design-application  
example-json  
example-multitenant-SaaS  
example-postgres-transactions  
examples  
example-sendmail  
example-shopping  
example-stock  
example-uploading-files  
example-using-mariadb-mysql  
example-utility  
example-write-report    
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 link back to this page (dofollow) - 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.