GOLF(2gg)
πΈ NAME πΈ
Client-API β Golf documentation (API). Yeah bestie, this is the GOLF(2gg) page from the Development section, and we're about to make you a whole entire C client. Sit down, hydrate, let's go. π
β¨ DESCRIPTION β¨
Okay so listen β you can absolutely use the C API client library to connect to Golf, and it's giving effortless:
- The API has only a few functions, and the main one is
gg_cli_request(), which makes a call to the service. π - There is only a single data type used, and it's
gg_cliβ you use it to specify a request and its options, and to retrieve results. One struct, doing the most. - There is a single include file:
gcli.h. π - When building your client executable, you can either specify build flags by using the result of
gg -i(if you have Golf installed), or use self-contained source files directly. Your call, babe. - It is MT-safe, so you can use it in multi-threaded applications, like making a bunch of requests in parallel. π₯
See the Examples section below for detailed examples.
π«Ά SENDING A REQUEST TO GOLF SERVICE
The following function is what you use to make a call with the C API:
int gg_cli_request (gg_cli *req);
All input and output is contained in a single variable of type gg_cli, and you pass a pointer to it into the gg_cli_request() function, which sends a request to the service. Real talk though: a variable of type gg_cli must be initialized to zero before using it (like with {0} initialization, memset() or calloc()), or otherwise some of its members may have random values. Not the vibe. π
// Define and initialize request variable
gg_cli req = {0};
// You could also do:
// memset ((char*)&req, 0, sizeof(gg_cli));
...
// Set members of 'req' variable (see below)
...
// Make a call
int result = gg_cli_request (&req);
Type gg_cli is defined as (i.e. public members of it):
typedef struct {
const char *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_params; // URL params (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 to servicde
int timeout; // timeout for request
int req_status; // status of request from service
int data_len; // length of response from service
int error_len; // length of error from service
char *errm; // error message when gg_cli_request returns other than GG_OKAY
gg_cli_out_hook out_hook; // hook to get data output as soon as it arrives
gg_cli_err_hook err_hook; // hook get error output as soon as it arrives
gg_cli_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 gg_cli_request()
} gg_cli;
π Mandatory input
The following members of the gg_cli type must be supplied in order to make a call to a service:
serverrepresents either a Unix socket or a TCP socket, and is:- for a Unix socket, a fully qualified name to a Unix socket file used to communicate with the service (for a Golf service, it's
/var/lib/gg/<app name>/sock/sock, where<app name>is the application name), or - for a TCP socket, a host name and port name in the form of
<host name>:<port number>, specifying where is the server listening on (for instance127.0.0.1:2301if the Golf service is local and runs on TCP port 2301).
- for a Unix socket, a fully qualified name to a Unix socket file used to communicate with the service (for a Golf service, it's
req_methodis a request method, such asGET,POST,PUT,DELETEor any other. πapp_pathis an application path (see request). By default it's/<application name>.reqis a request path, i.e. a request name preceded by a forward slash, as in/<request name>(see request).
π URL parameters
url_params is the URL parameters, meaning input parameters (as path segments and query string, see request). URL parameters can be NULL or empty, in which case it is not used. So like, optional bestie, no pressure.
π¦ 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. Heads up: 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. All three, or nothing. β¨
π± Passing environment to service
env is any environment variables that should be passed along to the service. You can access those in Golf via the environment clause of the 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 service, you must size env as char* array with 2*N+1 elements. Do the math, don't skip it. π
Note that in order to suppress output of HTTP headers from the service, you can include environment variable GG_SILENT_HEADER with value yes; to let the service control headers output (either by default, with -z option of mgrg or with silent-header) simply omit this environment variable.
β³ Timeout
timeout is the number of seconds a call to the service should not exceed. For instance if the remote service 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 service call may eventually timeout due to underlying socket and network settings. Note that even if your service call times out, the actual service executing may continue until it's done. It's giving "I'm leaving but the party continues." π
π§΅ 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 true when a request completes, and return_code is the return value from gg_cli_request() (see below for a list). 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 OF GG_CLI_REQUEST() π₯
The following are possible return values from gg_cli_request() (available in the return_code member of gg_cli type):
GG_OKAYif request succeeded. πGG_CLI_ERR_RESOLVE_ADDRif host name for TCP connection cannot be resolved.GG_CLI_ERR_PATH_TOO_LONGif path name of Unix socket is too long.GG_CLI_ERR_SOCKETif cannot create a socket (for instance they are exhausted for the process or system).GG_CLI_ERR_CONNECTif cannot connect to server (TCP or Unix alike).GG_CLI_ERR_SOCK_WRITEif cannot write data to server (for instance if server has encountered an error or is down, or if network connection is no longer available).GG_CLI_ERR_SOCK_READif cannot read data from server (for instance if server has encountered an error or is down, or if network connection is no longer available).GG_CLI_ERR_PROT_ERRif there is a protocol error, which indicates a protocol issue on either or both sides.GG_CLI_ERR_BAD_VERif either side does not support protocol used by the other.GG_CLI_ERR_SRVif server cannot complete the request.GG_CLI_ERR_UNKif server does not recognize record types used by the client.GG_CLI_ERR_OUT_MEMif client is out of memory.GG_CLI_ERR_ENV_TOO_LONGif the combined length of all environment variables is too long.GG_CLI_ERR_ENV_ODDif the number of supplied environment name/value pairs is incorrect.GG_CLI_ERR_BAD_TIMEOUTif the value for timeout is incorrect.GG_CLI_ERR_TIMEOUTif the request timed out based ontimeoutparameter or otherwise if the underlying Operating System libraries declared their own timeout.
You can obtain the error message (corresponding to the above return values) in the errm member of gg_cli type. π
π¬ SERVER REPLY π¬
The service 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. All of service output goes to data, except from report-error and print-format (with to-error clause) which goes to error. Note that data and error streams can be co-mingled when output by the service, but they will be obtained separately. This allows for clean separation of output from any error messages. Iconic, honestly. π
You can obtain service 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 further here).
π STATUS OF REQUEST EXECUTION π
req_status member of gg_cli type is the request status when a request had executed; it is somewhat similar to an exit status of a program. A Golf service request returns status by means of exit-status statement. Note that req_status is valid only if gg_cli_request() returned GG_OKAY (or if return_code is GG_OKAY for multi-threaded programs). Don't read it otherwise, fr fr.
π₯ GETTING DATA REPLY (STDOUT) π₯
Okay so bestie, the data you get back from a request? It's only valid if gg_cli_request() returned GG_OKAY (or if return_code is GG_OKAY for multi-threaded programs). If that vibe check passed, you use the gg_cli_data() function, like this:
// Declare and initialize request variable
gg_cli req = {0};
// Setup the req variable
...
// Execute request
if (gg_cli_request (&req) == GG_OKAY) {
char *data = gg_cli_data (req); // data response
int data_len = req->data_len; // length of data response in bytes
}
The data_len member of gg_cli 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. periodt π
gg_cli_data() returns the actual response (i.e. data output) from service as passed to data stream. Any output from service will go there, except when to-error clause is used in print-format - use these constructs to output errors without stopping the service execution. Additionally, the output of report-error will also not go to data output. iykyk β¨
β¨ GETTING ERROR REPLY (STDERR) β¨
An error reply returned from a service is valid only if gg_cli_request() returned GG_OKAY (or if return_code is GG_OKAY for multi-threaded programs). In that case, use gg_cli_error() function, for example:
// Declare and initialize request variable
gg_cli req = {0};
// Setup the req variable
...
// Execute request
if (gg_cli_request (&req) == GG_OKAY) {
char *err = gg_cli_error (req); // error response
int err_len = req->error_len; // length of error response in bytes
}
gg_cli_error() returns any error messages from a service response, i.e. data passed to error stream. It is comprised of any service output when to-error clause is used in print-format, as well as any output from report-error.
error_len member (of gg_cli 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. no cap π¦
π 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 gg_cli_delete():
// Declare and initialize request variable
gg_cli req = {0};
// Setup the req variable
...
// Execute request
gg_cli_request (&req);
// .. Use the result ..
// Free request output (data and error streams)
gg_cli_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. bestie, don't leak memory, it's not cute π
You can use gg_cli_delete() regardless of whether gg_cli_request() returned GG_OKAY or not. slay π
π 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 gg_cli typed variable:
typedef void (*gg_cli_done_hook)(char *recv, int recv_len, char *err, int err_len, gg_cli *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 gg_cli type used in the request, and there is not much use for a completion hook. lowkey π
See an example with asynchronous hooks. obsessed β¨
π¦ ASYNCHRONOUS HOOKS π¦
You can obtain the service's reply as it arrives by specifying read hooks. This is useful if the service supplies partial replies over a period of time, and your application can get those partial replies as they become available. main character energy π₯
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 (*gg_cli_out_hook)(char *recv, int recv_len, gg_cli *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 (*gg_cli_err_hook)(char *err, int err_len, gg_cli *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 gg_cli_request() function, assign their pointers to out_hook and err_hook members of request variable of type gg_cli respectively. Note that the output hook (i.e. hook function of type gg_cli_out_hook) will receive empty string ("") in recv and recv_len will be 0 when the request has completed, meaning all service output (including error) has been received. that's the vibe check π
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, gg_cli *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, gg_cli *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, gg_cli *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);
}
...
gg_cli 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 Golf 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 gg_cli request variable, since it provides both input and output parameters to a request call and as such cannot be shared between the threads. no sharing bestie, it's giving personal space π
π«Ά USAGE π«Ά
Do not use this API directly with Golf - use call-remote instead which is made specifically for use in .golf files. Otherwise, you can use this API with any program. flexible icon β¨
π USING API WITHOUT GOLF π
You can use API without installing Golf. To do that:
- get the Golf source code,
- copy source files
gcli.candgcli.hto where you need to build your program, - add the content of Golf's
LICENSEfile to your own in order to include the license for these source files, - then make your application:
gcc -o cli cli.c gcli.c
Note that you do not need to install any other dependencies, as API is entirely contained in the aforementioned source files. ate π
β¨ EXAMPLES β¨
okay bestie, this is the part where we actually do the thing π two whole worked examples, from "baby's first request" all the way up to "we are passing env vars, a body, a timeout and watching stdout and stderr split like a glow-up". let's go π«Ά
πΈ SIMPLE EXAMPLE πΈ
This one's the low-key demo, using the minimum of options possible. Copy the C code into a file called cli.c in a directory all of its own π
#include "gcli.h"
void main ()
{
// Request type gg_cli - create a request variable and zero it out
gg_cli req = {0};
req.server = "/var/lib/gg/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 = gg_cli_request (&req);
// Check return status, and if there's an error, display error code and the
// corresponding error message. Otherwise, print out service response.
if (res != GG_OKAY) printf("Request failed [%d] [%s]\n", res, req.errm);
else printf("%s", gg_cli_data(&req));
// Free up resources so there are no memory leaks
gg_cli_delete(&req);
}
Then to make this client application, you literally just:
gcc -o cli cli.c $(gg -i)
In this case you're using a Unix socket to talk to the Golf service. Wanna test it against a Golf service handler? Copy the following code into a file called hello_simple.golf, in a separate directory:
begin-handler /hello_simple public
silent-header
@Hi there!
end-handler
Now create and make the Golf application, and run it via local Unix socket:
sudo mgrg -i -u $(whoami) helloworld
gg -q
mgrg -m quit helloworld
mgrg -w 1 helloworld
Run the client:
./cli
And the output is, as expected, iconic:
Hi there!
π EXAMPLE WITH MORE OPTIONS π
This one is the main character β¨ it shows off multiple options at once β TCP sockets connecting to a host and port number, environment variables, query string, request body, and request execution timeout. It also shows the separation of the data and error streams (that's stdout and stderr, bestie) coming back from the service π
Copy this into a file called cli1.c in a directory of its own β and note that in this example a server will run on localhost (127.0.0.1) and TCP port 2301:
#include "gcli.h"
void main ()
{
// Request type gg_cli - create a request variable
gg_cli 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 service request
req.env = env;
// Server IP and port
req.server = "127.0.0.1:2301";
// Request method
req.req_method = "GET";
// Application path
req.app_path = "/helloworld";
// Request
req.req = "/hello";
// URL parameters (path and query string)
req.url_params = "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 = gg_cli_request (&req);
// Check return status, and if there's an error, display error code and the
// corresponding error message
if (res != GG_OKAY) printf("Request failed [%d] [%s]\n", res, req.errm);
else {
// If successful, display request results
// Exit code from the service processing
printf("Server status %d\n", req.req_status);
// Length of reply from service
printf("Len of data %d\n", req.data_len);
// Length of any error from service
printf("Len of error %d\n", req.error_len);
// Reply from service
printf("Data [%s]\n", gg_cli_data(&req));
// Any error message from service
printf("Error [%s]\n", gg_cli_error(&req));
}
// Free up resources so there are no memory leaks
gg_cli_delete(&req);
}
ngl, that URL parameter line has options. Note that the URL parameters (i.e. req.url_params) could have been written as a combination of a path segment and query string (see request):
req.url_params = "/par1/val1?par2=91";
or just as a path segment:
req.url_params = "/par1=val1/par2=91";
To make this client application:
gcc -o cli1 cli1.c $(gg -i)
To test it, you can create a Golf application. Copy this into a hello.golf file in a separate directory:
begin-handler /hello public
silent-header
// Get request body
request-body rb
// Input params
get-param par1
get-param par2
// Get environment variables passed on from the client
get-sys environment "REMOTE_USER" to ruser
get-sys environment "SOME_VAR" to somev
get-sys environment "NEW_VAR" to newv
// Output, print the environment variables, the PID of server process and the request body received from the client
get-req process-id to pid
@Hello World! [<<print-out ruser>>] [<<print-out somev>>] [<<print-out newv>>] [<<print-out par1>>] [<<print-out par2>>] <<print-out pid>> <<print-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 demonstrates outputting data to both stdout and stderr
start-loop repeat 8000 use i start-with 0
@Output line #<<print-out i>>
if-true i equal 1419
print-format "Line %ld has an error\n", i to-error
end-if
if-true i equal 4419
// Exit code of the service execution
exit-status 82
report-error "%s", "Some error!"
end-if
end-loop
end-handler
Create and make the Golf application, and run it on local TCP port 2301 so it matches the client above:
sudo mgrg -i -u $(whoami) helloworld
gg -q
mgrg -m quit helloworld
mgrg -w 1 -p 2301 helloworld
Run the 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!
]
So what's actually happening here? The output shows the service exit code (82, see exit-status in the Golf code above), the length of the data output, and a bunch of other info β including the environment variables passed to the service from the client, the PID of the server process, the request body from the client, and then the error output. And note the important bit π the data output (stdout) and the error output (stderr) are separated, since the protocol does use separate streams over the same connection. Which makes working with the output easy, while the data transfer stays fast at the same time. ate and left no crumbs fr fr π
π¦ SEE ALSO π¦
- API
Client-API Server-API See all documentation
that's the whole page bestie, you made it to the end π this lil' beauty is $VERSION and it's dated $DATE β go forth and GOLF(2gg) responsibly πβ¨
π¦ where this page comes from
- ubuntu questing: golf 601.4.41-1
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms