Vely Single-Page Documentation 16.10.0
This page contains all of Vely documentation topics combined into one. It may be easier to search.
123_hello_world
after_request_handler
application_setup
before_request_handler
begin-transaction
building_URL
call-web
CGI
Client_API
close-file
command_line
commit-transaction
connect_apache_tcp_socket
connect_apache_unix_socket
connect_nginx_tcp_socket
connect_nginx_unix_socket
containerize_application
copy-file
copy-string
count-substring
current-row
database_config_file
database_queries
debugging
decode-base64
decode-hex
decode-url
decode-web
decrypt-data
delete-cookie
delete-file
delete-json
delete-mem
delete-query
deploying_application
derive-key
documentation
dot
encode-base64
encode-hex
encode-url
encode-web
encrypt-data
error_code
error_handling
example_cookies
example_create_table
example_docker
example_file_manager
example_form
example_hash
example_hello_world
example_json
example_multitenant_SaaS
example_sendmail
example_shopping
examples
example_stock
example_utility
example_write_report
exec-program
exit-code
exit-request
FastCGI_client
FastCGI
file-position
file_storage
file_uploading
finish-output
flush-output
get-app
get-cookie
get-hash
get-req
get-sys
get-time
getting_URL
global_process_data
global_request_data
hash-string
how_vely_works
inline_code
input-param
json-utf8
license
lock-file
lower-string
manage-memory
match-regex
memory_handling
new-fifo
new-hash
new-json
new-mem
non_request
normalized_URL
on-error
open-file
out-header
output_statement
p-dbl
pf-out
pf-url
pf-web
plain_C_FCGI
p-num
p-out
p-path
prepared_statements
purge-fifo
purge-hash
p-url
p-web
quality_control
query-result
random-crypto
random-string
read-fifo
read-file
read-hash
read-json
read-line
rename-file
rename_files
report-error
request-body
request
request_URL
resize-hash
resize-mem
rewind-fifo
rollback-transaction
run-query
SELinux
send-file
set-app
set-cookie
set-input
set-req
silent-header
split-string
startup_handler
statement_APIs
stat-file
syntax_highlighting
temporary_file
trace-run
trim-string
uniq-file
unlock-file
unused-var
upper-string
utf8-json
vely_architecture
vely_dispatch_request
vely_removal
vf
vv
why_C_and_Vely
write-fifo
write-file
write-hash
write-string
123 hello world
This is a condensed version of Vely Hello World that you can run in minutes. No additional software needs to be installed. For more, see
examples.
Step 1: Install Vely
First
install Vely.
Step 2: Build it
Create Hello World source file (hello.vely); note it's all one bash command:
echo '#include "vely.h"
void hello()
{
out-header default
@Hello World!
}' > hello.vely
Create Hello World application:
sudo vf -i -u $(whoami) helloworld
Make Hello World application:
Step 3: Run it
You can run Hello World both as a service and from command line:
- As a service, first start your Hello World FastCGI application server:
then connect to it:
export REQUEST_METHOD=GET
export SCRIPT_NAME="/helloworld"
export PATH_INFO="/hello"
cgi-fcgi -connect /var/lib/vv/helloworld/sock/sock /
- From command line:
export REQUEST_METHOD=GET
export SCRIPT_NAME="/helloworld"
export PATH_INFO="/hello"
/var/lib/vv/bld/helloworld/helloworld
Expected result
The end result looks like this (notice in bold the responses from your web application and the command line):
You have run Hello World with Vely, first by talking directly to a
FastCGI server and then from command line. See
FastCGI_client for a similar example that uses TCP sockets.
See also
Quick start (
123_hello_world )
SEE ALL (
documentation)
Application setup
Initialize application
A Vely application must be initialized first. This means creating a directory structure owned by application owner, which can be any Operating System user. To initialize application <app name> while logged-in as application owner:
sudo vf -i -u $(whoami) <app name>
Setup database(s)
If your application does not use database(s), you can skip this part.
You can setup your database(s) in any way you see fit, and this includes creating the database objects (such as tables or indexes) used by your application; all Vely needs to know is the connection parameters, which include database login information (but can include other things as well). For each database in use, you must provide
database_config_file along with your source code. This file contains the database connection parameters - these parameters are database-specific. For example, if your code has statements like:
run-query @mydb = ...
or
begin transaction @sales_db
then you must have files "mydb" and "sales_db" present. For example, MariaDB config file might look like:
[client]
user=velyuser
password=pwd
database=velydb
protocol=TCP
host=127.0.0.1
port=3306
or for PostgreSQL:
user=myuser password=mypwd dbname=mydb
Make application
To compile and link the application that doesn't use database(s):
When you have database(s) in use, for instance assuming in above example that "mydb" is MariaDB database, "sales_db" is PostgreSQL, and "contacts" is SQLite database:
vv -q --db="mariadb:mydb postgres:sales_db sqlite:contacts"
See
vv for more options.
Start application
Stop the application first in case it was running, then start the application:
vf -m quit <app name>
vf <app name>
See
vf for more details.
Running application
You can run your application as
FastCGI,
CGI or
command_line.
See also
Running application (
application_setup CGI Client_API command_line containerize_application FastCGI FastCGI_client plain_C_FCGI )
SEE ALL (
documentation)
begin-transaction
Purpose: Begins database transaction.
begin-transaction [ <options> ] [ @<database> ] [ on-error-continue | on-error-exit ] [ error [ define ] <error> ] [ error-text [ define ] <error text> ]
This statement begins a database transaction. <options> is any additional options to database's BEGIN/START you wish to supply and must immediately follow begin-transaction.
Once you start a transaction with begin-transaction, you must either commit it with
commit-transaction or rollback with
rollback-transaction. If you do neither, your transaction will be rolled back once the request has completed and your program will stop with an error message. This is because opening a transaction and leaving without committing or a rollback is a bug in your program.
You must use begin-transaction,
commit-transaction and
rollback-transaction instead of calling the BEGIN/COMMIT/END through
run-query.
Database
Optional <database> is specified in "@" clause and is the name of the
database_config_file.
Error handling
The error code is available in <error> variable in optional "error" clause - this code is always "0" if successful. The <error> code may or may not be a number but is always returned as a string value. <error> is
allocated memory. In case of error, error text is available in optional "error-text" clause in <error text>, which is
allocated memory.
"on-error-continue" clause specifies that
request processing will continue in case of error, whereas "on-error-exit" clause specifies that it will exit. This setting overrides database-level
on-error for this specific statement only. If you use "on-error-continue", be sure to check the error code.
<error> and <error text> can be created with optional "define".
Note that if database connection was lost, and could not be reestablished, the request will error out (see
error_handling).
Examples
begin-transaction @mydb
run-query @mydb="insert into employee (name, dateOfHire) values ('%s', now())" : "Terry" no-loop
commit-transaction @mydb
See also
Database (
begin-transaction commit-transaction current-row database_config_file database_queries delete-query on-error prepared_statements query-result rollback-transaction run-query )
SEE ALL (
documentation)
call-web
Purpose: Get content of URL resource (call a web address).
call-web <URL> \
response [ define ] <result> \
[ response-code [ define ] <response code> ] \
[ response-headers [ define ] <headers> ] \
[ status [ define ] <status> ] \
[ method <request method> ] \
[ request-headers [ content-type <content type> ] \
custom <header name>=<header value> [ , ... ] ] \
[ request-body \
( [ fields <field name>=<field value> [ , ... ] ] \
[ files <file name>=<file location> [ , ... ] ] ) \
| \
( payload <body content> [ payload-length <content length> ] ) \
] \
[ error [ define ] <error> ] \
[ cert <certificate> | no-cert ] \
[ cookie-jar <cookie jar> ] \
[ timeout <timeout> ]
With call-web, you can get the content of any accessible URL resource, for example web page, image, PDF document, XML document, REST API etc. It allows you to programmatically download URL's content, including the header. For instance, you might want to obtain (i.e. download) the source code of a web page and its HTTP headers. You can then save such downloaded items into files, analyze them, or do anything else.
<URL> is the resource locator, for example "https://some.web.page.com" or if you are downloading an image (for instance) it could be "https://web.page.com/image.jpg". Anything you can access from a client (such as web browser), you can also obtain programmatically. You can specify any URL parameters, for example "https://some.web.page.com?par1=val1&par2=val2".
Response and headers
The result is obtained via "response" clause into variable <result>, and the length (in bytes) of such response is obtained via "status" clause in <status> variable. <result> is
allocated memory.
The response code (such as 200 for "OK", 404 for "Not Found" etc.) is available via optional "response-code" clause in number variable <response code>; the default value is 0 if response code is unavailable (due to error for instance).
The optional "response-headers" clause allows for retrieval of response headers (such as HTTP headers) in <headers> variable, as a single string variable. <headers> is
allocated memory.
Each of <result>, <response code>, <headers> and <status> can be created if they don't exist with the corresponding "define".
Request method
You can specify the request method using "method" clause. <method> has a string value of the request method, such as "GET", "POST", "PUT", "PATCH", "DELETE" or any other.
Status
In case of error, <status> is negative, and has value of VV_ERR_FAILED (typically indicating system issue, such as lack of memory, library or system issue or local permissions), VV_ERR_WEB_CALL (error in accessing URL or obtaining data) - otherwise <status> is the length in bytes of the response (0 or positive). Optionally, you can obtain the error message (if any) via "error" clause in <error> variable (which can also be created with "define" if it doesn't exist). Error is an empty string ("") if there is no error. <error> is
allocated memory.
Timeout
If "timeout" clause is specified, call-web will timeout if operation has not completed within <timeout> seconds. If this clause is not specified, the default timeout is 120 seconds. If timeout occurs, <status> will be VV_ERR_WEB_CALL and <error> will indicate timeout. Timeout cannot be negative nor greater than 86400 seconds.
HTTPS and certificates
You can call any valid URL that uses protocol supported by the underlying library (cURL). If you're using https protocol (or any other that requires a SSL/TSL certificate), you can either use the local CA (certificate authority) issued, specify the location of a certificate with "cert" clause, or if you do not want it checked, use "no-cert". By default, the locally installed certificates are used; if the URL you are visiting is not trusted via those certificates, and you still want to visit it, use "no-cert"; and if you do have a no-CA (i.e. self-signed certificate) for that URL, use "cert" to provide it as a file name (either a full path or a name relative to current working directory, see
how_vely_works).
Cookies
If you'd like to obtain cookies (for example to maintain session or examine their values), use "cookie-jar" clause. <cookie jar> specifies the location of a file holding cookies. Cookies are read from this file (which can be empty or non-existent to begin with) before making a call-web and any changes to cookies are reflected in this file after the call. This way, multiple calls to the same server maintain cookies the same way browser would do. Make sure the same <cookie jar> file is not used across different application spaces, meaning it should be under the application home directory (see
how_vely_works), which is the most likely method of implementation.
Binary result
The result of call-web (which is <result>) can be a text value or a binary value. If it is a binary value (for example if getting "JPG", "PNG", "PDF" or other documents), then <status> is the number of bytes in a buffer that holds the value. If it is a string value (such as if downloading "HTML" document as a text), then <status> is the string length.
Request body, sending files and arbitrary content
In order to include request body, for instance to send files, use "request-body" clause. Request body is typically used with POST, PUT or PATCH methods. Even though not common, you can also use it with GET, DELETE or any other custom method, such as for example if the resource you wish to identify requires binary data; perhaps a disposable image is used to identify the resource.
- Structured content
Use "fields" and/or "files" subclauses to send a structured body request in the form of name/value pairs, the same as sent from an HTML form. To do that, you can specify fields with "fields" subclause in the form of <field name>=<field value> pairs separated by a comma. For instance, here two fields are set (field "source" with value "web" and field "act" with value "setcookie"):
To include files, use "files" subclause in the form of <file name>=<file location> separated by commas. For example, here "file1" is the file name sent to the client (which can be anything), and local file "uploadtest.jpg" is the file whose contents is sent; and "file23" is the file name sent to the client (which can be anything), and "fileup4.pdf" is the actual local file read and sent to the client. In this case files are in the application home directory (see
how_vely_works), but in general you can specify a relative or absolute path:
call-web "http://website.com" response resp response-code rc status len \
request-body files "file1"="uploadtest.jpg", "file23"="fileup4.pdf"
You can specify both "files" and "fields" fields, for instance (along with getting error text and status):
call-web "http://website.com/app_name/some_request" response resp response-code rc status len
request-body fields "source"="web","act"="setcookie" \
files "file1"="uploadtest.jpg", "file23"="fileup4.pdf" \
status define st error err
You can specify a maximum of 1000 files and fields.
- Non-structured content
To send any arbitrary (non-structured) content in the request body, such as JSON text for example, use "payload" subclause:
call-web "https://website.com" response resp \
request-headers content-type "application/json" \
request-body payload "{ \
\"employee\": { \
\"name\": \"sonoo\", \
\"salary\": 56000, \
\"married\": true \
} \
}"
Optional "payload-length" subclause can be specified to denote the length of body content:
read-file "somefile" to define file_contents status define file_length
call-web "https://website.com" response resp \
request-body payload file_contents \
payload-length file_length
If "payload-length" is not used, then it is assumed to be the length of string <payload>.
Request headers
If your request has a body (i.e. "request-body" clause is used), you can set the content type with "content-type" subclause of a request-headers clause:
call-web "https://<web address>/resource" request-headers \
content-type "application/json" \
request-body payload some.json
Note that using "content-type" without the request body may be ignored by the server processing your request or may cause it to consider the request invalid. If "content-type" is not used, the default is generally "multipart/form-data" if "fields" or "files" subclause(s) are used with "body-request" clause, or "application/x-www-form-urlencoded" otherwise. Thus, if you use "payload" subclause to send other types of data, you should set content type explicitly.
You can also specify custom request headers with "request-headers" clause, using "custom" subclause with a list of <header name>=<header value> pairs separated by a comma. For example, here custom header "Vely-header" has value of "Some_ID", and "Another-Header" a value of "New_ID":
call-web "http://website.com/<app name>/<request name>?act=get_file" response resp response-code rc status len \
request-headers custom "Vely-header"="Some_ID", "Another-Header"="New_ID"
On the receiving side you can get any such custom header by using "header" clause of the
get-req statement:
get-req header "Vely-header" to define hvh0
get-req header "Another-Header" to define hvh1
Examples
Get the web page and print it out:
Get the "JPG" image from the web and save it to a file "pic.jpg":
See also
Web (
call-web out-header send-file silent-header )
SEE ALL (
documentation)
CGI
You can run Vely application as a CGI (Common Gateway Interface) program, if your web server supports CGI. This is not recommended in general, as CGI programs do not exhibit great performance. However in some cases you may need to use CGI, such as when performance is not of critical importance.
To run your application with CGI, use Vely
command_line program. Since Vely applications require running in the security context of the user who owns the application, you must use "suexec" (or similar feature) of your web server.
The following script sets up an application named "func_test" (any kind of application will do) to run as CGI (after it's been compiled with
vv) on Apache web server running on Ubuntu 18 and up. For other web servers/distros, consult their documentation on how to setup CGI for a program.
sudo apt update
sudo a2enmod cgid
sudo service apache2 restart
sudo apt-get -y install apache2-suexec-custom
sudo a2enmod suexec
sudo service apache2 restart
sudo mkdir -p /usr/lib/cgi-bin/vv
sudo chown $(whoami):$(whoami) /usr/lib/cgi-bin/vv
sudo sed -i '1c\/usr/lib/cgi-bin/vv' /etc/apache2/suexec/www-data
sudo mv /var/lib/vv/bld/func_test/func_test /usr/lib/cgi-bin/vv
sudo chown $(whoami):$(whoami) /usr/lib/cgi-bin/vv/func_test
sudo chmod 700 /usr/lib/cgi-bin/vv/func_test
sudo sed -i "/SuexecUserGroup/d" /etc/apache2/sites-enabled/000-default.conf
sudo sed -i "s/<\/VirtualHost>/SuexecUserGroup $(whoami) $(whoami)\n<\/VirtualHost>/g" /etc/apache2/sites-enabled/000-default.conf
sudo service apache2 restart
See also
Running application (
application_setup CGI Client_API command_line containerize_application FastCGI FastCGI_client plain_C_FCGI )
SEE ALL (
documentation)
Client API
You can use Vely C API client library to connect to a
FastCGI application server, including Vely:
- The API has only one function, and that is "vv_fc_request()", which makes the call to the server.
- There is only a single data type used, which is "vv_fc" and it is used to specify request and its options, as well as to retrieve results.
- There is a single include file ("vfcgi.h").
- When building your client executable, you can specify build flags by using the result of "vv -i".
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:
vv_fc req = {0};
0
...
int result = vv_fc_request (&req);
- Return value
The following are possible return values from "vv_fc_request()":
- VV_OKAY if request succeeded,
- VV_FC_ERR_RESOLVE_ADDR if host name for TCP connection cannot be resolved,
- VV_FC_ERR_PATH_TOO_LONG if path name of Unix socket is too long,
- VV_FC_ERR_SOCKET if cannot create a socket (for instance they are exhausted for the process or system),
- VV_FC_ERR_CONNECT if cannot connect to server (TCP or Unix alike),
- VV_FC_ERR_SOCK_WRITE if cannot write data to server (for instance if server has encountered an error or is down or if network connection is no longer available),
- VV_FC_ERR_SOCK_READ if cannot read data from server (for instance if server has encountered an error or is down or if network connection is no longer available),
- VV_FC_ERR_PROT_ERR if there is a protocol error, which indicates a FastCGI protocol issue on either or both sides,
- VV_FC_ERR_BAD_VER if either side does not support FastCGI protocol used by the other,
- VV_FC_ERR_SRV if server cannot complete the request,
- VV_FC_ERR_UNK if server does not recognize record types used by the client,
- VV_FC_ERR_OUT_MEM if client is out of memory,
- VV_FC_ERR_ENV_TOO_LONG if the combined length of all environment variables is too long,
- VV_FC_ERR_ENV_ODD if the number of supplied environment name/value pairs is incorrect,
- VV_FC_ERR_BAD_TIMEOUT if the value for timeout is incorrect,
- VV_FC_ERR_TIMEOUT if the request timed out based on "timeout" parameter or otherwise if the underlying Operating System libraries declared their own timeout.
- 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.
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).
Request type (vv_fc)
Type "vv_fc" is defined as:
typedef struct {
const char *fcgi_server;
const char *req_method;
const char *app_path;
const char *req;
const char *url_payload;
const char *content_type;
int content_len;
const char *req_body;
char **env;
int timeout;
int req_status;
char *data;
int data_len;
char *error;
int error_len;
vv_fc_out_hook out_hook;
vv_fc_err_hook err_hook;
} vv_fc;
Here is the more detailed explanation:
"fcgi_server" represents 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 server, 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
"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:
vv_fc req = {0};
...
vv_fc_request (&req);
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:
void get_output(char *d, int l)
{
printf("Got output of [%.*s] of length [%d]", l, d, l);
}
void get_err(char *d, int l)
{
printf("Got error of [%.*s] of length [%d]", l, d, l);
}
...
vv_fc req = {0};
...
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 ()
{
vv_fc req = {0};
req.fcgi_server = "/var/lib/vv/helloworld/sock/sock";
req.req_method = "GET";
req.app_path = "/helloworld";
req.req = "/hello-simple";
int res = vv_fc_request (&req);
if (res != VV_OKAY) printf ("Request failed [%d] [%s]\n", res, req.errm);
else printf ("%s", req.data);
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:
The output is, as expected:
- 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 ()
{
vv_fc req;
3
char *env[] = { "REMOTE_USER", "John", "SOME_VAR", "SOME\nVALUE", "NEW_VAR", "1000", NULL };
req.env = env;
req.fcgi_server = "127.0.0.1:2301";
req.req_method = "GET";
req.app_path = "/helloworld";
req.req = "/hello";
req.url_payload = "par1=val1&par2=91";
req.content_type = "application/json";
req.req_body = "This is request body";
req.content_len = strlen (req.req_body);
0
req.timeout = 0;
int res = vv_fc_request (&req);
if (res != VV_OKAY) printf("Request failed [%d] [%s]\n", res, req.errm);
else {
printf("Server status %d\n", req.req_status);
printf("Len of data %d\n", req.data_len);
printf("Len of error %d\n", req.error_len);
printf("Data [%s]\n", req.data);
printf("Error [%s]\n", req.error);
}
free (req.data);
free (req.error);
}
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
request-body rb
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
@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 line #<num of line>"
1418"Line 1419 has an error"
4418
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 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:
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)
Command line
Command-line programs
A Vely application can run as a web application or a command line program, or both - such as when some requests can be either fulfilled through web interface or the command line. Note that Vely produces two separate executables: a
FastCGI server one and a command-line one - they are different because command-line program does not need the FastCGI library and thus is smaller.
The name of the command-line executable is the same as the application name, and its path is (assuming <app name> is the application name):
/var/lib/vv/bld/<app name>/<app name>
Output
A command line program works the same way as a FastCGI executable, and the output is the same, except that it is directed to stdout (standard output) and stderr (standard error).
Exit code
To specify the exit code of a command line program, use
exit-code. To exit the program, use
exit-request, or otherwise the program will exit when it reaches the end of a
request.
Executing a request
A command line program must obtain its request URL via environment variables, for example if the application name is "stock", here is how to execute a request "add-stock" with
input-parameters "name" having a value of "ABC" and "price" a value of "300":
export REQUEST_METHOD=GET
export SCRIPT_NAME="/stock"
export PATH_INFO="/add-stock"
export QUERY_STRING="name=ABC&price=300"
/var/lib/vv/bld/stock/stock
Note that you if specify parameters as part of the path, you could write the above the same way as in a URL:
export REQUEST_METHOD=GET
export SCRIPT_NAME="/stock"
export PATH_INFO="/add-stock/name/ABC"
export QUERY_STRING="price=300"
/var/lib/vv/bld/stock/stock
Including a request body
You can include a request body when executing a command-line program. It is always included as the standard input (stdin) to the program.
You must provide the length of this input (as CONTENT_LENGTH environment variable), the type of input (as CONTENT_TYPE), as well as REQUEST_METHOD (such as POST, PUT, PATCH, GET, DELETE or any other).
Here is an example of using a request body to make a POST request on the command line - the application name is "json" and request name is "process". File "prices.json" is sent as request body:
export CONTENT_LENGTH=$(stat -c%s prices.json)
export CONTENT_TYPE="application/json"
export SCRIPT_NAME="/json"
export PATH_INFO="/process"
export QUERY_STRING="act=get_total&period=YTD"
export REQUEST_METHOD=POST
cat prices.json | /var/lib/vv/bld/ordering/ordering
Note that you can also include any other headers as environment variables by using the "HTTP_" convention, where custom headers are capitalized with use of underscore for dashes and prefixed with "HTTP_", for example header "Vely-Header" would be set as:
export HTTP_VELY_HEADER="some value"
Suppressing HTTP header output for the entire application
If you wish to suppress the output of HTTP headers for all requests, set environment variable VV_SILENT_HEADER to "yes" before executing the program:
export VV_SILENT_HEADER=yes
This will suppress the effect of
out-header, or for any other case where headers are output. This has the same effect as
silent-header, the only difference is that it applies to the entire application.
URL-encoding the input
Any data in QUERY_STRING or PATH_INFO must be formatted to be a valid URL; for example, data that contains special characters (like "&" or "?") must be URL-encoded, for instance:
export QUERY_STRING="action=show&data=a%3Fb"
In this case, field "data" has value of "a?b", where special character "?" is encoded as "%3F".
To make sure all your input parameters are properly URL-encoded, use Vely's v1 code processor:
$($(vv -l)/v1 -urlencode '<your data>')
For instance, to encode "a?=b" as a parameter:
export QUERY_STRING="act=show&data=$($(vv -l)/v1 -urlencode 'a?=b')"
CGI
You can also use a command line program with
CGI (Common Gateway Interface). Note that CGI programs generally exhibit much lower performance; use CGI only when warranted by a specific situation.
See also
Running application (
application_setup CGI Client_API command_line containerize_application FastCGI FastCGI_client plain_C_FCGI )
SEE ALL (
documentation)
commit-transaction
Purpose: Commits a database transaction.
commit-transaction [ @<database> ] [ on-error-continue | on-error-exit ] [ error [ define ] <error> ] [ error-text [ define ] <error text> ]
Database transaction started with
begin-transaction is committed with commit-transaction.
Database
Optional <database> is specified in "@" clause and is the name of the
database_config_file.
Error handling
The error code is available in <error> variable in optional "error" clause - this code is always "0" if successful. The <error> code may or may not be a number but is always returned as a string value. <error> is
allocated memory. In case of error, error text is available in optional "error-text" clause in <error text>, which is
allocated memory.
"on-error-continue" clause specifies that
request processing will continue in case of error, whereas "on-error-exit" clause specifies that it will exit. This setting overrides database-level
on-error for this specific statement only. If you use "on-error-continue", be sure to check the error code.
<error> and <error text> can be created with optional "define".
Note that if database connection was lost, and could not be reestablished, the request will error out (see
error_handling).
Examples
begin-transaction @mydb
run-query @mydb="insert into employee (name, dateOfHire) values ('Terry', now())"
run-query @mydb="insert into payroll (name, salary) values ('Terry', 100000)"
commit-transaction @mydb
See also
Database (
begin-transaction commit-transaction current-row database_config_file database_queries delete-query on-error prepared_statements query-result rollback-transaction run-query )
SEE ALL (
documentation)
Connect apache tcp socket
This shows how to connect your application listening at TCP port <port number> (started with "-p" option in
vf) to Apache web server.
- Step 1:
To setup Apache as a reverse proxy and connect your application to it, you need to enable FastCGI proxy support, which generally means "proxy" and "proxy_fcgi" modules - this is done
only once:
- For Debian (like Ubuntu) and OpenSUSE systems you need to enable proxy and proxy_fcgi modules:
sudo a2enmod proxy
sudo a2enmod proxy_fcgi
- For Fedora systems (or others like Archlinux) enable proxy and proxy_fcgi modules by adding (or uncommenting) LoadModule directives in the Apache configuration file - the default location of this file on Linux depends on the distribution. For Fedora (such as RedHat), Archlinux:
sudo vi /etc/httpd/conf/httpd.conf
For OpenSUSE:
sudo vi /etc/apache2/httpd.conf
Add this to the end of the file:
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
- Step 2:
Edit the Apache configuration file:
- For Debian (such as Ubuntu):
sudo vi /etc/apache2/apache2.conf
- for Fedora (such as RedHat), Archlinux:
sudo vi /etc/httpd/conf/httpd.conf
- and for OpenSUSE:
sudo vi /etc/apache2/httpd.conf
Add this to the end of file ("/<app path>" is the application path, see
request_URL):
ProxyPass "/<app path>" fcgi://127.0.0.1:<port number>/
- Step 3:
Finally, restart Apache. On Debian systems (like Ubuntu) or OpenSUSE:
sudo systemctl restart apache2
On Fedora systems (like RedHat) and Arch Linux:
sudo systemctl restart httpd
Note: you must not have any other URL resource that starts with "/<app path>" (such as for example "/<app path>.html" or "/<app path>_something" etc.) as the web server will attempt to pass them as a reverse proxy request, and they will likely not work. If you need to, you can change the application path to be different from "/<app path>", see
request_URL.
See also
Web servers (
connect_apache_tcp_socket connect_apache_unix_socket connect_nginx_tcp_socket connect_nginx_unix_socket )
SEE ALL (
documentation)
Connect apache unix socket
This shows how to connect your application listening on a Unix socket (started with
vf) to Apache web server.
- Step 1:
To setup Apache as a reverse proxy and connect your application to it, you need to enable FastCGI proxy support, which generally means "proxy" and "proxy_fcgi" modules - this is done
only once:
- For Debian (like Ubuntu) and OpenSUSE systems you need to enable proxy and proxy_fcgi modules:
sudo a2enmod proxy
sudo a2enmod proxy_fcgi
- For Fedora systems (or others like Archlinux) enable proxy and proxy_fcgi modules by adding (or uncommenting) LoadModule directives in the Apache configuration file - the default location of this file on Linux depends on the distribution. For Fedora (such as RedHat), Archlinux:
sudo vi /etc/httpd/conf/httpd.conf
For OpenSUSE:
sudo vi /etc/apache2/httpd.conf
Add this to the end of the file:
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
- Step 2:
Edit the Apache configuration file:
- For Debian (such as Ubuntu):
sudo vi /etc/apache2/apache2.conf
- for Fedora (such as RedHat), Archlinux:
sudo vi /etc/httpd/conf/httpd.conf
- and for OpenSUSE:
sudo vi /etc/apache2/httpd.conf
Add this to the end of file ("/<app path>" is the application path (see
request_URL) and "<app name>" is your application name):
ProxyPass "/<app path>" unix:///var/lib/vv/<app name>/sock/sock|fcgi://localhost/<app name>
- Step 3:
Finally, restart Apache. On Debian systems (like Ubuntu) or OpenSUSE:
sudo systemctl restart apache2
On Fedora systems (like RedHat) and Arch Linux:
sudo systemctl restart httpd
Note: you must not have any other URL resource that starts with "/<app path>" (such as for example "/<app path>.html" or "/<app path>_something" etc.) as the web server will attempt to pass them as a reverse proxy request, and they will likely not work. If you need to, you can change the application path to be different from "/<app path>", see
request_URL.
See also
Web servers (
connect_apache_tcp_socket connect_apache_unix_socket connect_nginx_tcp_socket connect_nginx_unix_socket )
SEE ALL (
documentation)
Connect nginx tcp socket
This shows how to connect your application listening at TCP port <port number> (started with "-p" option in
vf) to Nginx web server.
- Step 1:
You will need to edit the Nginx configuration file. For Ubuntu and similar:
sudo vi /etc/nginx/sites-enabled/default
while on Fedora and other systems it might be at:
sudo vi /etc/nginx/nginx.conf
Add the following in the "server {}" section ("/<app path>" is the application path, see
request_URL):
location /<app path> { include /etc/nginx/fastcgi_params; fastcgi_pass 127.0.0.1:<port number>; }
- Step 2:
Finally, restart Nginx:
sudo systemctl restart nginx
Note: you must not have any other URL resource that starts with "/<app path>" (such as for example "/<app path>.html" or "/<app path>_something" etc.) as the web server will attempt to pass them as a reverse proxy request, and they will likely not work. If you need to, you can change the application path to be different from "/<app path>", see
request_URL.
See also
Web servers (
connect_apache_tcp_socket connect_apache_unix_socket connect_nginx_tcp_socket connect_nginx_unix_socket )
SEE ALL (
documentation)
Connect nginx unix socket
This shows how to connect your application listening on a Unix socket (started with
vf) to Nginx web server.
- Step 1:
You will need to edit the Nginx configuration file. For Ubuntu and similar:
sudo vi /etc/nginx/sites-enabled/default
while on Fedora and other systems it might be at:
sudo vi /etc/nginx/nginx.conf
Add the following in the "server {}" section ("/<app path>" is the application path (see
request_URL) and "<app name>" is your application name):
location /<app path> { include /etc/nginx/fastcgi_params; fastcgi_pass unix:///var/lib/vv/<app name>/sock/sock; }
- Step 2:
Finally, restart Nginx:
sudo systemctl restart nginx
Note: you must not have any other URL resource that starts with "/<app path>" (such as for example "/<app path>.html" or "/<app path>_something" etc.) as the web server will attempt to pass them as a reverse proxy request, and they will likely not work. If you need to, you can change the application path to be different from "/<app path>", see
request_URL.
See also
Web servers (
connect_apache_tcp_socket connect_apache_unix_socket connect_nginx_tcp_socket connect_nginx_unix_socket )
SEE ALL (
documentation)
Containerize application
This is a short tutorial on building a base Vely image, and an application image (regardless of what your application does) that can be deployed anywhere, even on non-Linux systems. This is one way to deploy your Vely application. See also
example_docker for an example you can run. You can use
docker or
podman (substitute podman for docker).
The application container has a Vely application installed, and your reverse proxy web server and your databases must be created (see
application_setup for more information), as they will likely either run on the host machine, or be containers themselves.
The base image used here is "vely" (created in the previous step). User "vely" is created and assigned limited sudo privileges for Vely only. Source code of your application is in "docker" directory on the host, and copied to the container, where application is compiled and linked, then source code is deleted, making the container ready for deployment (if you wish to keep the source, you certaintly can, just remove the lines that delete it). Finally, the entry point will start application - that's what runs when the container is deployed.
The following shows how to create a deployment image and a subsequent running container. The application name is "velydemo" - change it as needed.
FROM ubuntu:20.04
ENV TZ=America/Phoenix
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN apt update
RUN apt install -y apt-transport-https ca-certificates wget sudo
RUN wget -qO - https://vely.dev//pkg/OPENPGP|sudo tee /usr/share/keyrings/vely.asc >/dev/null
ARG arch
RUN sudo bash -c "echo 'deb [signed-by=/usr/share/keyrings/vely.asc] https://vely.dev//pkg/ubuntu_20_$arch/latest ubuntu_20_$arch vely' >/etc/apt/sources.list.d/vely.list"
RUN sudo apt update
RUN sudo apt install -y vely
Create an image with - use --no-cache in order to update the image when new Vely release is available:
sudo docker build --no-cache -t vely .
In some cases your application is stateless, i.e. all the data is through to database(s) and other applications. In other cases, you may want to have file storage that persists even when containers recycle - typically that means persisting Vely directory (which is /var/lib/vv, see
how_vely_works). In that case, you can use a container storage option for that purpose; in case of docker, that would be volumes, for instance:
sudo docker volume create velyhome
For example, if your application uploads/downloads files, you may use Vely
file_storage (which is under /var/lib/vv); or you may store other files that your application produces. In such cases using a volume (or similar) may be a good idea; in other cases you may want a stateless container and offload such functionality elsewhere (for instance to databases, other applications etc.).
Next is velyapp.dockerfile, which is used to create an image for an application. In this case it is a demo application with source code in "docker" directory (under the current working directory):
FROM vely
RUN useradd -ms /bin/bash vely && echo "vely:vely" | chpasswd
RUN echo "vely ALL=(ALL) NOPASSWD: /usr/bin/vf" >> /etc/sudoers
USER vely
WORKDIR /home/vely
EXPOSE 2300
COPY ./docker/* /home/vely/
ENTRYPOINT [ "./runit" ]
The "runit" script will create an application in the container, build it, and start it in the foreground. This script is in the "docker" directory with your application source code:
sudo vf -i -u $(whoami) velydemo
vv -c
vv -q --db=mariadb:db -s
vv -c
vf -f -w3 -p2300 velydemo > demout
Stop and remove any current containers/images:
sudo docker stop velyapp || true
sudo docker rmi velyapp || true
Build an application image:
sudo docker build --no-cache -t velyapp -f velyapp.dockerfile .
Assuming you have setup web server and any database(s), start the container (using "host" network interface for simplicity):
sudo docker run --init --name velyapp -d --network="host" --rm velyapp velyapp
If your application uses volumes for data persistence, you might use:
sudo docker run --init --name velyapp -d -v velyhome:/var/lib/vv --network="host" --rm velyapp
Note that you can persist any storage you want, however at the minimum you would likely persist Vely directory (/var/lib/vv) which is stored on the host under volume "velyhome".
Now you can use your application through web server:
http://127.0.0.1/velydemo/<request name>...
See also
Running application (
application_setup CGI Client_API command_line containerize_application FastCGI FastCGI_client plain_C_FCGI )
SEE ALL (
documentation)
copy-string
Purpose: Copies string to another string.
copy-string <source string> to [ define ] <dest string> \
[ ( length [ define ] <length> ) \
| ( [ bytes-written [ define ] <bytes written> ) ]
Use copy-string to copy <source string> to <dest string> (which can be created with optional "define"). <dest string> is
allocated memory.
Without "length" clause, <source string> is treated as a null-terminated string and the number of bytes to copy is computed as its length. With "length" clause, exactly <length> bytes are copied into <dest string>, regardless of whether there is a null-character within <source string>, i.e. you can copy binary data this way. Regardless of whether "length" clause is used or not, <dest string> will always have a null-character at the end.
If "bytes-written" clause is used, then the number of bytes copied is stored into <bytes written>, which can be created with optional "define". "bytes-written" can be used only if "length" is not used, for obvious reason, as the two are equal otherwise. <bytes written> does not include the null-character placed at the end.
You can copy a string to itself. In this case, the original string remains and the new string points to a copy:
char *str = "original string";
char *orig = str;
copy-string str to str // make a copy of string to change and assign it to itself
str[0] = 'O';
"str""Original string"
"orig""original string"
Examples
After copy-string below, "my_str" will point to a copy of string "some value", and "bw" will be 10:
char *other_string="some value";
copy-string other_string to define my_str bytes-written define bw
Copy certain number of bytes, the result in "my_str" will be "som":
char *other_string="some value";
copy-string other_string to define my_str length 3
See also
Strings (
copy-string count-substring lower-string split-string trim-string upper-string write-string )
SEE ALL (
documentation)
count-substring
Purpose: Count substrings.
count-substring <substring> in <string> to [ define ] <count> [ case-insensitive [ <case-insensitive> ] ]
count-substring counts the number of occurrences of <substring> in <string> and stores the result in <count> (specified in "to" clause), which can be created with "define" if it does not exist. By default, search is case-sensitive. If you use "case-insensitive" clause without boolean expression <case-insensitive>, or if <case-insensitive> evaluates to true, then the search is case-insensitive.
If <substring> is empty ("") or NULL, <count> is 0.
Examples
In the following example, 1 occurrence will be found after the first count-substring, and 2 after the second (since case insensitive search is used there):
char sub[] = "world";
char str[] = "Hello world and hello World!";
count-substring sub in str to define num_occ
pf-out "Found %lld occurrences!\n", num_occ
count-substring sub in str to num_occ case-insensitive
pf-out "Found %lld occurrences!\n", num_occ
See also
Strings (
copy-string count-substring lower-string split-string trim-string upper-string write-string )
SEE ALL (
documentation)
Database config file
Vely application can use any number of databases, with each specified by a database configuration file. This file provides database name, login and connection settings and preferences.
When making a Vely project, you specify a database vendor and the database configuration file for each database your application uses (see
vv), for instance:
vv ... --db="mariadb:db1 postgres:db2 sqlite:db3" ...
in which case there are three database configuration files (db1, db2 and db3), with db1 being MariaDB, db2 being PostgreSQL and db3 being SQLite database.
You must create a database configuration file for each database your application uses, and this file must be placed with your source code. Such file will be copied to locations specified and used by Vely to connect to database(s) (see
how_vely_works).
Each such file contains connection information and authentication to a database, which Vely uses to login. The names of these configuration files are used in queries. There is no limit on how many databases can be used in your application and those from different vendors can be used in the same application.
An example of database configuration file (in this case MariaDB):
[client]
user=mydbuser
password=somepwd
database=mydbname
protocol=TCP
host=127.0.0.1
port=3306
Using in your queries
Database statements that perform queries (such as
run-query) must specify the database configuration file used, unless your application uses only a single database. Such configuration is given by "@<database config file>" (for instance in
run-query or
begin-transaction). For example, in:
run-query @mydb="select name from employees"
...
end-query
the query "myquery" is performed on a database specified by the configuration file "mydb", as in (assuming it's PostgreSQL database):
vv ... --db="postgres:mydb" ...
You do not need to manually connect to the database; when your application uses it for the first time, a connection is automatically established, and lost connection is automatically re-established when needed.
If a database is the only one used by your application, you can omit it, as in:
run-query ="select name from employees"
...
end-query
Connection settings
The contents of a configuration file depends on the database used:
- MariaDB: configuration file is written as a MariaDB client options file. You can see the parameters available at
https://mariadb.com/kb/en/configuring-mariadb-connectorc-with-option-files/#options
Most of the time, though, you would likely use only a few of those options, as in (for local connection):
[client]
user=myuser
password=mypwd
database=mydb
socket=/run/mysqld/mysqld.sock
The above file has fields "user" (MariaDB user), "password" (the password for MariaDB user), "database" (the MariaDB database name) and MariaDB communication "socket" location (assuming your database is local to your computer - if the database is across network you would not use sockets!).
If you use passwordless MariaDB login (such as when the MariaDB user name is the same as your Operating System user name and where unix socket plugin is used for authentication), the password would be empty.
To get the location of the socket, you might use:
sudo mysql -u root -e "show variables like 'socket'"
- Postgres: configuration file has a Postgres connection string. You can see the parameters available at
https://www.postgresql.org/docs/14/libpq-connect.html#LIBPQ-CONNSTRING
Most of the time, though, you may be using only a few of those options, as in:
user=myuser password=mypwd dbname=mydb
The above file has parameters "user" (Postgres user), "password" (the password for Postgres user), "dbname" (the Postgres database name). If you use peer-authenticated (i.e. passwordless) login, then omit "password" - this is when the Postgres user name is the same as your Operating System user name and where local unix domain socket is used for authentication.
- SQLite: configuration file should contain a single line of text, and it must be the full path to the SQLite database file used, for example (if you keep it in Vely's application directory):
/var/lib/vv/<app name>/app/<db name>.db
Substituting environment variables
You can use environment variables in database configuration files by means of substitution, in the form of "${VAR_NAME}". For example in file "mydb":
[client]
user=${DB_USER}
password=${DB_PWD}
database=${DB_NAME}
protocol=TCP
host=127.0.0.1
port=${DB_PORT}
Here, environment variables DB_USER, DB_PWD, DB_NAME and DB_PORT are used. They must be defined in the shell environment prior to calling
vv to make your application (if not defined the value will be empty):
export DB_USER="my_user"
export DB_PWD="my_password"
export DB_NAME="my_database"
export DB_PORT="3307"
vv -q --db=mariadb:mydb
which results in file /var/lib/vv/<app name>/app/db/mydb:
[client]
user=my_user
password=my_password
database=my_database
protocol=TCP
host=127.0.0.1
port=3307
Besides making application deployment easier, this also adds to its security as the information such as above (including the database password) does not need to be a part of source code and reside in source code control system (such as git).
Your environment variables can have any names, except that they cannot start with an underscore ("_") or be prefixed by "VV_", because those variable names are reserved by Vely.
Note that if your data actually has a dollar sign and is a part of the configuration file, then you can create a variable for it:
and in the configuration file:
..
database=my${DOLLAR_SIGN}database
..
In this case the database name is "my$database".
See also
Database (
begin-transaction commit-transaction current-row database_config_file database_queries delete-query on-error prepared_statements query-result rollback-transaction run-query )
SEE ALL (
documentation)
Database queries
Databases
Your application can use any number of databases, specified by
database_config_files. If only a single database is used, you may skip specifying a database with "@" clause. You can use different database vendors at the same time.
Query interface, SQL injection
Vely has the same query interface for all databases (see for example
run-query). It provides means to execute dynamic as well as static queries; regardless, query input parameters are always separated from the query logic. This separation is in some cases emulated (see
run-query), and in others native, such as with prepared statements (see
run-query). In either case, Vely provides protection against SQL injections.
Input and output parameters
Input parameters and output resuls are always null-delimited strings. In web applications, more often than not, it is string values that are sought as output or provided as input, and conversion to other types rarely happens; there is surely a number of applications where the opposite may be true, but for the majority of applications this approach may work better. If data conversion is needed, it can be performed pre-query or post-query, depending on the purpose. The placeholder for input parameter is chosen to represent this approach, using single quotes (as text literals are single quoted in SQL) and %s (a C convention for string placeholders), thus '%s' is used for input parameters.
Binary data
Binary data can be handled for storage or retrieval via hexadecimal strings (see
encode-hex,
decode-hex) or Base64 (see
decode-base64,
encode-base64); this is reasonable for smaller amounts of data. Note that storing large amounts of binary data in the database is generally less desirable than storing such data (for instance PDF or JPG documents) in the file system (see
file_storage), as manipulating and retrieving binary data is generally easier and faster that way.
Prepared statements
Prepared statements typically provide better performance but may not be ideal in every circumstance (see
prepared_statements).
Multiple statements
Multiple statements in one query can be executed in PostgreSQL, where only the last statement's result set is available, which is simpler and probably more manageable. MariaDB multiple statement execution is disabled by default, and so is for SQLite. Multiple statement execution isn't ideal as generally the number of statements may not be known in advance and retrieving multiple results sets can be challenging. Using stored procedures that return a single result set, or multiple queries to execute multiple statements are both better approach.
Prepared queries, regardless of the database, can never be multiple statements, which is a limitation that exists in all databases.
Connection persistence and reuse, transactions
A single Vely process (see
how_vely_works and
vely_architecture) keeps a single connection open to the database, which is shared between all requests a process will handle. Each request will either commit or rollback a transaction; if the transaction is neither committed nor rollback-ed, it will be rollback-ed with error (see
begin-transaction). If a database connection is lost, Vely will automatically re-establish it if possible. This allows for very fast query execution and essentially unlimited reuse of prepared statements.
Autocommit
The autocommit feature is enabled by default on all supported databases. Do not disable it, as that would cause unpredictable and inconsistent behavior. Whenever you need a transactional behavior, use
commit-transaction to begin a transaction and
commit-transaction/
rollback-transaction to end it. Also, in general, multiple DML statements (such as INSERT) are faster within a transaction block because the data is not flushed with each one, but rather once per block.
See also
Database (
begin-transaction commit-transaction current-row database_config_file database_queries delete-query on-error prepared_statements query-result rollback-transaction run-query )
SEE ALL (
documentation)
Debugging
Several techniques on debugging Vely applications are discussed here.
Tracing and Backtrace file
To see any errors reported by Vely, use -e option of
vv and check backtrace file. For example, to see the last 3 error messages:
You can use
trace-run statement to create run-time traces (see
how_vely_works for directory location). To quickly find the location of recently written-to trace files, use -t option of
vv, for example for 5 most recently used trace files:
Use
get-req to get the trace file location at run-time from your application.
Output from FastCGI application without web server
Use
FastCGI_client to send request and receive reply from your
FastCGI server processes from command line. This is useful in debugging issues and automating tests.
Issues in starting vf
vf starts your web application, running as
FastCGI processes. If you're having issues with vf, check out its log. Assuming your application name is "app_name", the log file is:
/var/lib/vv/app_name/vflog/log
Web server error log
Check web server's error log, which would store the error messages emitted to the client. Typically, such files are in the following location:
(for example /var/log/apache2), but the location may vary - consult your web server's documentation.
Using gdb debugger
In order to use gdb debugger, you must make your application with "--debug" flag (see
vv). Do not use "--debug" in any other case, because performance will be considerably affected.
Ultimately, you can attach a debugger to a running Vely process. If your application name is "app_name", first find the PID (process ID) of its process(es):
ps -ef|grep app_name.fcgi
Note that you can control the number of worker processes started, and thus have only a single worker process (or the minimum necessary), which will make attaching to the process that actually processes a request easier (see
vv).
Use gdb to load your program:
sudo gdb /var/lib/vv/bld/app_name/app_name.fcgi
and then attach to the process (<PID> is the process ID you obtained above):
Once attached, you can break in the request you're debugging:
or in general Vely request dispatcher:
which would handle any request to your application.
Error containing "vely_statement_must_be_within_code_block_here"
If you get an error like:
error: ‘_vely_statement_must_be_within_code_block_here_1’ undeclared (first use in this function);
then you are attempting to use a Vely statement in a single-statement implicit block, as in:
if (some condition)
exec-program ....
Since each Vely statement expands into one or more lines, if a Vely statement is the sole functional code used in an implicit code block, you must place it in an explicit block:
if (some condition) {
exec-program ....
}
See
example_hello_world for an example in debugging and tracing.
See also
Debugging (
debugging trace-run )
SEE ALL (
documentation)
decode-base64
Purpose: Base64 decode.
decode-base64 <data> to [ define ] <output data> \
[ input-length <input length> ] \
[ output-length [ define ] <output length> ]
decode-base64 will decode string <data> into <output data>, which can be binary data. <output data> is
allocated memory.
If "input-length" clause is used, then <input length> is the number of bytes decoded, otherwise <data> is treated as a null-terminated string and its length determined.
The result is stored in <output data> (in "to" clause), which can be created with optional "define". The length of the decoded string can be obtained via "output-length" clause in <output length>, which can be created with optional "define".
<output data> will always have an extra null byte appended - this null byte is not included in length and is appended only as a safety precaution.
Note that the string to decode can have whitespaces before it (such as spaces or tabs), and whitespaces and new lines after it, which will be ignored for the purpose of decoding.
Examples
See
encode-base64.
See also
Base64 (
decode-base64 encode-base64 )
SEE ALL (
documentation)
decode-hex
Purpose: Decode hexadecimal string into data.
decode-hex <data> to [ define ] <output> \
[ input-length <input length> ] \
[ output-length [ define ] <output length> ]
decode-hex will decode hexadecimal string <data> to string <output> given in "to" clause, which may be any binary data. <output> is
allocated memory.
<data> must consist of an even number of digits 0-9 and letters A-F or a-f. The length of <data> may be given by optional <input length> in "input-length" clause, otherwise it is assumed to be the string length of <data>. The output string <output> is allocated and its length is given in the optional <output length> in "output-length" clause.
Optional "define" subclause can be used to create <output> string and a number variable <output length> if they do not already exist.
Examples
Get binary data from a hexadecimal string "hexdata". The output string "binout" is created and so is its length "olen" variable of type "num":
char *hexdata = "0041000F414200";
decode-hex hexdata to define binout output-length define olen
The value of "binout" will be binary data equal to this C literal:
char *binout = "\x00""A""\x00""\xF""AB""\x00""\x04";
See also
Hex encoding (
decode-hex encode-hex )
SEE ALL (
documentation)
decode-url
Purpose: Decode URL-encoded string.
decode-url <string> [ output-length [ define ] <decoded length> ] [ input-length <length> ]
decode-url will decode <string> (created by
encode-url or other URL-encoding software) and store the result back into it (i.e. it will be modified). An optional "output-length" clause lets you get the length of decoded string (which is always lesser or equal than the length of the encoded string) in <decoded length>, which can be created with optional "define". <length> in optional "input-length" clause specifies the number of bytes to decode; if omitted or negative, it is the string length of <string>.
All encoded values (starting with %) are decoded, and "+" (plus sign) is converted to space. If there is an error (for example hexadecimal value following % is invalid), the decoding stops and whatever was decoded up to that point is the result, and the length reflects that.
Note that <string> must not be a constant because decode-url will write into it. If it is a constant, make a copy of it first with
copy-string. See
encode-url.
Examples
Decode URL-encoded string "str", after which it will hold a decoded string. "dec_len" will be the length (in bytes) of that string:
decode-url str output-length define dec_len
See also
URL encoding (
decode-url encode-url )
SEE ALL (
documentation)
decode-web
Purpose: Decode web(HTML)-encoded string.
decode-web <string> [ output-length [ define ] <decoded length> ] [ input-length <length> ]
decode-web will decode <string> (created by
encode-web or other web-encoding software) and store the result back into it (i.e it will be modified). "output-length" clause lets you get the length of the decoded string (which is always lesser or equal than the length of the encoded string) in <decoded length>, which can be created with optional "define". To decode only a number of leading bytes in <string>, use "input-length" clause and specify <length>.
See
encode-web.
Examples
Decode web-encoded string "str", after which it will hold a decoded string. "dec_len" will be the length (in bytes) of that string:
decode-web str output-length define dec_len
See also
Web encoding (
decode-web encode-web )
SEE ALL (
documentation)
decrypt-data
Purpose: Decrypt data.
decrypt-data <data> to [ define ] <result> \
[ input-length <input length> ] \
[ output-length [ define ] <output length> ] \
[ binary [ <binary> ] ] \
( password <password> \
[ salt <salt> [ salt-length <salt length> ] ] \
[ iterations <iterations> ] \
[ cipher <cipher algorithm> ] \
[ digest <digest algorithm> ]
[ cache ]
[ clear-cache <clear cache> ) \
[ init-vector <init vector> ]
decrypt-data will decrypt <data> which must have been encrypted with
encrypt-data, or other software using the same algorithms and clauses as specified.
If "input-length" clause is not used, the data to decrypt is considered to be a string, i.e. null-terminated, otherwise, if specified, then exactly <input length> bytes are decrypted. Password used for decryption is <password> (in "password" clause) and it must match the password used in
encrypt-data. If "salt" clause is used, then string <salt> must match the salt used in encryption. If "init-vector" clause is used, then string <init vector> must match the IV (initialization vector) used in encryption. If "iterations" clause is used, then <iterations> must match the number used in encryption.
"output-length" clause lets you obtain the number of bytes in decrypted data in <output length>, which can be created with optional "define". The result of decryption is in <result> (in "to" clause) and can be created with optional "define". <result> is
allocated memory.
If data was encrypted in binary mode (see
encrypt-data), you must decrypt it with the same, and if it wasn't, then you must not use it in decrypt-data either. The reason for this is obvious - binary mode of encryption is encrypted data in its shortest form, and character mode (without "binary" or if <binary> evaluates to false) is the same data converted to a hexadecimal string - thus decryption must first convert such data back to binary before decrypting.
The cipher and digest algorithms (if specified as <cipher algorithm> and <digest algorithm> in "cipher" and "digest" clauses respectively) must match what was used in
encrypt-data.
"cache" clause is used to cache the result of key computation, so it is not computed each time decryption takes place, while "clear-cache" allows key to be re-computed every time <clear cache> evaluates to boolean true. For more on "cache" and "clear-cache" clauses, as well as safety of encrypting/decrypting, see "Caching key" and "Safety" in
encrypt-data.
Examples
See
encrypt-data.
See also
Encryption (
decrypt-data derive-key encrypt-data hash-string random-crypto random-string )
SEE ALL (
documentation)
delete-cookie
Purpose: Deletes a cookie.
delete-cookie <cookie name> [ path <cookie path> ] [ status [ define ] <status> ] [ secure <secure> ]
delete-cookie marks a cookie named <cookie name> for deletion, so it is sent back in the reply telling the client (such as browser) to delete it.
Newer client implementations require a cookie deletion to use a secure context if the cookie is considered secure, and it is recommended to use "secure" clause to delete such a cookie. This is the case when either "secure" clause is used without optional boolean expression <secure>, or if <secure> evaluates to true.
<cookie name> is a cookie that was either received from the client as a part of the request, or was added with
set-cookie.
A cookie can be deleted before or after sending out a header (see
out-header). However a cookie must be deleted prior to outputting any actual response (such as with
output_statement or
p-out for example), or the program will error out and stop.
<status> (in the optional "status" clause) is the integer variable (which can be created with optional "define") that will be -1 if the cookie did not exist, or 0 or greater if it did.
The same cookie name may be stored under different URL paths. You can use the optional "path" clause to specify <cookie path> to ensure the desired cookie is deleted.
Examples
delete-cookie "my_cookie"
bool is_secure = true;
delete-cookie "my_cookie" path "/path" secure is_secure
See also
Cookies (
delete-cookie get-cookie set-cookie )
SEE ALL (
documentation)
delete-json
Purpose: Delete memory associated with JSON parsing.
delete-json will delete memory associated with variable <json> created to parse a JSON string with
new-json.
See
memory_handling for more on when (not) to delete memory explicitly like this; the same rules apply as for
delete-mem.
Examples
See also
JSON (
delete-json new-json read-json )
SEE ALL (
documentation)
delete-mem
Purpose: Free memory.
delete-mem <memory> status [ define ] <status>
delete-mem frees memory allocated by Vely. Do not use it on memory allocated by any C-library functions (such as malloc(), calloc() or realloc()).
delete-mem will attempt to handle memory violations or invalid pointers, and may not
error out if there is an issue; if the error is too severe, it will error out. If it doesn't error out, you can obtain the status of freeing memory in variable <status> by using "status" clause (you can create this number variable with "define" if it does not exist). The reason for this is that freeing memory is in many cases unnecessary as Vely will automatically do so at the end of each request and problems in freeing memory will not affect doing so in the end.
If <status> is VV_OKAY, the memory has been freed successfully. If <status> is VV_ERR_MEMORY, there was a problem; most likely <string> was not a valid memory pointer allocated by any of Vely statements; or it was a bad memory pointer for any number of reasons (such as for example it was already freed once).
Examples
Allocate and free memory:
new-mem define mystr size 300
delete-mem mystr status define st
if (st != VV_OKAY) {
@Error in memory release!
}
Free memory allocated by write-string (consisting of 100 "Hello World" statements):
write-string define ws
num i;
for (i = 0; i < 100; i++) {
@Hello World
}
end-write-string
delete-mem ws
See also
Memory (
delete-mem manage-memory memory_handling new-mem resize-mem )
SEE ALL (
documentation)
delete-query
Purpose: Delete memory associated with a query.
delete-query <query name> [ skip-data ]
A named query (see clause "name" in
run-query) can be deleted with delete-query by specifying the <query name>. Deletion means that all memory allocated for a query will be freed. It means query results will be freed as well. Do not delete query if its results are still referenced afterwards.
However, if "skip-data" clause is used, then the query results will not be deleted; rather everything else will be. This means the results of
query-result statement or "output define" clause in
run-query statement will not be freed. This is useful if you want to keep the results of a query and otherwise release all of its resources. Note that the results will remain allocated even if you do not obtain them via "query-result" or "output define" clause, for instance.
Make sure not to delete anything twice. For instance, do not use
delete-mem to delete query data such as error message, column name or results, while using delete-query as well. By the same token, if you use "skip-data", you can use delete-mem to free query results later.
See
memory_handling for more on when (not) to delete memory explicitly like this; the same rules apply as for
delete-mem.
Examples
run-query @db="drop table if exists test" no-loop name drop_query
delete-query drop_query
See also
Database (
begin-transaction commit-transaction current-row database_config_file database_queries delete-query on-error prepared_statements query-result rollback-transaction run-query )
SEE ALL (
documentation)
Deploying application
You can deploy Vely applications in different ways.
Containers
Using containers (such as with
docker or
podman) is an easy, reliable and portable way of deploying a Vely application. See
containerize_application.
This method is also applicable for deployment to
Internet of Things (IOT) systems, such as for example
Fedora IOT or with
Raspberry PI Operating Systems often used for devices. For IOT, Vely can be deployed both on servers and devices.
Installing from source
You can also deploy a Vely application by installing it from source; see
application_setup. After installation, you may delete the source code and remove generated C code:
Installing from binary
The binary for your application can be copied to another computer with the same Operating System and with Vely installed, provided the Operating System has compatible libraries; in practice this means the same major Operating System version and the same or higher minor version. The same is true for Vely version; you must have the same major Vely version and the same or higher minor version.
You must install Vely first on the target machine and then create the application, for instance:
sudo vf -i -u $(whoami) <app name>
Next you must copy the binaries for your application from source to target, to the same location. Assuming application name is <app name>, the binaries are:
#Fast CGI binary:
/var/lib/vv/bld/<app name>.fcgi
#command line and CGI binary:
/var/lib/vv/bld/<app name>
For an application that does not use any database(s) and does not need any additional files, this is enough to deploy the application to a target machine.
If you use database(s), you must install such database(s) and migrate any needed data, and copy the
database_config_files, meaning the following directory:
/var/lib/vv/<app name>/app/db
If you use SQLite, you can just copy the database file as it is portable; generally such file would be in the application's home directory. Since the application's home directory is:
/var/lib/vv/<app name>/app
you would copy any custom files and directories you have created and that your application may need. Note that all files and directories copied must be owned by the application owner, i.e. the user used in application creation command above (see
vf).
Multi-user and multi-application environment
Each application owner (i.e. the user used in application creation with -u flag, see
vf) can have any number of applications. At the same time, different application owners can create any number of applications of their own; the application directory for each application is owned by its respective owner with 700 permissions, meaning different users cannot access application files of other owners (see
how_vely_works). Similarly, application servers (i.e. FastCGI servers) run under the application owner permissions, and can only be managed by its owner.
See also
General (
deploying_application how_vely_works quality_control rename_files SELinux vely vely_architecture vely_removal vf vv why_C_and_Vely )
SEE ALL (
documentation)
derive-key
Purpose: Derive a key.
derive-key [ define ] <key> from <source> length <length> \
[ binary [ <binary> ] ] \
[ from-length <source length> ] \
[ digest <digest algorithm> ] \
[ salt <salt> [ salt-length <salt length> ] ] \
[ iterations <iterations> ]
derive-key derives <key> (which can be created with optional "define") from string <source> in "from" clause. <key> is
allocated memory. If <source length> in "from-length" clause is specified, exactly <source length> bytes of <source> are used, regardless of null bytes. Otherwise, the length of a null-terminated <source> string is used as the number of bytes.
The length of derived key is given by <length> in "length" clause. The method for key generation is PBKDF2. By default the digest used is "SHA256". You can use a different <digest algorithm> in "digest" clause (for example "SHA3-256"). To see a list of available digests:
openssl list -digest-algorithms
The optional salt for key derivation can be given with <salt> in "salt" clause. If "salt-length" clause is not used, then the salt is null-terminated, otherwise its length is <salt length>.
The number of iterations is given by <iterations> in "iterations" clause. The default is 1000 per
RFC 8018, though depending on your needs and the quality of <source> you may choose a different value.
By default, the derived key is produced as a null-terminated string in a hexadecimal form, where each byte is encoded as two-character hexadecimal characters, so its length is 2*<length>. If "binary" clause is used without optional boolean expression <binary>, or if <binary> evaluates to true, then the output is a binary string of <length> bytes, and a null byte is placed after it.
Key derivation is often used when storing password-derivatives in the database (with salt), and also for symmetrical key generation.
Examples
Derived key is in variable "mk":
random-string to define rs9 length 16
derive-key define mk from "clave secreta" digest "sha-256" salt rs9 salt-length 10 iterations 2000 length 16
See also
Encryption (
decrypt-data derive-key encrypt-data hash-string random-crypto random-string )
SEE ALL (
documentation)
Dot
Purpose: Write a single line of C code when conflicting with Vely code.
If Vely code conflicts with C code, you can use the dot (".") statement to write a single line of C code - so each line of such code must be prefixed with "." (a dot). Note there doesn't have to be a space after the dot.
Examples
In this case, valid C expression exec-sql would be confused for "exec-sql" Vely statement:
num exec = 3;
num sql = 2;
printf ("%lld\n",
exec-sql);
You can simply prefix the offending code with the dot:
num exec = 3;
num sql = 2;
printf ("%lld\n",
.exec-sql);
See also
Language (
dot inline_code statement_APIs syntax_highlighting unused-var )
SEE ALL (
documentation)
encode-base64
Purpose: Base64 encode.
encode-base64 <data> to [ define ] <output data> \
[ input-length <input length> ] \
[ output-length [ define ] <output length> ]
encode-base64 will encode <data> into base64 string <output data>, which is
allocated memory.
If "input-length" clause is used, then <input length> is the number of bytes encoded, otherwise <data> is treated as a null-terminated string and its length determined.
The result is stored in <output data> (in "to" clause), which can be created with optional "define". The length of the encoded string can be obtained via "output-length" clause in <output length>, which can be created with optional "define".
Base64-encoded strings are often used where binary data needs to be in a format that complies with certain text-based protocols, such as in attaching documents in email, or embedding binary documents (such as "JPG" files for example) in web pages, such as with images with "data:image/jpg;base64..." specified, etc.
Examples
Simple example that encodes a string and then decodes, and checks if they match:
char dt[]=" oh well ";
encode-base64 dt to define out_dt
decode-base64 out_dt to define new_dt
if (!strcmp (dt, new_dt)) {
@Success!
} else {
@Failure!
}
In the next example, "input-length" and "output-length" clauses are used, and only a partial of the input string is encoded. The length of the final decoded string, as well as the string itself is checked against the original:
char dt[]=" oh well ";
6
encode-base64 dt input-length 6 to define out_dt output-length define out_len
decode-base64 out_dt input-length out_len to define new_dt output-length define new_len
if (new_len != 6) {
@Failure!
} else {
@Success!
}
if (!strncmp(dt,new_dt,6)) {
@Success!
} else {
@Failure!
}
See also
Base64 (
decode-base64 encode-base64 )
SEE ALL (
documentation)
encode-hex
Purpose: Encode data into hexadecimal string.
encode-hex <data> to [ define ] <output> \
[ input-length <input length> ] \
[ output-length [ define ] <output length> ] \
[ prefix <prefix> ]
encode-hex will encode string <data> to hexadecimal string <output> given in "to" clause, which is null-terminated and consists of digits "0"-"9" and letters "a"-"f". <output> is
allocated memory.
<data> can contain null-bytes and in general is any binary data, the length of which is given by <input length> in "input-length" clause. The output string <output> is allocated and its length is given in <output length> in "output-length" clause. If you wish to prefix the output with a null-terminated string <prefix>, you can specify it in "prefix" clause.
"output-length" clause is optional, and so is "prefix" (in which case no prefix is prepended). "input-length" clause is also optional, and if not specified, <input length> is taken as the string length of <data>, which is in that case assumed to be null-terminated.
Optional "define" subclause can be used to create <output> string and a number variable <output length> if they do not already exist.
Examples
Create hexadecimal string from binary data "mydata" of length 7, prefixed with string "\\\\x" (which is typically needed for PostgreSQL binary input to queries). The output string "hexout" is created and so is its length "olen" variable of type "num":
char *mydata = "\x00""A""\x00""\xF""AB""\x00""\x04";
encode-hex mydata to define hexout input-length 7 output-length define olen prefix "\\\\x"
The value of "hexout" will be:
See also
Hex encoding (
decode-hex encode-hex )
SEE ALL (
documentation)
encode-url
Purpose: URL-encode string.
encode-url <string> to [ define ] <encoded string> \
[ output-length [ define ] <encoded length> ] \
[ input-length <length> ]
encode-url URL-encodes <string> and stores the result in <encoded string> which may be created with optional "define". <encoded string> is
allocated memory.
Number <length> in the optional "input-length" clause lets you specify the number of bytes in <string> that will be encoded - if not specified or negative, it is the string length. "output-length" clause (also optional) lets you get the length of the encoded string in <encoded length>, which can be created with optional "define".
All bytes except alphanumeric and those from "-._~" (i.e. dash, dot, underscore and tilde) are encoded.
Examples
In this example, a string "str" is URL encoded and the result is in a "result" string variable, and its length is in "len_of_result" variable:
char str[]=" x=y?z& ";
encode-url str to define result output-length define len_of_result
The "result" is "%20%20x%3Dy%3Fz%26%20%20" and "len_of_result" is 24.
See also
URL encoding (
decode-url encode-url )
SEE ALL (
documentation)
encode-web
Purpose: Web(HTML)-encode string.
encode-web <string> to [ define ] <encoded string> \
[ output-length [ define ] <encoded length> ] \
[ input-length <length> ]
encode-web encodes <string> so it can be used in a HTML-like markup text (such as a web page or an XML/XHTML document), and stores the result in <encoded string> which may be created with optional "define". <encoded string> is
allocated memory.
Optional "output-length" clause lets you get the length of the encoded string in <encoded length>, which can be created with optional "define". You can encode only the first <length> bytes, given by an "input-length" clause.
Examples
In this example, a string "str" will be web-encoded and the result is in "result" variable, with its length in "len_of_result" variable:
char str[]=" x<y>z&\"' ";
encode-web str to define result output-length define len_of_result
The "result" is " x<y>z&"' " and "len_of_result" is 33.
See also
Web encoding (
decode-web encode-web )
SEE ALL (
documentation)
encrypt-data
Purpose: Encrypt data.
encrypt-data <data> to [ define ] <result> \
[ input-length <input length> ] \
[ output-length [ define ] <output length> ] \
[ binary [ <binary> ] ] \
( password <password> \
[ salt <salt> [ salt-length <salt length> ] ] \
[ iterations <iterations> ] \
[ cipher <cipher algorithm> ] \
[ digest <digest algorithm> ]
[ cache ]
[ clear-cache <clear cache> ) \
[ init-vector <init vector> ]
encrypt-data encrypts <data> and stores the ciphertext to <result> specified by "to" clause, which can be created with optional "define".
Cipher and digest
By default, AES-256-CBC encryption and SHA256 hashing is used. You can however specify different cipher and digest algorithms with <cipher algorithm> (in "cipher" clause) and <digest algorithm> (in "digest" clause) as long as
OpenSSL supports them, or you have added them to OpenSSL. You can see the available ones by using:
openssl list -cipher-algorithms
openssl list -digest-algorithms
Note that the default algorithms will typically suffice. If you use different algorithms, you should have a specific reason. If you use a specific cipher and digest for encoding, you must use the same for decoding. The key derivation method is PBKDF2.
Data to be encrypted
If "input-length" clause is missing, then <data> is considered to be a null-terminated string and the number of bytes encrypted is its length. If "input-length" clause is used, then <input length> bytes are encrypted, regardless of whether <data> is a null-terminated string or not.
Password
String <password> (in "password" clause) is the password used to encrypt and it must be a null-terminated string.
Salt
Optional <salt> (in "salt" clause) is the salt used in Key Derivation Function (KDF) when an actual symmetric encryption key is created. If <salt length> (in "salt-length" clause) is not specified, then the salt is null-terminated, otherwise it is a binary value of length <salt length>. See
random-string or
random-crypto for generating a random salt. If you use the "salt" clause, then you must use the exact same <salt> when data is decrypted with
decrypt-data - typically salt values are stored or transmitted unencrypted.
Iterations
The number of iterations used in producing a key is specified in <iterations> in optional "iterations" clause. The default is 1000 per
RFC 8018, though depending on your needs and the quality of password you may choose a different value.
Initialization vector (IV)
Different encrypted messages should have a different IV value, which is specified with <init vector> in the "init-vector" clause. See
random-string or
random-crypto for generating IV values. The decrypting side must use the same IV value to decrypt the message. Just like salt, IV is not a secret and is transmitted in plain text. IV is generally a binary value and each cipher algorithm may require a certain number of bytes.
Encrypted data
The encrypted data is stored in <result> (in "to" clause), which you can create with optional "define". <result> is
allocated memory. The encrypted data can be a binary data (if "binary" clause is present without optional boolean expression <binary>, or if <binary> evaluates to true), which is binary-mode encryption; or if not, it will be a null-terminated string, which is character-mode encryption, consisting of hexadecimal characters (i.e. ranging from "0" to "9" and "a" to "f"). Character mode of encryption is convenient if the result of encryption should be a human readable string, or for the purposes of non-binary storage in the database.
If this is a binary-mode encryption, then "output-length" clause can be used to get the length of the binary encrypted data in <output length> , which can be created with optional "define". In any case, if used, <output length> has the length of the encrypted data, which is the exact byte count in binary mode, or the length of encrypted string in character mode (i.e. the number of character bytes excluding the terminating null byte).
Caching key
A key used to actually encrypt/decrypt data is produced by using password, salt, cipher, digest and the number of iterations. Depending on these parameters (especially the number of iterations), computing the key can be a resource intensive and lengthy operation. You can cache the key value and compute it only once (or once in a while) by using "cache" clause. If you need to recompute the key once in a while, use "clear-cache" clause. <clear cache> is a "bool" variable; the key cache is cleared if it is true, and stays if it is false. For example with encrypt-data (the same applies to decrypt-data):
bool clear;
if (q == 0) clear = true; else clear = false;
encrypt-data dt init-vector non password pwd \
salt rs salt-length 10 iterations iter to \
define dt_enc cache clear-cache clear
In this case, when "q" is 0, cache will be cleared, with values of password, salt and iterations presumably changed, and the new key is computed and then cached. In all other cases, the last computed key stays the same. Normally, with IV usage (in "init-vector" clause), there is no need to change the key often, or at all.
Note that while "cache" clause is in effect, the values for "password", "salt", "cipher", "digest" and "iterations" clauses can change without any effect. Only when "clear-cache" evaluates to "true" are those values taken into account.
Safety
Unless you are encrypting/decrypting a single message, you should always use IV in "init-vector" clause. Its purpose is to randomize the data encrypted, so that same messages do not produce the same ciphertext.
If you use salt, a random IV is created with each different salt value. However, different salt values without "cache" clause will regenerate the key, which may be computationally intensive, so it may be better to use a different IV instead for each new encryption and keep the salt value the same with the high number of iterations. In practicality this means using "cache" so that key is computed once per process with the salt, and IV changes with each message. If you need to recompute the key occasionally, use "clear-cache".
Each cipher/digest combination carries separate recommendations about the usage of salt, IV and the number of iterations. Please consult their documentation for more details.
Examples
In the following example, the data is encrypted, and then decrypted, producing the very same data:
char *orig_data="something to encrypt!";
"res"
encrypt-data orig_data password "mypass" to define res
"dec_data"
decrypt-data res password "mypass" to define dec_data
if (!strcmp (orig_data, dec_data)) {
@Success!
} else {
@Failure!
}
A more involved example below encrypts specific number of bytes (6 in this case).
random-string is used to produce salt. The length of data to encrypt is given with "input-length" clause. The encrypted data is specified to be "binary" (meaning not as a human-readable string), so the "output-length" of such binary output is specified. The decryption thus uses "input-length" clause to specify the length of data to decrypt, and also "output-length" to get the length of decrypted data. Finally, the original data is compared with the decrypted data, and the length of such data must be the same as the original (meaning 6):
6
char *orig_data="something to encrypt!";
8
random-string to define newsalt length 8 binary
"encrypted_len"
encrypt-data orig_data input-length 6 output-length define encrypted_len password "mypass" salt newsalt to define res binary
"encrypted_len"
"decrypted_len"
decrypt-data res output-length define decrypted_len password "mypass" salt newsalt to define dec_data input-length encrypted_len binary
66
if (!strncmp(orig_data,dec_data, 6) && decrypted_len == 6) {
@Success!
} else {
@Failure!
}
An example of using different algorithms:
encrypt-data "some data!" password "mypwd" salt rs1 to encd1 cipher "camellia-256-cfb1" digest "sha3-256"
decrypt-data encd1 password "mypwd" salt rs1 to decd1 cipher "camellia-256-cfb1" digest "sha3-256"
See also
Encryption (
decrypt-data derive-key encrypt-data hash-string random-crypto random-string )
SEE ALL (
documentation)
Error code
Many Vely statements return status with VV_ERR_... error codes, which are generally descriptive to a point. Such status is not as detailed as the operating system "errno" variable, however you can use "errno" clause in
get-req statement to obtain the last known errno value from aforementioned statements. You should obtain this value as soon as possible after the statement because another statement may set it afterwards.
In the following example, a directory is attempted to be deleted via
delete-file, which will fail with VV_ERR_DELETE - however you can get a more specific code via "errno" (which in this case is "21", or "EISDIR", which means that it cannot delete a directory with this statement):
delete-file "some_directory" status define stc
if (stc == VV_ERR_DELETE) {
get-req errno to define e
@Cannot delete file
pf-out "Error %lld\n", e
}
Note that with some VV_ERR_... codes, the "errno" clause in get-req may return 0. This means the error was detected by Vely and not reported by the operating system.
See also
Error handling (
error_code error_handling on-error report-error )
SEE ALL (
documentation)
Error handling
When your program errors out
"Erroring out" means your program has encountered insurmountable difficulty and it will end. For instance, it could be out of memory, or the database is permanently down and connection cannot be re-established. These are errors that cannot be handled. If your program is started with
vf, it may be automatically restarted.
When report-error is called
You can report an error in your Vely code with
report-error, after which:
- if it is started with vf (such as a FastCGI application), the program will not exit, but rather it will skip the rest of the request and move on to handle the next request, or
- if it is a command_line application, it will exit.
When there is a problem in Vely
If there is a fatal internal error (i.e. error in Vely code itself that cannot be handled), it will be caught by Vely, and the program will end. If your program is started with
vf, it may be automatically restarted.
Logging the error
Regardless of the type of error and regardless of whether the program exits or not, the error is logged and the program stack with full source code lines (see
vv for including debug information) will be written to backtrace file (use -e option of
vv to obtain its location).
You can see the list of last N errors (and the location of file containing backtrace for them) by using
vv, for instance to see the last 3 errors:
See also
Error handling (
error_code error_handling on-error report-error )
SEE ALL (
documentation)
Example: cookies
A value is entered in the browser and saved as a cookie, then read back later. This example:
- displays a web form,
- when a form is submitted, uses the input to set a cookie in response,
- in a separate page, gets the cookie value and displays it.
This is the basic mechanism often used in saving web application states and session information.
In a nutshell: web browser; Apache; Unix sockets; 1 source files, 49 lines of code.
Screenshots of application
A web form is displayed where the user can enter a name. Clicking "Submit" sends it to the server:
A reply page when the server receives the information and sets the cookie in the browser:
A request sent to the server will now have the cookie, which the server code will read and display back:
Setup prerequisites
Install Vely - you can use standard packaging tools such as
apt,
dnf,
pacman or
zypper.
Because it is used in this example, you will need to install
Apache as a web server.
After installing Vely, turn on syntax highlighting in vim if you're using it:
Get the source code
The source code is a part of Vely installation. It is a good idea to create a separate source code directory for each application (and you can name it whatever you like). In this case, unpacking the source code will do that for you:
tar xvf $(vv -o)/examples/cookies.tar.gz
cd cookies
Setup application
The very first step is to create an application. The application will be named "cookies", but you can name it anything (if you do that, change it everywhere). It's simple to do with
vf:
sudo vf -i -u $(whoami) cookies
This will create a new application home (which is "/var/lib/vv/cookies") and do the application setup for you. Mostly that means create various subdirectories in the home folder, and assign them privileges. In this case only current user (or the result of "whoami" Linux command) will own those directories with 0700 privileges; it means a secure setup.
Build application
Use
vv utility to make the application:
Start your application server
To start the application server for your web application use
vf FastCGI process manager. The application server will use a Unix socket to communicate with the web server (i.e. a reverse-proxy):
This will start 3 daemon processes to serve the incoming requests. You can also start an adaptive server that will increase the number of processes to serve more requests, and gradually reduce the number of processes when they're not needed:
See
vf for more options to help you achieve best performance.
If you want to stop your application server:
Setup web server
This shows how to connect your application listening on a Unix socket (started with
vf) to Apache web server.
- Step 1:
To setup Apache as a reverse proxy and connect your application to it, you need to enable FastCGI proxy support, which generally means "proxy" and "proxy_fcgi" modules - this is done
only once:
- For Debian (like Ubuntu) and OpenSUSE systems you need to enable proxy and proxy_fcgi modules:
sudo a2enmod proxy
sudo a2enmod proxy_fcgi
- For Fedora systems (or others like Archlinux) enable proxy and proxy_fcgi modules by adding (or uncommenting) LoadModule directives in the Apache configuration file - the default location of this file on Linux depends on the distribution. For Fedora (such as RedHat), Archlinux:
sudo vi /etc/httpd/conf/httpd.conf
For OpenSUSE:
sudo vi /etc/apache2/httpd.conf
Add this to the end of the file:
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
- Step 2:
Edit the Apache configuration file:
- For Debian (such as Ubuntu):
sudo vi /etc/apache2/apache2.conf
- for Fedora (such as RedHat), Archlinux:
sudo vi /etc/httpd/conf/httpd.conf
- and for OpenSUSE:
sudo vi /etc/apache2/httpd.conf
Add this to the end of file ("/cookies" is the application path (see
request_URL) and "cookies" is your application name):
ProxyPass "/cookies" unix:///var/lib/vv/cookies/sock/sock|fcgi://localhost/cookies
- Step 3:
Finally, restart Apache. On Debian systems (like Ubuntu) or OpenSUSE:
sudo systemctl restart apache2
On Fedora systems (like RedHat) and Arch Linux:
sudo systemctl restart httpd
Note: you must not have any other URL resource that starts with "/cookies" (such as for example "/cookies.html" or "/cookies_something" etc.) as the web server will attempt to pass them as a reverse proxy request, and they will likely not work. If you need to, you can change the application path to be different from "/cookies", see
request_URL.
Access application server from the browser
Use the following URL(s) to access your application server from a client like browser (see
request_URL). Use actual IP or web address instead of 127.0.0.1 if different.
#Enter cookie
http://127.0.0.1/cookies/cookies?action=enter_cookie
#Query entered cookie
http://127.0.0.1/cookies/cookies?action=query_cookie
Note: if your server is on the Internet and it has a firewall, you may need to allow HTTP traffic - see
ufw,
firewall-cmd etc.
Files
You are now done with the example! What follows are the source files in this project so you can examine how it works:
Working with cookies (cookies.vely)
Usage of cookies is simple -
get-cookie is used to obtain the cookie from a web client (i.e. a browser), and
set-cookie to set the cookie in the browser when the response is sent back.
Three sub
requests (based on "action" input parameter) allow user to enter a value that is sent back to the server (see "enter_cookie" as the "action" parameter), which the server then sends back to the browser in response (see "save_cookie"), and finally with "query_cookie" this value can be obtained and displayed again. This demonstrates how server-side software uses cookies to manipulate and obtain the client state.
2.0
2018
#include "vely.h"
void cookies()
{
input-param action
if (!strcmp (action, "enter_cookie")) {
out-header default
@<h2>Enter your name</h2>
@<form action="<<p-path>>/cookies" method="POST">
@ <input type="hidden" name="action" value="save_cookie">
@ <label for="cookie_value">Your name:</label><br/>
@ <input type="text" name="cookie_value" value=""><br/>
@ <br/>
@ <input type="submit" value="Submit">
@</form>
} else if (!strcmp (action, "save_cookie")) {
input-param cookie_value
get-time to define cookie_expiration year 1 timezone "GMT"
set-cookie "customer_name" = cookie_value expires cookie_expiration path "/"
out-header default
@Cookie sent to browser!
@<hr/>
} else if (!strcmp (action, "query_cookie")) {
get-cookie define name="customer_name"
out-header default
@Customer name is <<p-web name>>
@<hr/>
} else {
out-header default
@Unrecognized action<hr/>
}
}
See also
Examples (
example_cookies example_create_table example_docker example_file_manager example_form example_hash example_hello_world example_json example_multitenant_SaaS examples example_sendmail example_shopping example_stock example_utility example_write_report )
SEE ALL (
documentation)
Example: create table
Example of manipulating tables via SQL. Table is
- dropped,
- created,
- data inserted,
- then queried,
- and finally the table is dropped.
In a nutshell: PostgreSQL; command line; web browser; Nginx; Unix sockets; 2 source files, 44 lines of code.
Screenshots of application
The web output from creating a table, inserting data, selecting and dropping it:
The same output, only when the program runs from the command line. Set VV_SILENT_HEADER to yes to skip the header output:
Setup prerequisites
Install Vely - you can use standard packaging tools such as
apt,
dnf,
pacman or
zypper.
Because they are used in this example, you will need to install
Nginx as a web server and
PostgreSQL as a database.
After installing Vely, turn on syntax highlighting in vim if you're using it:
Get the source code
The source code is a part of Vely installation. It is a good idea to create a separate source code directory for each application (and you can name it whatever you like). In this case, unpacking the source code will do that for you:
tar xvf $(vv -o)/examples/create_table.tar.gz
cd create_table
Setup application
The very first step is to create an application. The application will be named "create_table", but you can name it anything (if you do that, change it everywhere). It's simple to do with
vf:
sudo vf -i -u $(whoami) create_table
This will create a new application home (which is "/var/lib/vv/create_table") and do the application setup for you. Mostly that means create various subdirectories in the home folder, and assign them privileges. In this case only current user (or the result of "whoami" Linux command) will own those directories with 0700 privileges; it means a secure setup.
Setup the database
Before any coding, you need some place to store the information used by the application. First, you will create PostgreSQL database "db_create_table". You can change the database name, but remember to change it everywhere here. And then, you will create database objects in the database.
Note the example here uses peer-authentication, which is the default on all modern PostgreSQL installations - this means the database user name is the same as the Operating System user name.
Execute the following in PostgreSQL database as root (using
psql utility):
echo "create user $(whoami);
create database db_create_table with owner=$(whoami);
grant all on database db_create_table to $(whoami);
\q
" | sudo -u postgres psql
Next, login to database db_create_table and create the database objects for the application:
psql -d db_create_table -f setup.sql
Connect Vely to a database
In order to let Vely know where your database is and how to log into it, you will create
database_config_file named "db_create_table". This name doesn't have to be "db_create_table", rather it can be anything - this is the name used in actual database statements in source code (like
run-query), so if you change it, make sure you change it everywhere. Create it:
echo "user=$(whoami) dbname=db_create_table" > db_create_table
The above is a standard
postgres connection string that describes the login to the database you created. Since Vely uses native PostgreSQL connectivity, you can specify any connection string that your database lets you.
Build application
Use
vv utility to make the application:
vv -q --db=postgres:db_create_table
Note usage of --db option to specify PostgreSQL database and the database configuration file name.
Start your application server
To start the application server for your web application use
vf FastCGI process manager. The application server will use a Unix socket to communicate with the web server (i.e. a reverse-proxy):
This will start 3 daemon processes to serve the incoming requests. You can also start an adaptive server that will increase the number of processes to serve more requests, and gradually reduce the number of processes when they're not needed:
See
vf for more options to help you achieve best performance.
If you want to stop your application server:
Setup web server
This shows how to connect your application listening on a Unix socket (started with
vf) to Nginx web server.
- Step 1:
You will need to edit the Nginx configuration file. For Ubuntu and similar:
sudo vi /etc/nginx/sites-enabled/default
while on Fedora and other systems it might be at:
sudo vi /etc/nginx/nginx.conf
Add the following in the "server {}" section ("/create_table" is the application path (see
request_URL) and "create_table" is your application name):
location /create_table { include /etc/nginx/fastcgi_params; fastcgi_pass unix:///var/lib/vv/create_table/sock/sock; }
- Step 2:
Finally, restart Nginx:
sudo systemctl restart nginx
Note: you must not have any other URL resource that starts with "/create_table" (such as for example "/create_table.html" or "/create_table_something" etc.) as the web server will attempt to pass them as a reverse proxy request, and they will likely not work. If you need to, you can change the application path to be different from "/create_table", see
request_URL.
Access application server from the browser
Use the following URL(s) to access your application server from a client like browser (see
request_URL). Use actual IP or web address instead of 127.0.0.1 if different.
#Create, use and drop table, show output
http://127.0.0.1/create_table/create_table
Note: if your server is on the Internet and it has a firewall, you may need to allow HTTP traffic - see
ufw,
firewall-cmd etc.
Access application server from command line
To access your application server from command line (instead through web browser/web server), use "cgi-fcgi" to see the application response:
export REQUEST_METHOD=GET
export SCRIPT_NAME='/create_table'
export PATH_INFO='/create_table'
export QUERY_STRING=''
cgi-fcgi -connect /var/lib/vv/create_table/sock/sock /
Note: to suppress output of HTTP headers, add this before running "cgi-fcgi":
export VV_SILENT_HEADER=yes
If you need to, you can also run your application as a
CGI program.
Run program from command line
Execute the following to run your application from command line (as a command-line utility):
export REQUEST_METHOD=GET
export SCRIPT_NAME='/create_table'
export PATH_INFO='/create_table'
export QUERY_STRING=''
/var/lib/vv/bld/create_table/create_table
Note: to suppress output of HTTP headers, add this before running /var/lib/vv/bld/create_table/create_table program:
export VV_SILENT_HEADER=yes
Note: if running your program as a command-line utility is all you want, you don't need to run an application server.
Files
You are now done with the example! What follows are the source files in this project so you can examine how it works:
Setup database objects (setup.sql)
Create a table used in the example:
create table if not exists my_table (col1 bigint);
Creating, using and dropping a table (create_table.vely)
DDL (Data Definition Language), DML (Data Definition Language) and SELECT SQL is used in this example. A table is dropped (if it exists), created, data inserted, queried, and then table is dropped again:
2.0
2018
#include "vely.h"
void create_table()
{
out-header default
run-query @db_create_table = "drop table if exists my_table" error define err error-text define err_text no-loop
if (strcmp (err, "0")) {
report-error "Trouble dropping table, error [%s], error text [%s]", err, err_text
}
@Table dropped!<br/>
run-query @db_create_table = "create table if not exists my_table (my_number bigint)" error err error-text err_text no-loop
if (strcmp (err, "0")) {
report-error "Trouble creating table, error [%s], error text [%s]", err, err_text
}
@Table created!<br/>
run-query @db_create_table="insert into my_table (my_number) values ('%s'), ('%s'), ('%s')" : "100", "200", "400" affected-rows define nrows
end-query
@Added <<pf-out "%lld", nrows>> rows!<br/>
@Getting data we inserted:<br/>
run-query @db_create_table="select my_number from my_table" output my_number
@I got number <<query-result my_number>>!<br/>
end-query
run-query @db_create_table = "drop table if exists my_table" error err error-text err_text no-loop
if (strcmp (err, "0")) {
report-error "Trouble creating table, error [%s], error text [%s]", err, err_text
}
@Table dropped!<br/>
}
See also
Examples (
example_cookies example_create_table example_docker example_file_manager example_form example_hash example_hello_world example_json example_multitenant_SaaS examples example_sendmail example_shopping example_stock example_utility example_write_report )
SEE ALL (
documentation)
Example: docker
The following is a bash script that will create a base Vely image as well as a base image for a Vely application, and setup Apache server and MariaDB database. To run this example, you must have Docker, Apache and MariaDB installed on Ubuntu 20 and up or Red Hat 9 or up. You can use
docker or
podman (substitute podman for docker).
To run this example on Ubuntu:
tar xvf /usr/share/vely/examples/velydocker.tar.gz
cd velydocker
export DBROOTPWD="<mariadb root database pwd>"
export VV_APACHE_CONFIG="/etc/apache2/apache2.conf"
export VV_APACHE_SERVICE="apache2"
./runvelydocker
DBROOTPWD environment variable should have a MariaDB root database password (or empty if passwordless). VV_APACHE_CONFIG should be the location of Apache configuration file, and VV_APACHE_SERVICE the name of Apache service. You must have sudo privilege to run this example. The settings above are for Ubuntu/Debian, but you can change them for other distros.
The script will create a container with your application installed. You can remove the application source code from the container in "runit" script in order to distribute only the application binaries. You can then run this container on any machine.
Note that Vely demo application source code being containerized is in "docker" directory. You can replace it with your own source code; see
application_setup on building application with Vely.
Once the image is built and container started, use the following link(s) to run it, or if you can't, use curl to see the demo application response (the demo is
example_stock):
http://127.0.0.1/velydemo/docker_stock?action=add&stock_name=XYZ&stock_price=450
http://127.0.0.1/velydemo/docker_stock?action=show
Files
File Dockerfile:
FROM ubuntu:20.04
ENV TZ=America/Phoenix
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
RUN apt update
RUN apt install -y apt-transport-https ca-certificates wget sudo
RUN wget -qO - https://vely.dev//pkg/OPENPGP|sudo tee /usr/share/keyrings/vely.asc >/dev/null
ARG arch
RUN sudo bash -c "echo 'deb [signed-by=/usr/share/keyrings/vely.asc] https://vely.dev//pkg/ubuntu_20_$arch/latest ubuntu_20_$arch vely' >/etc/apt/sources.list.d/vely.list"
RUN sudo apt update
RUN sudo apt install -y vely
File velyapp.dockerfile:
FROM vely
RUN useradd -ms /bin/bash vely && echo "vely:vely" | chpasswd
RUN echo "vely ALL=(ALL) NOPASSWD: /usr/bin/vf" >> /etc/sudoers
USER vely
WORKDIR /home/vely
EXPOSE 2300
COPY ./docker/* /home/vely/
ENTRYPOINT [ "./runit" ]
File runvelydocker:
CACHE=""
set -eE -o functrace
trap 'echo "Error: status $?, $(caller), line ${LINENO}"' ERR
sudo docker build --build-arg arch=$(uname -m) $CACHE -t vely .
sudo docker volume create velyhome
sudo docker stop velyapp || true
sudo docker rmi --force velyapp || true
sudo docker build $CACHE -t velyapp -f velyapp.dockerfile .
sudo sed -i "/^ProxyPass \"\/velydemo\" .*$/d" $VV_APACHE_CONFIG
echo 'ProxyPass "/velydemo" fcgi://127.0.0.1:2300/' | sudo tee -a $VV_APACHE_CONFIG >/dev/null
sudo a2enmod proxy || true
sudo a2enmod proxy_fcgi || true
sudo service $VV_APACHE_SERVICE restart
MKDB=$(echo "create database if not exists velydb;
create user if not exists velyuser identified by 'pwd';
grant create,alter,drop,select,insert,delete,update on velydb.* to velyuser;
use velydb;
source docker/setup.sql;")
if [ "$DBROOTPWD" == "" ]; then
echo $MKDB | sudo mysql
else
echo $MKDB | sudo mysql -u root -p$DBROOTPWD
fi
mkdir -p $HOME/libvv
sudo docker run --name velyapp -d -v velyhome:/var/lib/vv --network="host" --rm velyapp
exit 0
File runit:
sudo vf -i -u $(whoami) velydemo
vv -c
vv -q --db=mariadb:db -s
vv -c
vf -f -w3 -p2300 velydemo > demout
See also
Examples (
example_cookies example_create_table example_docker example_file_manager example_form example_hash example_hello_world example_json example_multitenant_SaaS examples example_sendmail example_shopping example_stock example_utility example_write_report )
SEE ALL (
documentation)
Example: file manager
This is a file manager application.
- Files such as JPG, PDF or other can be uploaded, and each file can have a description tag.
- A list of uploaded files can be displayed.
- Each file can be viewed or downloaded, and each file can be deleted.
Files metadata is kept in the database (name, path, description, size, extension), while the files themselves are kept in Vely
file_storage.
In a nutshell: PostgreSQL; web browser; Apache; TCP sockets; 6 source files, 169 lines of code.
Screenshots of application
This is the page where application user always goes first. Here, your application will display a web link that shows all files kept in the database so far, as well as HTML form to upload a new file where you can enter its description and choose a file from your computer or device. Here's a snapshot of what the page will look like - this is a basic layout and you can change the code to style it however you wish:
Files that have been saved will be shown in a list. Next to each file name will be its size, and links to show the file (display it or download it if can't be displayed), and a link to delete the file:
When a file is about to be deleted, a confirmation is asked of user:
Once confirmed, deletion of a file is carried out:
Setup prerequisites
Install Vely - you can use standard packaging tools such as
apt,
dnf,
pacman or
zypper.
Because they are used in this example, you will need to install
Apache as a web server and
PostgreSQL as a database.
After installing Vely, turn on syntax highlighting in vim if you're using it:
Get the source code
The source code is a part of Vely installation. It is a good idea to create a separate source code directory for each application (and you can name it whatever you like). In this case, unpacking the source code will do that for you:
tar xvf $(vv -o)/examples/file_manager.tar.gz
cd file_manager
Setup application
The very first step is to create an application. The application will be named "file_manager", but you can name it anything (if you do that, change it everywhere). It's simple to do with
vf:
sudo vf -i -u $(whoami) file_manager
This will create a new application home (which is "/var/lib/vv/file_manager") and do the application setup for you. Mostly that means create various subdirectories in the home folder, and assign them privileges. In this case only current user (or the result of "whoami" Linux command) will own those directories with 0700 privileges; it means a secure setup.
Setup the database
Before any coding, you need some place to store the information used by the application. First, you will create PostgreSQL database "db_file_manager". You can change the database name, but remember to change it everywhere here. And then, you will create database objects in the database.
Note the example here uses peer-authentication, which is the default on all modern PostgreSQL installations - this means the database user name is the same as the Operating System user name.
Execute the following in PostgreSQL database as root (using
psql utility):
echo "create user $(whoami);
create database db_file_manager with owner=$(whoami);
grant all on database db_file_manager to $(whoami);
\q
" | sudo -u postgres psql
Next, login to database db_file_manager and create the database objects for the application:
psql -d db_file_manager -f setup.sql
Connect Vely to a database
In order to let Vely know where your database is and how to log into it, you will create
database_config_file named "db_file_manager". This name doesn't have to be "db_file_manager", rather it can be anything - this is the name used in actual database statements in source code (like
run-query), so if you change it, make sure you change it everywhere. Create it:
echo "user=$(whoami) dbname=db_file_manager" > db_file_manager
The above is a standard
postgres connection string that describes the login to the database you created. Since Vely uses native PostgreSQL connectivity, you can specify any connection string that your database lets you.
Build application
Use
vv utility to make the application:
vv -q --db=postgres:db_file_manager
Note usage of --db option to specify PostgreSQL database and the database configuration file name.
Start your application server
To start the application server for your web application use
vf FastCGI process manager. The application server will use a TCP socket 2310 to communicate with the web server (i.e. a reverse-proxy).
vf -p 2310 -w 3 file_manager
This will start 3 daemon processes to serve the incoming requests. You can also start an adaptive server that will increase the number of processes to serve more requests, and gradually reduce the number of processes when they're not needed:
See
vf for more options to help you achieve best performance.
If you want to stop your application server:
- SELinux and TCP ports
If you are using
SELinux and have it enabled, then you must make the TCP socket accessible to the web server and Vely:
sudo semanage port -a -t vvport_t -p tcp 2310
Without this step, the application will appear to not work.
Setup web server
This shows how to connect your application listening at TCP port 2310 (started with "-p" option in
vf) to Apache web server.
- Step 1:
To setup Apache as a reverse proxy and connect your application to it, you need to enable FastCGI proxy support, which generally means "proxy" and "proxy_fcgi" modules - this is done
only once:
- For Debian (like Ubuntu) and OpenSUSE systems you need to enable proxy and proxy_fcgi modules:
sudo a2enmod proxy
sudo a2enmod proxy_fcgi
- For Fedora systems (or others like Archlinux) enable proxy and proxy_fcgi modules by adding (or uncommenting) LoadModule directives in the Apache configuration file - the default location of this file on Linux depends on the distribution. For Fedora (such as RedHat), Archlinux:
sudo vi /etc/httpd/conf/httpd.conf
For OpenSUSE:
sudo vi /etc/apache2/httpd.conf
Add this to the end of the file:
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
- Step 2:
Edit the Apache configuration file:
- For Debian (such as Ubuntu):
sudo vi /etc/apache2/apache2.conf
- for Fedora (such as RedHat), Archlinux:
sudo vi /etc/httpd/conf/httpd.conf
- and for OpenSUSE:
sudo vi /etc/apache2/httpd.conf
Add this to the end of file ("/file_manager" is the application path, see
request_URL):
ProxyPass "/file_manager" fcgi://127.0.0.1:2310/
- Step 3:
Finally, restart Apache. On Debian systems (like Ubuntu) or OpenSUSE:
sudo systemctl restart apache2
On Fedora systems (like RedHat) and Arch Linux:
sudo systemctl restart httpd
Note: you must not have any other URL resource that starts with "/file_manager" (such as for example "/file_manager.html" or "/file_manager_something" etc.) as the web server will attempt to pass them as a reverse proxy request, and they will likely not work. If you need to, you can change the application path to be different from "/file_manager", see
request_URL.
Access application server from the browser
Use the following URL(s) to access your application server from a client like browser (see
request_URL). Use actual IP or web address instead of 127.0.0.1 if different.
#Start the file management application
http://127.0.0.1/file_manager/start
Note: if your server is on the Internet and it has a firewall, you may need to allow HTTP traffic - see
ufw,
firewall-cmd etc.
Files
You are now done with the example! What follows are the source files in this project so you can examine how it works:
Storing file information in the database (setup.sql)
The information about files, such as file names, sizes, extensions, descriptions etc. is stored in the table "files". Where will the files themselves be stored?
Well, you could store them in the database, but that's not the most optimal way. You could store them anywhere on a file system, but Vely has a very simple (and generally transparent and automatic) way of storing files, which is Vely's
file_storage. When a file is uploaded, it's automatically stored there; and you can also create files there as well.
create table if not exists files (fileName varchar(100), localPath varchar(300), extension varchar(10), description varchar(200), fileSize int, fileID bigserial primary key);
Application landing page (start.vely)
This code displays the page where application user always goes first. One thing you'll notice often in Vely code is the use of "@" to output text (in this case HTML page text) - "@" is an
output_statement, and it makes it easy and readable. In general, Vely does not use traditional API for its functionality, rather it uses
statement_APIs.
2.0
2018
#include "vely.h"
void start ()
{
out-header default
@<h2>File Manager</h2>
@To manage the uploaded files, <a href="<<p-path>>?req=list">click here.</a><br/>
@<br/>
@<form action="<<p-path>>" method="POST" enctype="multipart/form-data">
@ <input type="hidden" name="req" value="upload">
@ <label for="file_description">File description:</label><br>
@ <textarea name="filedesc" rows="3" columns="50"></textarea><br/>
@ <br/>
@ <label for="filename">File:</label>
@ <input type="file" name="file" value=""><br><br>
@ <input type="submit" value="Submit">
@</form>
}
Uploading files (upload.vely)
The next source code file is "upload.vely", which will upload a file when a user selects a file and clicks "Submit" button, which is in "start.vely" file. When you upload a file into Vely application, it's just an
input-parameter, just like it would be with any GET or POST
request parameter. In addition, Vely will automatically store the uploaded file into file directory (see
file_storage) which is optimized for speed of access, and you can either keep it there, move it elsewhere or do whatever you want. All of this make is easy to work with files.
The above code will get the information about the file uploaded (such as file description, as well as file name, location, size, extension) - which is provided by Vely as input parameters. Then, use
run-query statement to insert such information into the database table you created. From now on, the information about a file is stored in the database (and that includes the location where it was uploaded), and the actual file is in the file system. This allows fast access to files and easier management.
And finally, the message will be sent back to web client (such as a browser) that you've saved the file.
2.0
2018
#include "vely.h"
void upload ()
{
out-header default
input-param filedesc
input-param file_filename
input-param file_location
input-param file_size
input-param file_ext
VV_UNUSED (file_ext);
@<h2>Uploading file</h2>
run-query @db_file_manager="insert into files (fileName, localPath, extension, description, fileSize) values ('%s', '%s', '%s', '%s', '%s')": file_filename, file_location, file_ext, filedesc, file_size
end-query
@File <<p-web file_filename>> of size <<p-web file_size>> is stored on server at <<p-web file_location>>. File description is <<p-web filedesc>>.<hr/>
}
Listing files (list.vely)
For files that have been saved, this code shows them in a list. Next to each file name will be its size, and links to show the file (display it or download it if can't be displayed), and a link to delete the file. To produce the list of files, the table "files" is queried, and the list of files displayed as an HTML table.
2.0
2018
#include "vely.h"
void list ()
{
out-header default
@<h2>List of files</h2>
@To add a file, <a href="<<p-path>>?req=start">click here</a><br/><br/>
@<table border="1">
@<tr>
@ <td>File</td><td>Description</td><td>Size</td><td>Show</td><td>Delete</td>
@</tr>
run-query @db_file_manager="select fileName, description, fileSize, fileID from files order by fileSize desc" output fileName, description, fileSize, fileID
query-result fileName to define file_name
query-result fileID to define file_ID
query-result description to define description
query-result fileSize to define file_size
@<tr>
@ <td><<p-web file_name>></td><td><<p-web description>><td><<p-web file_size>></td>
@ <td><a href="<<p-path>>?req=download&file_id=<<p-url file_ID>>">Show</a></td>
@ <td><a href="<<p-path>>?req=delete&action=confirm&file_id=<<p-url file_ID>>">Delete</a></td>
@</tr>
end-query
@</table>
}
Downloading files (download.vely)
When a link to download a file is clicked in the list of files, the following code is called. The ID of a file requested is queried in table "files", the file path on server is found, and the file is sent to the client using
send-file statement.
2018
#include "vely.h"
void download ()
{
input-param file_id
char *local_path=NULL;
char *ext = NULL;
run-query @db_file_manager="select localPath,extension from files where fileID='%s'" output localPath, extension : file_id row-count define num_files
query-result localPath to local_path
query-result extension to ext
end-query
if (num_files != 1) {
out-header default
@Cannot find this file!<hr/>
return;
}
if (!strcmp (ext, ".jpg")) {
send-file local_path headers content-type "image/jpg"
} else if (!strcmp (ext, ".pdf")) {
send-file local_path headers content-type "application/pdf"
} else {
send-file local_path headers content-type "application/octet-stream" download
}
}
Deleting files (delete.vely)
Finally, deleting a file (by clicking on delete link in the list of files) is handled in the following code.
When a file is about to be deleted, a confirmation is asked of user - input parameter "action" is "confirm" in this case.
Once confirmed, deletion of a file is carried out; input parameter "action" is "delete" then (you can name parameter "action" anything you want).
Note that deletion is done as a transaction, only if both the record in table "files" is deleted and the actual file deleted. If the file cannot be deleted, transaction is rollback-ed.
2.0
2018
#include "vely.h"
void delete ()
{
out-header default
@<h2>Delete a file</h2>
input-param action
input-param file_id
char *file_name = NULL;
char *desc = NULL;
char *local_path = NULL;
run-query @db_file_manager="select fileName, localPath, description from files where fileID='%s'" output fileName, localPath, description : file_id
query-result fileName to file_name
query-result description to desc
query-result localPath to local_path
end-query
if (!strcmp (action, "confirm")) {
@Are you sure you want to delete file <<p-web file_name>> (<<p-web desc>>)? Click <a href="<<p-path>>?req=delete&action=delete&file_id=<<p-url file_id>>">Delete</a> or click the browser's Back button to go back.<br/>
} else if (!strcmp (action, "delete")) {
begin-transaction @db_file_manager
run-query @db_file_manager= "delete from files where fileID='%s'" : file_id error define err no-loop
if (atol(err) != 0) {
@Could not delete the file (error <<p-web err>>)
rollback-transaction @db_file_manager
} else {
delete-file local_path status define st
if (st == VV_OKAY) {
commit-transaction @db_file_manager
@File deleted. Go back to <a href="<<p-path>>?req=start">start page</a>
} else {
rollback-transaction @db_file_manager
@File could not be deleted, error <<pf-web "%lld", st>>
}
}
} else {
@Unrecognized action <<p-web action>>
}
}
See also
Examples (
example_cookies example_create_table example_docker example_file_manager example_form example_hash example_hello_world example_json example_multitenant_SaaS examples example_sendmail example_shopping example_stock example_utility example_write_report )
SEE ALL (
documentation)
Example: form
This application is a guest tracking database. The user can:
- input first and last name, which once submitted, are added to a database table,
- display web page showing the list of names queried from the database.
In a nutshell: PostgreSQL; web browser; Apache; Unix sockets; 4 source files, 86 lines of code.
Screenshots of application
Users can sign the guest book via HTML form:
When a Submit button is clicked, entered data is added to the guest book:
This shows the entire guest book:
Setup prerequisites
Install Vely - you can use standard packaging tools such as
apt,
dnf,
pacman or
zypper.
Because they are used in this example, you will need to install
Apache as a web server and
PostgreSQL as a database.
After installing Vely, turn on syntax highlighting in vim if you're using it:
Get the source code
The source code is a part of Vely installation. It is a good idea to create a separate source code directory for each application (and you can name it whatever you like). In this case, unpacking the source code will do that for you:
tar xvf $(vv -o)/examples/form.tar.gz
cd form
Setup application
The very first step is to create an application. The application will be named "form", but you can name it anything (if you do that, change it everywhere). It's simple to do with
vf:
sudo vf -i -u $(whoami) form
This will create a new application home (which is "/var/lib/vv/form") and do the application setup for you. Mostly that means create various subdirectories in the home folder, and assign them privileges. In this case only current user (or the result of "whoami" Linux command) will own those directories with 0700 privileges; it means a secure setup.
Setup the database
Before any coding, you need some place to store the information used by the application. First, you will create PostgreSQL database "db_form". You can change the database name, but remember to change it everywhere here. And then, you will create database objects in the database.
Note the example here uses peer-authentication, which is the default on all modern PostgreSQL installations - this means the database user name is the same as the Operating System user name.
Execute the following in PostgreSQL database as root (using
psql utility):
echo "create user $(whoami);
create database db_form with owner=$(whoami);
grant all on database db_form to $(whoami);
\q
" | sudo -u postgres psql
Next, login to database db_form and create the database objects for the application:
psql -d db_form -f setup.sql
Connect Vely to a database
In order to let Vely know where your database is and how to log into it, you will create
database_config_file named "db_form". This name doesn't have to be "db_form", rather it can be anything - this is the name used in actual database statements in source code (like
run-query), so if you change it, make sure you change it everywhere. Create it:
echo "user=$(whoami) dbname=db_form" > db_form
The above is a standard
postgres connection string that describes the login to the database you created. Since Vely uses native PostgreSQL connectivity, you can specify any connection string that your database lets you.
Build application
Use
vv utility to make the application:
vv -q --db=postgres:db_form
Note usage of --db option to specify PostgreSQL database and the database configuration file name.
Start your application server
To start the application server for your web application use
vf FastCGI process manager. The application server will use a Unix socket to communicate with the web server (i.e. a reverse-proxy):
This will start 3 daemon processes to serve the incoming requests. You can also start an adaptive server that will increase the number of processes to serve more requests, and gradually reduce the number of processes when they're not needed:
See
vf for more options to help you achieve best performance.
If you want to stop your application server:
Setup web server
This shows how to connect your application listening on a Unix socket (started with
vf) to Apache web server.
- Step 1:
To setup Apache as a reverse proxy and connect your application to it, you need to enable FastCGI proxy support, which generally means "proxy" and "proxy_fcgi" modules - this is done
only once:
- For Debian (like Ubuntu) and OpenSUSE systems you need to enable proxy and proxy_fcgi modules:
sudo a2enmod proxy
sudo a2enmod proxy_fcgi
- For Fedora systems (or others like Archlinux) enable proxy and proxy_fcgi modules by adding (or uncommenting) LoadModule directives in the Apache configuration file - the default location of this file on Linux depends on the distribution. For Fedora (such as RedHat), Archlinux:
sudo vi /etc/httpd/conf/httpd.conf
For OpenSUSE:
sudo vi /etc/apache2/httpd.conf
Add this to the end of the file:
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
- Step 2:
Edit the Apache configuration file:
- For Debian (such as Ubuntu):
sudo vi /etc/apache2/apache2.conf
- for Fedora (such as RedHat), Archlinux:
sudo vi /etc/httpd/conf/httpd.conf
- and for OpenSUSE:
sudo vi /etc/apache2/httpd.conf
Add this to the end of file ("/form" is the application path (see
request_URL) and "form" is your application name):
ProxyPass "/form" unix:///var/lib/vv/form/sock/sock|fcgi://localhost/form
- Step 3:
Finally, restart Apache. On Debian systems (like Ubuntu) or OpenSUSE:
sudo systemctl restart apache2
On Fedora systems (like RedHat) and Arch Linux:
sudo systemctl restart httpd
Note: you must not have any other URL resource that starts with "/form" (such as for example "/form.html" or "/form_something" etc.) as the web server will attempt to pass them as a reverse proxy request, and they will likely not work. If you need to, you can change the application path to be different from "/form", see
request_URL.
Access application server from the browser
Use the following URL(s) to access your application server from a client like browser (see
request_URL). Use actual IP or web address instead of 127.0.0.1 if different.
#Display form
http://127.0.0.1/form/form_display
#Show data entered
http://127.0.0.1/form/display_data
Note: if your server is on the Internet and it has a firewall, you may need to allow HTTP traffic - see
ufw,
firewall-cmd etc.
Files
You are now done with the example! What follows are the source files in this project so you can examine how it works:
Setup database objects (setup.sql)
Create table to hold the first and last name of the guest:
create table if not exists names (firstName varchar(30), lastName varchar(40));
Web form (form_display.vely)
This is a simple HTML web form where first and last name is collected and sent to the server via POST mechanism. Note the path segment after the application path (set with
p-path), which is "/form_post", meaning this request will be handled in source file "form_post.vely":
2.0
2018
#include "vely.h"
void form_display ()
{
out-header default
@<h2>Enter your name</h2>
@<form action="<<p-path>>/form_post" method="POST">
@ <label for="first_name">First name:</label><br>
@ <input type="text" name="first_name" value=""><br>
@ <label for="last_name">Last name:</label><br>
@ <input type="text" name="last_name" value=""><br><br>
@ <input type="submit" value="Submit">
@ </form>
}
Submitting web form (form_post.vely)
input-param is used to obtain input parameters sent here from the web form created in "form_display.vely". The data is inserted into the database and the status message output back to web client (i.e. browser):
2.0
2018
#include "vely.h"
void form_post ()
{
out-header default
input-param first_name
input-param last_name
run-query @db_form="insert into names(firstName, lastName) values ('%s','%s')" : first_name, last_name error define err
end-query
if (strcmp(err, "0")) {
@Could not add name, error [<<p-out err>>]
exit-request
}
@Name added to the database<hr/>
}
Display database data in table format in browser (display_data.vely)
This shows typical web output from querying a database. SELECT statement in
run-query produces a result set, which is displayed in a tabular format in the browser:
2.0
2018
#include "vely.h"
void display_data ()
{
out-header default
@<h2>List of names</h2>
@<table>
run-query @db_form="select firstName, lastName from names order by lastName,firstName" output firstName,lastName
@ <tr>
@ <td>
query-result lastName
@ </td>
@ <td>
query-result firstName
@ </td>
@ </tr>
end-query
@</table>
}
See also
Examples (
example_cookies example_create_table example_docker example_file_manager example_form example_hash example_hello_world example_json example_multitenant_SaaS examples example_sendmail example_shopping example_stock example_utility example_write_report )
SEE ALL (
documentation)
Example: hash
You will create your own hash server in this example. A REST API will enable end-user to add key/data pairs, query and delete them. The data is in memory-only; a more involved example could be constructed to persist the data in some form. This is an extremely fast, single-process hash server.
In a nutshell: web browser; Apache; REST API; Unix sockets; 3 source files, 59 lines of code.
Setup prerequisites
Install Vely - you can use standard packaging tools such as
apt,
dnf,
pacman or
zypper.
Because it is used in this example, you will need to install
Apache as a web server.
After installing Vely, turn on syntax highlighting in vim if you're using it:
Get the source code
The source code is a part of Vely installation. It is a good idea to create a separate source code directory for each application (and you can name it whatever you like). In this case, unpacking the source code will do that for you:
tar xvf $(vv -o)/examples/hash.tar.gz
cd hash
Setup application
The very first step is to create an application. The application will be named "hash", but you can name it anything (if you do that, change it everywhere). It's simple to do with
vf:
sudo vf -i -u $(whoami) hash
This will create a new application home (which is "/var/lib/vv/hash") and do the application setup for you. Mostly that means create various subdirectories in the home folder, and assign them privileges. In this case only current user (or the result of "whoami" Linux command) will own those directories with 0700 privileges; it means a secure setup.
Build application
Use
vv utility to make the application:
Start your application server
To start the application server for your web application use
vf FastCGI process manager. The application server will use a Unix socket to communicate with the web server (i.e. a reverse-proxy):
This will start 1 daemon process to serve the incoming requests.
See
vf for more options to help you achieve best performance.
If you want to stop your application server:
Setup web server
This shows how to connect your application listening on a Unix socket (started with
vf) to Apache web server.
- Step 1:
To setup Apache as a reverse proxy and connect your application to it, you need to enable FastCGI proxy support, which generally means "proxy" and "proxy_fcgi" modules - this is done
only once:
- For Debian (like Ubuntu) and OpenSUSE systems you need to enable proxy and proxy_fcgi modules:
sudo a2enmod proxy
sudo a2enmod proxy_fcgi
- For Fedora systems (or others like Archlinux) enable proxy and proxy_fcgi modules by adding (or uncommenting) LoadModule directives in the Apache configuration file - the default location of this file on Linux depends on the distribution. For Fedora (such as RedHat), Archlinux:
sudo vi /etc/httpd/conf/httpd.conf
For OpenSUSE:
sudo vi /etc/apache2/httpd.conf
Add this to the end of the file:
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
- Step 2:
Edit the Apache configuration file:
- For Debian (such as Ubuntu):
sudo vi /etc/apache2/apache2.conf
- for Fedora (such as RedHat), Archlinux:
sudo vi /etc/httpd/conf/httpd.conf
- and for OpenSUSE:
sudo vi /etc/apache2/httpd.conf
Add this to the end of file ("/hash" is the application path (see
request_URL) and "hash" is your application name):
ProxyPass "/hash" unix:///var/lib/vv/hash/sock/sock|fcgi://localhost/hash
- Step 3:
Finally, restart Apache. On Debian systems (like Ubuntu) or OpenSUSE:
sudo systemctl restart apache2
On Fedora systems (like RedHat) and Arch Linux:
sudo systemctl restart httpd
Note: you must not have any other URL resource that starts with "/hash" (such as for example "/hash.html" or "/hash_something" etc.) as the web server will attempt to pass them as a reverse proxy request, and they will likely not work. If you need to, you can change the application path to be different from "/hash", see
request_URL.
Access application server from the browser
Use the following URL(s) to access your application server from a client like browser (see
request_URL). Use actual IP or web address instead of 127.0.0.1 if different.
Note: if your server is on the Internet and it has a firewall, you may need to allow HTTP traffic - see
ufw,
firewall-cmd etc.
- Test hash server with a bash script
To test the hash server, you can use "test_hash" bash script (see source code):
This will insert 1000 key/data value pairs, query them, delete and then query again. The result is:
Keys added
Keys queried
Keys deleted
Keys queried
Hash server test successful
- Use REST API from command line
Here is the REST API for your application.
Substitute the loopback "localhost" with the IP or web address of your server.
Note that in these REST calls, the application path is "/hash", and the request name is "/server", followed by URL payload (see
request_URL).
The request method used is based on REST methodology, i.e. POST to create resource, GET to obtain, DELETE to delete it, etc. This is how you can use the API:
- Here is an URL payload to add a key/data pair; use the "add" operation (i.e. "add" is the value for "op" parameter, written as path segment "/op/add"), and (as an example) specify "key_1" for a key (written as path segment "/key/key_1") and "data_1" for data (written as path segment "/data/data_1"). The entire URL is:
The result:
- To query the hash server, use the "query" operation (i.e. "query" is the value for "op" parameter, written as path segment "/op/query"), and (as an example) specify "key_1" for a key (written as path segment "/key/key_1"):
The result:
- To delete a key/data pair, use the "delete" operation (i.e. "delete" is the value for "op" parameter, written as path segment "/op/delete"), and (as an example) specify "key_1" for a key (written as path segment "/key/key_1"):
The result:
Files
You are now done with the example! What follows are the source files in this project so you can examine how it works:
Create hash server (_startup.vely)
Hash itself (i.e. the fast-access structure used to store and search data based on a key) is created on process startup in a
startup_handler, which in this case allocates
new-hash using unmanaged memory (see
memory_handling). It means this hash will remain available across all the requests a process serves, which is exactly what a hash server needs.
#include "vely.h"
void _startup() {
manage-memory off
new-hash define h size 1024
manage-memory on
set-app process-data h
}
Hash server (server.vely)
This is the hash server. Because the data kept in hash needs to exist beyond a single request, we use unmanaged memory (see
memory_handling). This way hash data stays allocated and available for the life of the server process.
The first step is to obtain the
global_process_data via
get-app statement. This global process data is the pointer to the hash data, which has been set in
set-app in the
startup_handler (_startup.vely).
Next, you get the input parameters (see
input-param); in this case "op" (operation requested), "key" (value of a key) and "data" (value of data). Then, depending on the operation requested, the data is added, deleted and retrieved (i.e. queried). Note that because input parameter values are automatically freed when the request is finished, they are copied before being stored in the hash.
#include "vely.h"
void server() {
out-header default
manage-memory off
vely_hash *h;
get-app process-data to h
input-param op
input-param key
input-param data
if (!strcmp (op, "add")) {
copy-string key to define c_key
copy-string data to define c_data
write-hash h key c_key value c_data
@Added [<<p-out key>>]
}
else if (!strcmp (op, "delete")) {
read-hash h key (key) value define val old-key define okey delete status define st
if (st == VV_ERR_EXIST) {
@Not found [<<p-out key>>]
} else {
@Deleted [<<p-out val>>]
delete-mem val
delete-mem okey
}
}
else if (!strcmp (op, "query")) {
read-hash h key (key) value define val status define st
if (st == VV_ERR_EXIST) {
@Not found, queried [<<p-out key>>]
} else {
@Value [<<p-out val>>]
}
}
}
bash testing script (test_hash)
Test hash server by adding 1000 key/data pairs, querying to make sure the data is correct, deleting all of them and then querying to make sure they were all deleted.
#Restart hash server for this test
vf -m restart hash
#Add 1000 key/data pairs. Key value is 0,1,2... and data values are "data_0", "data_1", "data_2" etc.
for i in {1..1000}; do
if [ "$(curl -s "http:")""Added [$i]"
echo "Error adding key [$i]"
exit -1
fi
done
echo "Keys added"
#Query all 1000 keys and check that values retrieved are the correct ones.
for i in {1..1000}; do
if [ "$(curl -s "http:")""Value [data_$i]"
echo "Error querying key [$i]"
exit -1
fi
done
echo "Keys queried"
#Delete all 1000 keys
ERR="0"
for i in {1..1000}; do
if [ "$(curl -s "http:")""Deleted [data_$i]"
echo "Error deleting key [$i]"
exit -1
fi
done
echo "Keys deleted"
#Query all 1000 keys and check that values retrieved are the correct ones.
for i in {1..1000}; do
if [ "$(curl -s "http:")""Not found, queried [$i]"
echo "Error querying key [$i] after deletion."
exit -1
fi
done
echo "Keys queried"
echo "Hash server test successful"
See also
Examples (
example_cookies example_create_table example_docker example_file_manager example_form example_hash example_hello_world example_json example_multitenant_SaaS examples example_sendmail example_shopping example_stock example_utility example_write_report )
SEE ALL (
documentation)
Example: hello world
This is a simple Hello World example. It explains basics of making applications as well as tracing and debugging them.
In a nutshell: command line; web browser; Nginx; Unix sockets; 2 source files, 7 lines of code.
Screenshots of application
Hello World output:
Setup prerequisites
Install Vely - you can use standard packaging tools such as
apt,
dnf,
pacman or
zypper.
Because it is used in this example, you will need to install
Nginx as a web server.
After installing Vely, turn on syntax highlighting in vim if you're using it:
Get the source code
The source code is a part of Vely installation. It is a good idea to create a separate source code directory for each application (and you can name it whatever you like). In this case, unpacking the source code will do that for you:
tar xvf $(vv -o)/examples/hello_world.tar.gz
cd hello_world
Setup application
The very first step is to create an application. The application will be named "hello_world", but you can name it anything (if you do that, change it everywhere). It's simple to do with
vf:
sudo vf -i -u $(whoami) hello_world
This will create a new application home (which is "/var/lib/vv/hello_world") and do the application setup for you. Mostly that means create various subdirectories in the home folder, and assign them privileges. In this case only current user (or the result of "whoami" Linux command) will own those directories with 0700 privileges; it means a secure setup.
Build application
Use
vv utility to make the application:
Start your application server
To start the application server for your web application use
vf FastCGI process manager. The application server will use a Unix socket to communicate with the web server (i.e. a reverse-proxy):
This will start 3 daemon processes to serve the incoming requests. You can also start an adaptive server that will increase the number of processes to serve more requests, and gradually reduce the number of processes when they're not needed:
See
vf for more options to help you achieve best performance.
If you want to stop your application server:
Setup web server
This shows how to connect your application listening on a Unix socket (started with
vf) to Nginx web server.
- Step 1:
You will need to edit the Nginx configuration file. For Ubuntu and similar:
sudo vi /etc/nginx/sites-enabled/default
while on Fedora and other systems it might be at:
sudo vi /etc/nginx/nginx.conf
Add the following in the "server {}" section ("/hello_world" is the application path (see
request_URL) and "hello_world" is your application name):
location /hello_world { include /etc/nginx/fastcgi_params; fastcgi_pass unix:///var/lib/vv/hello_world/sock/sock; }
- Step 2:
Finally, restart Nginx:
sudo systemctl restart nginx
Note: you must not have any other URL resource that starts with "/hello_world" (such as for example "/hello_world.html" or "/hello_world_something" etc.) as the web server will attempt to pass them as a reverse proxy request, and they will likely not work. If you need to, you can change the application path to be different from "/hello_world", see
request_URL.
Access application server from the browser
Use the following URL(s) to access your application server from a client like browser (see
request_URL). Use actual IP or web address instead of 127.0.0.1 if different.
#Hello world
http://127.0.0.1/hello_world/hello
Note: if your server is on the Internet and it has a firewall, you may need to allow HTTP traffic - see
ufw,
firewall-cmd etc.
Access application server from command line
To access your application server from command line (instead through web browser/web server), use "cgi-fcgi" to see the application response:
export REQUEST_METHOD=GET
export SCRIPT_NAME='/hello_world'
export PATH_INFO='/hello'
export QUERY_STRING=''
cgi-fcgi -connect /var/lib/vv/hello_world/sock/sock /
Note: to suppress output of HTTP headers, add this before running "cgi-fcgi":
export VV_SILENT_HEADER=yes
If you need to, you can also run your application as a
CGI program.
Run program from command line
Execute the following to run your application from command line (as a command-line utility):
export REQUEST_METHOD=GET
export SCRIPT_NAME='/hello_world'
export PATH_INFO='/hello'
export QUERY_STRING=''
/var/lib/vv/bld/hello_world/hello_world
Note: to suppress output of HTTP headers, add this before running /var/lib/vv/bld/hello_world/hello_world program:
export VV_SILENT_HEADER=yes
Note: if running your program as a command-line utility is all you want, you don't need to run an application server.
Files
You are now done with the example! What follows are the source files in this project so you can examine how it works:
Hello World (hello.vely)
Hello World example outputs HTTP header and a simple message back to the browser.
The file name ("hello.vely") must match the function name implemented in it, so the function's signature is "void hello()". Call it with a URL like:
http://your-web-site/hello_world/hello
Note "/hello_world" path segment - that's the application path and by default the same as application name (see
how_vely_works and
request_URL). The following path segment is "/hello", which is the request name, and it must match the function name that implements it.
Note the "@" is used to do the output, it's the
output_statement. You'll probably use that one a lot.
In this case there's only one request file. In a real world application, there'd likely be quite a few. Vely will pick up all .vely files and automatically make them into an application. While at it, it will generate a simple request dispatcher that routes an incoming request to the appropriate code; in this case a "hello" request will be routed to your "void hello()" function.
#include "vely.h"
void hello()
{
out-header default
@Hello World!
}
Changing your code
When you change your source code and recompile, vf will automatically pick up that the executable has changed and reload (see
vf if you wish to change this behavior), making your web application live instantly. For example, change hello.vely to this:
#include "vely.h"
void hello()
{
out-header default
@Hello World! And a good day to you too!
}
and then recompile:
Now try again from the browser:
http://127.0.0.1/hello_world/hello
The result:
Generated C code, errors, tracing and debugging
Generated C code is located at:
/var/lib/vv/bld/hello_world/__hello.o.c
It's useful if you'd like to see it or debug your code.
If you are using your Vely application as a command line program, you can debug it simply by using
gdb.
Note that in order to see the debugging symbols (such as variable names), you should compile your application with --debug flag:
For FastCGI processes, you'd want to start your application as a single process, generally as few as possible, for example, stop your application first and then start as a single process:
vf -m quit hello_world
vf -w 1 hello_world
First, find out the
PID of your process:
ps -ef|grep hello_world.fcgi
Then you can load gdb with your application and attach to it - assuming the PID is 12345:
sudo gdb /var/lib/vv/bld/hello_world/hello_world.fcgi
... att 12345
To break in your code (in this case in function hello()), do this in gdb:
If you'd like to break in Vely's request dispatcher, i.e. just before any request is handled:
From then on, you can set breakpoints, continue, stop and do anything else to debug your program.
When you debug Vely applications in gdb, each Vely statement is a single execution unit. This is very useful when reporting errors, since they are reported referencing lines in .vely file, in this case hello.vely. Sometimes you'd want to use line numbers of the generated C file, in which case you'd compile with --c-lines flag:
Another way to debug your application is to use tracing (see
trace-run). To enable tracing, compile your application with --trace flag:
Trace files are in the trace directory, see
how_vely_works. In our case, trace file would be in this directory:
/var/lib/vv/hello_world/app/trace
and trace file name for the process you're debugging would be (with timestamp being of the time it's written):
trace-12345-2022-05-17-22-46-54
In addition to this, learn more about
debugging Vely applications.
See also
Examples (
example_cookies example_create_table example_docker example_file_manager example_form example_hash example_hello_world example_json example_multitenant_SaaS examples example_sendmail example_shopping example_stock example_utility example_write_report )
SEE ALL (
documentation)
Example: json
This example demonstrates parsing JSON text. The text parsed contains information about cities. JSON document includes an array of countries, which includes an array of states, each having an array of cities. The JSON data is also not fixed - some array members contain data that others don't. So the example is fairly involved.
Use of UTF8 Unicode data is also shown - some cities have such characters in their names.
JSON text comes from two different kinds of client requests: from an HTML form and also from Javascript/fetch(). The first one uses URL-query POST from a form. The second one demonstrates a request with "application/json" content type and use of
request-body to get the HTTP request body.
Each JSON data node is retrieved using built-in hash statements (see
new-json).
The JSON parsing demonstrates two ways to do it:
- First, by searching for specific elements, in this case all the countries, then all the states under a country, and then all cities under a state. This shows how to deal with a generic JSON document where structure is known but can change.
- Second, by simply traversing all the data nodes and getting each in a loop. This is useful to get all the data even if you don't know its structure and search for what interests you along the way.
In a nutshell: web browser; Nginx; Unix sockets; 5 source files, 160 lines of code.
Screenshots of application
This is the form where you can enter JSON text to be parsed:
This is the result of submitting as 'Extract all data', where all data nodes are shown, along with their normalized names (that includes hierarchy and arrays), their values and types:
The following is the output of pushing 'Extract specific data' button. Specific data nodes are found, taking into account the hierarchy of data, and their names and values displayed:
You can call Vely code from Javascript fetch() - this is the output from post.html (see Access application... on how to run it):
Setup prerequisites
Install Vely - you can use standard packaging tools such as
apt,
dnf,
pacman or
zypper.
Because it is used in this example, you will need to install
Nginx as a web server.
After installing Vely, turn on syntax highlighting in vim if you're using it:
Get the source code
The source code is a part of Vely installation. It is a good idea to create a separate source code directory for each application (and you can name it whatever you like). In this case, unpacking the source code will do that for you:
tar xvf $(vv -o)/examples/json.tar.gz
cd json
Setup application
The very first step is to create an application. The application will be named "json", but you can name it anything (if you do that, change it everywhere). It's simple to do with
vf:
sudo vf -i -u $(whoami) json
This will create a new application home (which is "/var/lib/vv/json") and do the application setup for you. Mostly that means create various subdirectories in the home folder, and assign them privileges. In this case only current user (or the result of "whoami" Linux command) will own those directories with 0700 privileges; it means a secure setup.
Build application
Use
vv utility to make the application:
Start your application server
To start the application server for your web application use
vf FastCGI process manager. The application server will use a Unix socket to communicate with the web server (i.e. a reverse-proxy):
This will start 3 daemon processes to serve the incoming requests. You can also start an adaptive server that will increase the number of processes to serve more requests, and gradually reduce the number of processes when they're not needed:
See
vf for more options to help you achieve best performance.
If you want to stop your application server:
Setup web server
This shows how to connect your application listening on a Unix socket (started with
vf) to Nginx web server.
- Step 1:
You will need to edit the Nginx configuration file. For Ubuntu and similar:
sudo vi /etc/nginx/sites-enabled/default
while on Fedora and other systems it might be at:
sudo vi /etc/nginx/nginx.conf
Add the following in the "server {}" section ("/json" is the application path (see
request_URL) and "json" is your application name):
location /json { include /etc/nginx/fastcgi_params; fastcgi_pass unix:///var/lib/vv/json/sock/sock; }
- Step 2:
Finally, restart Nginx:
sudo systemctl restart nginx
Note: you must not have any other URL resource that starts with "/json" (such as for example "/json.html" or "/json_something" etc.) as the web server will attempt to pass them as a reverse proxy request, and they will likely not work. If you need to, you can change the application path to be different from "/json", see
request_URL.
Access application server from the browser
Use the following URL(s) to access your application server from a client like browser (see
request_URL). Use actual IP or web address instead of 127.0.0.1 if different.
#Enter JSON and send for parsing
http://127.0.0.1/json/json_form
Note: if your server is on the Internet and it has a firewall, you may need to allow HTTP traffic - see
ufw,
firewall-cmd etc.
Copy the contents of file cities.json (see under FILES) to a text area in the form.
- Run from Javascript via fetch method
You can also test JSON parsing via Javascript/fetch mechanism. First, copy the file to your web server in a separate directory:
- For Apache or older versions of Nginx:
sudo mkdir /var/www/html/velytest
sudo cp post.html /var/www/html/velytest
Test it (use your web address instead of 127.0.0.1 if not testing locally):
http://127.0.0.1/velytest/post.html
- For newer versions of Nginx:
sudo mkdir /usr/share/nginx/html/velytest
sudo cp post.html /usr/share/nginx/html/velytest
Test it (use your web address instead of 127.0.0.1 if not testing locally):
http://127.0.0.1/velytest/post.html
Files
You are now done with the example! What follows are the source files in this project so you can examine how it works:
JSON document (cities.json)
This is the JSON document that you enter in the form for parsing. It contains countries, states and cities along with their population.
{ "country": [
{
"name": "USA",
"state": [
{
"name": "Arizona",
"city": [
{
"name" : "Phoenix",
"population": 5000000
} ,
{
"name" : "Tuscon",
"population": 1000000
}
]
} ,
{
"name": "California",
"city": [
{
"name" : "Los Angeles",
"population": 19000000
},
{
"name" : "Irvine"
}
]
}
]
} ,
{
"name": "Mexico",
"state": [
{
"name": "Veracruz",
"city": [
{
"name" : "Xalapa-Enríquez",
"population": 8000000
},
{
"name" : "C\u00F3rdoba",
"population": 220000
}
]
} ,
{
"name": "Sinaloa",
"city": [
{
"name" : "Culiac\u00E1n Rosales",
"population": 3000000
}
]
}
]
}
]
}
Call from Javascript (post.html)
You can call your Vely code from Javascript via fetch(). This HTML file will do that as soon as it loads (no buttons to push), you can of course change it to fit your needs. This also demonstrates the use of POST method with Content-Type of "application/json" to talk to your server-side Vely code.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Vely + JavaScript/Fetch + POST/PUT/PATCH + JSON</title>
</head>
<body>
<h1 class="align">Example: Vely + JavaScript/Fetch + POST/PUT/PATCH + JSON</h1>
<script>
fetch('/json/json_process',{
method: 'POST',
headers: {'content-type': 'application/json'},
body: '{ "country": [ \
{ \
"name": "USA", \
"state": [ \
{ \
"name": "Arizona", \
"city": [ \
{ \
"name" : "Phoenix", \
"population": "5000000" \
} , \
{ \
"name" : "Tuscon", \
"population": "1000000" \
} \
\
] \
} , \
{ \
"name": "California", \
"city": [ \
{ \
"name" : "Los Angeles", \
"population": "4000000" \
}, \
{ \
"name" : "Irvine" \
} \
] \
} \
] \
} , \
{ \
"name": "Mexico", \
"state": [ \
{ \
"name": "Veracruz", \
"city": [ \
{ \
"name" : "Xalapa-Enríquez", \
"population": "8000000" \
}, \
{ \
"name" : "C\u00F3rdoba", \
"population": "220000" \
} \
] \
} , \
{ \
"name": "Sinaloa", \
"city": [ \
{ \
"name" : "Culiac\u00E1n Rosales", \
"population": "3000000" \
} \
] \
} \
] \
} \
] \
}'
})
.then((result) => { return result.text(); })
.then((content) => { document.getElementById("json_output").innerHTML = content; });
</script>
<div id='json_output'></div>
</body>
</html>
Enter JSON (json_form.vely)
This is a simple HTML form where you can enter JSON document. Since the code in json_process.vely and json_all.vely parses a list of cities as described, the text to enter is given in cities.json file.
#include "vely.h"
void json_form () {
out-header default
@<h2>Enter JSON</h2>
@<form action="<<p-path>>/json_process" method="POST">
@ <label for="json_text">JSON text:</label><br>
@ <textarea name="json_text" rows="8" columns="70"></textarea><br/>
@ <button type="submit">Extract specific data</button>
@ <button type="submit" formaction="<<p-path>>/json_all">Extract all data</button>
@ </form>
}
Parse all JSON data in a loop (json_all.vely)
This parses the document that you may not know the structure of. Each data node is obtained you can examine it in your code.
#include "vely.h"
void json_all() {
out-header default
input-param json_text
new-json define json from json_text status define st error-text define etext error-position define epos
if (st != VV_OKAY) {
@Could not parse JSON! Error [<<p-out etext>>] at position <<p-num epos>>.
exit-request
}
read-json json traverse begin
@<table border='1'>
while (1)
{
read-json json traverse key define k value define v type define t status define s
if (s != VV_OKAY) break;
@<tr>
@<td><<p-out k>></td> <td><<p-out v>></td>
@<td><<p-out t==VV_JSON_TYPE_NUMBER?"Number":(t==VV_JSON_TYPE_STRING?"String":"Other")>></td>
@</tr>
}
@</table>
}
Parse JSON by looking up specific elements (json_process.vely)
Parse the JSON document. This shows parsing the document that you know a structure of, but does not have a fixed structure, so each element is retrieved based on its normalized name (see
read-json).
#include "vely.h"
void json_process() {
out-header default
input-param json_text
request-body json_body
get-req content-type to define ctype
if (!strcmp(ctype, "application/json")) json_text=json_body;
new-json define json from json_text status define st error-text define etext error-position define epos
if (st != VV_OKAY) {
@Could not parse JSON! Error [<<p-out etext>>] at position <<p-num epos>>.
exit-request
}
@Cities found<hr/>
num country_count;
num state_count;
num city_count;
@<ul>
010
0
for (country_count = 0; ; country_count++) {
(( define json_key_country
@"country"[<<p-num country_count>>]
))
(( define json_key_country_name
@<<p-out json_key_country>>."name"
))
read-json json key json_key_country_name value define country_name status st
if (st != VV_OKAY) break;
@<li>Country: <<p-out country_name>><br/>
@<ul>
for (state_count = 0; ; state_count++) {
(( define json_key_state
@<<p-out json_key_country>>."state"[<<p-num state_count>>]
))
(( define json_key_state_name
@<<p-out json_key_state>>."name"
))
read-json json key json_key_state_name value define state_name status st
if (st != VV_OKAY) break;
@<li>State: <<p-out state_name>><br/>
@<ul>
for (city_count = 0; ; city_count++) {
(( define json_key_city
@<<p-out json_key_state>>."city"[<<p-num city_count>>]
))
(( define json_key_city_name
@<<p-out json_key_city>>."name"
))
read-json json key json_key_city_name value define city_name status st
if (st != VV_OKAY) break;
(( define json_key_city_population
@<<p-out json_key_city>>."population"
))
read-json json key json_key_city_population value define city_population status st
if (st != VV_OKAY) city_population="unknown";
@<li>City:<<p-out city_name>> (<<p-out city_population>>)</li>
}
@</ul>
@</li>
}
@</ul>
@</li>
}
@</ul>
}
See also
Examples (
example_cookies example_create_table example_docker example_file_manager example_form example_hash example_hello_world example_json example_multitenant_SaaS examples example_sendmail example_shopping example_stock example_utility example_write_report )
SEE ALL (
documentation)
Example: multitenant SaaS
This is a multi-tenant web application that you can run on the Internet as Software-as-a-Service (SaaS). Each user has a completely separate data space from any other.
This web application will let user sign up for Notes service - a place where a user can create notes, and then view and delete them.
This example shows how to change application path (see
request_URL) and use REST-like URLs for your API/application.
In a nutshell: MariaDB; web browser; Apache; Application path; Unix sockets; 7 source files, 312 lines of code.
Screenshots of application
Create new user by specifying an email address and password (you can style these anyway you like, such as with CSS):
Verifying user's email:
User logs in with own user name and password:
Adding a note once the user logged in:
Listing notes:
Ask for confirmation to delete a note:
After user confirms, delete a note:
Setup prerequisites
Install Vely - you can use standard packaging tools such as
apt,
dnf,
pacman or
zypper.
Because they are used in this example, you will need to install
Apache as a web server and
MariaDB as a database.
After installing Vely, turn on syntax highlighting in vim if you're using it:
Get the source code
The source code is a part of Vely installation. It is a good idea to create a separate source code directory for each application (and you can name it whatever you like). In this case, unpacking the source code will do that for you:
tar xvf $(vv -o)/examples/multitenant_SaaS.tar.gz
cd multitenant_SaaS
Setup application
The very first step is to create an application. The application will be named "multitenant_SaaS", but you can name it anything (if you do that, change it everywhere). It's simple to do with
vf:
sudo vf -i -u $(whoami) multitenant_SaaS
This will create a new application home (which is "/var/lib/vv/multitenant_SaaS") and do the application setup for you. Mostly that means create various subdirectories in the home folder, and assign them privileges. In this case only current user (or the result of "whoami" Linux command) will own those directories with 0700 privileges; it means a secure setup.
Setup the database
Before any coding, you need some place to store the information used by the application. First, you will create MariaDB database "db_multitenant_SaaS" owned by user "vely" with password "your_password". You can change any of these names, but remember to change them everywhere here. And then, you will create database objects in the database.
Execute the following logged in as root in
mysql utility:
create database if not exists db_multitenant_SaaS;
create user if not exists vely identified by 'your_password';
grant create,alter,drop,select,insert,delete,update on db_multitenant_SaaS.* to vely;
use db_multitenant_SaaS;
source setup.sql;
exit
Connect Vely to a database
In order to let Vely know where your database is and how to log into it, you will create
database_config_file named "db_multitenant_SaaS". This name doesn't have to be "db_multitenant_SaaS", rather it can be anything - this is the name used in actual database statements in source code (like
run-query), so if you change it, make sure you change it everywhere. Create it:
echo '[client]
user=vely
password=your_password
database=db_multitenant_SaaS
protocol=TCP
host=127.0.0.1
port=3306' > db_multitenant_SaaS
The above is a
standard mariadb client options file. Vely uses native MariaDB database connectivity, so you can specify any options that a given database lets you.
Build application
Use
vv utility to make the application:
vv -q --db=mariadb:db_multitenant_SaaS --path="/api/v2/multitenant_SaaS"
Note usage of --db option to specify MariaDB database and the database configuration file name.
--path is used to specify the application path, see
request_URL.
Start your application server
To start the application server for your web application use
vf FastCGI process manager. The application server will use a Unix socket to communicate with the web server (i.e. a reverse-proxy):
This will start 3 daemon processes to serve the incoming requests. You can also start an adaptive server that will increase the number of processes to serve more requests, and gradually reduce the number of processes when they're not needed:
See
vf for more options to help you achieve best performance.
If you want to stop your application server:
vf -m quit multitenant_SaaS
Setup web server
This shows how to connect your application listening on a Unix socket (started with
vf) to Apache web server.
- Step 1:
To setup Apache as a reverse proxy and connect your application to it, you need to enable FastCGI proxy support, which generally means "proxy" and "proxy_fcgi" modules - this is done
only once:
- For Debian (like Ubuntu) and OpenSUSE systems you need to enable proxy and proxy_fcgi modules:
sudo a2enmod proxy
sudo a2enmod proxy_fcgi
- For Fedora systems (or others like Archlinux) enable proxy and proxy_fcgi modules by adding (or uncommenting) LoadModule directives in the Apache configuration file - the default location of this file on Linux depends on the distribution. For Fedora (such as RedHat), Archlinux:
sudo vi /etc/httpd/conf/httpd.conf
For OpenSUSE:
sudo vi /etc/apache2/httpd.conf
Add this to the end of the file:
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
- Step 2:
Edit the Apache configuration file:
- For Debian (such as Ubuntu):
sudo vi /etc/apache2/apache2.conf
- for Fedora (such as RedHat), Archlinux:
sudo vi /etc/httpd/conf/httpd.conf
- and for OpenSUSE:
sudo vi /etc/apache2/httpd.conf
Add this to the end of file ("/api/v2/multitenant_SaaS" is the application path (see
request_URL) and "multitenant_SaaS" is your application name):
ProxyPass "/api/v2/multitenant_SaaS" unix:///var/lib/vv/multitenant_SaaS/sock/sock|fcgi://localhost/multitenant_SaaS
- Step 3:
Finally, restart Apache. On Debian systems (like Ubuntu) or OpenSUSE:
sudo systemctl restart apache2
On Fedora systems (like RedHat) and Arch Linux:
sudo systemctl restart httpd
Note: you must not have any other URL resource that starts with "/api/v2/multitenant_SaaS" (such as for example "/api/v2/multitenant_SaaS.html" or "/api/v2/multitenant_SaaS_something" etc.) as the web server will attempt to pass them as a reverse proxy request, and they will likely not work. If you need to, you can change the application path to be different from "/api/v2/multitenant_SaaS", see
request_URL.
Setup local mail
This example uses email as a part of its function. If your server already has capability to send email, you can skip this.
Otherwise, you can use local mail, and that means email addresses such as "myuser@localhost". To do that, install postfix (or sendmail). On Debian systems (like Ubuntu):
sudo apt install postfix
sudo systemctl start postfix
and on Fedora systems (like RedHat):
sudo dnf install postfix
sudo systemctl start postfix
When the application sends an email to a local user, such as <OS user>@localhost, then you can see the email sent at:
sudo vi /var/mail/<OS user>
Access application server from the browser
Use the following URL(s) to access your application server from a client like browser (see
request_URL). Use actual IP or web address instead of 127.0.0.1 if different.
#Get started
http://127.0.0.1/api/v2/multitenant_SaaS/notes/action/begin
Note: if your server is on the Internet and it has a firewall, you may need to allow HTTP traffic - see
ufw,
firewall-cmd etc.
Files
You are now done with the example! What follows are the source files in this project so you can examine how it works:
SQL setup (setup.sql)
The two tables created are: "users", which contains information about each user; and "notes" which contains notes entered by the user.
Each user in "users" table has its own unique ID ("userId" column) along with other information such as email address and whether it's verified. There's also a hashed password - an actual password is never stored in plain text (or otherwise), rather a one-way hash is used to check the password.
The "notes" table contains the notes, each along with "userId" column that states which user owns them. The "userId" column's value matches the namesake column from "users" table. This way, every note clearly belongs to a single user.
create table if not exists notes (dateOf datetime, noteId bigint auto_increment primary key, userId bigint, note varchar(1000));
create table if not exists users (userId bigint auto_increment primary key, email varchar(100), hashed_pwd varchar(100), verified smallint, verify_token varchar(30), session varchar(100));
create unique index if not exists users1 on users (email);
Run-time data (login.h)
In order to properly display the Login, Sign Up and Logout links, you will need some flags that are available anywhere in the application. Also, the application uses cookies to maintain a session, so this needs to be available anywhere, for example to verify that the session is valid. Every request sent to the application is verified that way. Only requests that come with cookies we can verify are permitted.
So to that effect, you will have a
global_request_data type "reqdata" (request data) and in it there's "sess_userId" (ID of user) and "sess_id" (user's current session ID). You'll also have rather self-explanatory flags that help render pages.
#ifndef _VV_LOGIN
#define _VV_LOGIN
typedef struct s_reqdata {
bool displayed_logout;
bool is_logged_in;
char *sess_userId;
char *sess_id;
} reqdata;
void login_or_signup ();
#endif
Session checking and session data (_before.vely)
Vely has a notion of a
before_request_handler. It's the code you write that executes before any other code that handles a request. To do this, all you need is to write this code in file that's named "_before.vely" and the rest will be automatically handled.
That's very useful here. Anything that a SaaS application does, such as handling requests sent to an application, must be validated for security. This way, the application knows if the caller has permissions needed to perform an action.
So, this checking of permission will be done here in a before-request handler. That way, whatever other code you have handling a request, you will have the information about session already provided.
To keep session data (like session ID and user ID) available anywhere in your code, you'll use
global_request_data. It's just a generic pointer (void*) to memory that any code that handles a request can access. This is perfect for handling sessions.
#include "vely.h"
#include "login.h"
void _before() {
out-header default
reqdata *rd;
new-mem rd size sizeof(reqdata)
rd->displayed_logout = false;
rd->is_logged_in = false;
set-req data rd
_check_session ();
}
Checking if session is valid (_check_session.vely)
One of the most important tasks in a multi-tenant SaaS application is to check (as soon as possible) if the session is valid. This means to check if a user is logged in. It's done by getting the session ID and user ID cookies from the client (i.e. web browser), and checking these against the database where sessions are stored.
#include "vely.h"
#include "login.h"
void _check_session () {
reqdata *rd;
get-req data to rd
get-cookie rd->sess_userId="sess_userId"
get-cookie rd->sess_id="sess_id"
if (rd->sess_id[0] != 0) {
char *email;
run-query @db_multitenant_SaaS = "select email from users where userId='%s' and session='%s'" output email : rd->sess_userId, rd->sess_id row-count define rcount
query-result email to email
end-query
if (rcount == 1) {
rd->is_logged_in = true;
if (rd->displayed_logout == false) {
get-app path to define upath
@Hi <<p-out email>>! <a href="<<p-out upath>>/login/actions/logout">Logout</a><br/>
rd->displayed_logout = true;
}
} else rd->is_logged_in = false;
}
}
Signing up, Logging in, Logging out (login.vely)
The basis of any multi-tenant system is the ability for a user to sign up, and once signed up, to log in and log out.
Typically, signing up involves verifying email address, and more often than not, the very same email address is used as a user name. That will be the case here.
There are several sub
requests implemented here that are necessary to perform the functionality. Each has its own "URL request signature".
- When Signing Up a new user, display the HTML form to collect the information. The URL request signature for this is "/login/actions/newuser"
- As a response to Sign Up form, create a new user. The URL request signature is "/login/actions/createuser". input-param is used to obtain "email" and "pwd" POST form fields (i.e. query string). Password is one-way hashed and an email verification token is created as a random 5-digit number. These are inserted into "users" table, creating a new user. A verification email is sent out, and the user is prompted to read the email and enter the code.
- Verify email by entering verification code sent to that email. The URL request signature is "/login/actions/verify"
- Display a Login form for user to login. The URL request signature is "/login" (i.e. "action" is empty)
- Login by verifying email address (i.e. user name) and password. The URL request signature is "/login/actions/login"
- Logout at user's request. The URL request signature is "/login/actions/logout"
- Landing page for application. The URL request signature is "/login/actions/begin"
- If the user is currently logged in, go to the application's landing page.
#include "vely.h"
#include "login.h"
void login () {
"actions"
input-param actions
reqdata *rd;
get-req data to rd
if (rd->is_logged_in) {
if (strcmp(actions, "logout")) {
_show_home();
exit-request
}
}
if (!strcmp (actions, "begin")) {
_show_home();
exit-request
} else if (!strcmp (actions, "newuser")) {
@Create New User<hr/>
@<form action="<<p-path>>/login/actions/createuser" method="POST">
@<input name="email" type="text" value="" size="50" maxlength="50" required autofocus placeholder="Email">
@<input name="pwd" type="password" value="" size="50" maxlength="50" required placeholder="Password">
@<input type="submit" value="Sign Up">
@</form>
} else if (!strcmp (actions, "verify")) {
input-param code
input-param email
run-query @db_multitenant_SaaS = "select verify_token from users where email='%s'" output db_verify : email
query-result db_verify to define db_verify
if (!strcmp (code, db_verify)) {
@Your email has been verifed. Please <a href="<<p-path>>/login">Login</a>.
run-query @db_multitenant_SaaS no-loop = "update users set verified=1 where email='%s'" : email
exit-request
}
end-query
@Could not verify the code. Please try <a href="<<p-path>>/login">again</a>.
exit-request
} else if (!strcmp (actions, "createuser")) {
input-param email
input-param pwd
hash-string pwd to define hashed_pwd
5
random-string to define verify length 5 number
0
begin-transaction @db_multitenant_SaaS
run-query @db_multitenant_SaaS no-loop = "insert into users (email, hashed_pwd, verified, verify_token, session) values ('%s', '%s', '0', '%s', '')" : email, hashed_pwd, verify affected-rows define arows error define err on-error-continue
if (strcmp (err, "0") || arows != 1) {
login_or_signup();
@User with this email already exists.
rollback-transaction @db_multitenant_SaaS
} else {
write-string define msg
@From: vely@vely.dev
@To: <<p-out email>>
@Subject: verify your account
@
@Your verification code is: <<p-out verify>>
end-write-string
exec-program "/usr/sbin/sendmail" args "-i", "-t" input msg status define st
if (st != 0) {
@Could not send email to <<p-out email>>, code is <<p-out verify>>
rollback-transaction @db_multitenant_SaaS
exit-request
}
commit-transaction @db_multitenant_SaaS
@Please check your email and enter verification code here:
@<form action="<<p-path>>/login/actions/verify" method="POST">
@<input name="email" type="hidden" value="<<p-out email>>">
@<input name="code" type="text" value="" size="50" maxlength="50" required autofocus placeholder="Verification code">
@<button type="submit">Verify</button>
@</form>
}
} else if (!strcmp (actions, "logout")) {
if (rd->is_logged_in) {
run-query @db_multitenant_SaaS = "update users set session='' where userId='%s'" : rd->sess_userId no-loop affected-rows define arows
if (arows == 1) {
rd->is_logged_in = false;
@You have been logged out.<hr/>
}
}
_show_home();
} else if (!strcmp (actions, "login")) {
input-param pwd
input-param email
hash-string pwd to define hashed_pwd
30
random-string to rd->sess_id length 30
run-query @db_multitenant_SaaS = "select userId from users where email='%s' and hashed_pwd='%s'" output sess_userId : email, hashed_pwd
query-result sess_userId to rd->sess_userId
run-query @db_multitenant_SaaS no-loop = "update users set session='%s' where userId='%s'" : rd->sess_id, rd->sess_userId affected-rows define arows
if (arows != 1) {
@Could not create a session. Please try again. <<.login_or_signup();>> <hr/>
exit-request
}
set-cookie "sess_userId" = rd->sess_userId path "/"
set-cookie "sess_id" = rd->sess_id path "/"
_check_session();
_show_home();
exit-request
end-query
@Email or password are not correct. <<.login_or_signup();>><hr/>
} else if (!strcmp (actions, "")) {
login_or_signup();
@Please Login:<hr/>
@<form action="<<p-path>>/login/actions/login" method="POST">
@<input name="email" type="text" value="" size="50" maxlength="50" required autofocus placeholder="Email">
@<input name="pwd" type="password" value="" size="50" maxlength="50" required placeholder="Password">
@<button type="submit">Go</button>
@</form>
}
}
void login_or_signup() {
@<a href="<<p-path>>/login">Login</a> <a href="<<p-path>>/login/actions/newuser">Sign Up</a><hr/>
}
General-purpose application (_show_home.vely)
With this tutorial you can create any multitenant SaaS application you want. The multitenant-processing module above (login.vely) calls _show_home() function, which can house any code of yours. In here, you'll have Notes application, but it can be anything. _show_home() simply calls any code you wish, and is a general-purpose multitenant application plug-in.
#include "vely.h"
void _show_home() {
notes();
exit-request
}
Notes application (notes.vely)
The application will be able to add notes, list them, and delete any given note. It operates under a request "notes", and the sub-requests it serves are:
- /notes/subreqs/add_note - add a note,
- /notes/subreqs/list - list notes,
- /notes/subreqs/delete_note - delete a note
while a few other subrequests are user-interface-only, such as:
- /notes/subreqs/add_note - display an HTML form to add a note,
- /notes/subreqs/delete_note_ask - ask user to confirm intent to delete a note
#include "vely.h"
#include "login.h"
void notes () {
reqdata *rd;
get-req data to rd
if (!rd->is_logged_in) {
login_or_signup();
}
@<h1>Welcome to Notes!</h1><hr/>
if (!rd->is_logged_in) {
exit-request
}
input-param subreqs
@<a href="<<p-path>>/notes/subreqs/add">Add Note</a> <a href="<<p-path>>/notes/subreqs/list">List Notes</a><hr/>
if (!strcmp (subreqs, "list")) {
run-query @db_multitenant_SaaS = "select dateOf, note, noteId from notes where userId='%s' order by dateOf desc" : rd->sess_userId output dateOf, note, noteId
query-result dateOf to define dateOf
query-result note to define note
query-result noteId to define noteId
(( define web_enc
p-web note
))
match-regex "\n" in web_enc replace-with "<br/>\n" result define with_breaks status define st cache
if (st == 0) with_breaks = web_enc;
@Date: <<p-out dateOf>> (<a href="<<p-path>>/notes/subreqs/delete_note_ask?note_id=<<p-out noteId>>">delete note</a>)<br/>
@Note: <<p-out with_breaks>><br/>
@<hr/>
end-query
}
else if (!strcmp (subreqs, "delete_note_ask")) {
input-param note_id
@Are you sure you want to delete a note? Use Back button to go back, or <a href="<<p-path>>/notes/subreqs/delete_note?note_id=<<p-out note_id>>">delete note now</a>.
}
else if (!strcmp (subreqs, "delete_note")) {
input-param note_id
run-query @db_multitenant_SaaS = "delete from notes where noteId='%s' and userId='%s'" : note_id, rd->sess_userId affected-rows define arows no-loop error define errnote
if (arows == 1) {
@Note deleted
} else {
@Could not delete note (<<p-out errnote>>)
}
}
else if (!strcmp (subreqs, "add_note")) {
input-param note
run-query @db_multitenant_SaaS = "insert into notes (dateOf, userId, note) values (now(), '%s', '%s')" : rd->sess_userId, note affected-rows define arows no-loop error define errnote
if (arows == 1) {
@Note added
} else {
@Could not add note (<<p-out errnote>>)
}
}
"add_note"
else if (!strcmp (subreqs, "add")) {
@Add New Note
@<form action="<<p-path>>/notes/subreqs/add_note" method="POST">
@<textarea name="note" rows="5" cols="50" required autofocus placeholder="Enter Note"></textarea>
@<button type="submit">Create</button>
@</form>
}
}
See also
Examples (
example_cookies example_create_table example_docker example_file_manager example_form example_hash example_hello_world example_json example_multitenant_SaaS examples example_sendmail example_shopping example_stock example_utility example_write_report )
SEE ALL (
documentation)
Example: sendmail
Sending email through web interface demonstrates:
- web input of the following data: From, To, Subject and Message for an email,
- submittal of the form that sends an email, and confirmation displayed.
In a nutshell: web browser; Apache; Unix sockets; 1 source files, 79 lines of code.
Screenshots of application
An HTML form will accept the necessary information to send an email. Your server must be enabled to send email via Internet; if not, you can send email to localhost (i.e. to users on your own computer) and for that you must have an MTA like sendmail installed:
Once email is sent, a confirmation is sent to the user.:
Setup prerequisites
Install Vely - you can use standard packaging tools such as
apt,
dnf,
pacman or
zypper.
Because it is used in this example, you will need to install
Apache as a web server.
After installing Vely, turn on syntax highlighting in vim if you're using it:
Get the source code
The source code is a part of Vely installation. It is a good idea to create a separate source code directory for each application (and you can name it whatever you like). In this case, unpacking the source code will do that for you:
tar xvf $(vv -o)/examples/sendmail.tar.gz
cd sendmail
Setup application
The very first step is to create an application. The application will be named "sendmail", but you can name it anything (if you do that, change it everywhere). It's simple to do with
vf:
sudo vf -i -u $(whoami) sendmail
This will create a new application home (which is "/var/lib/vv/sendmail") and do the application setup for you. Mostly that means create various subdirectories in the home folder, and assign them privileges. In this case only current user (or the result of "whoami" Linux command) will own those directories with 0700 privileges; it means a secure setup.
Build application
Use
vv utility to make the application:
Start your application server
To start the application server for your web application use
vf FastCGI process manager. The application server will use a Unix socket to communicate with the web server (i.e. a reverse-proxy):
This will start 3 daemon processes to serve the incoming requests. You can also start an adaptive server that will increase the number of processes to serve more requests, and gradually reduce the number of processes when they're not needed:
See
vf for more options to help you achieve best performance.
If you want to stop your application server:
Setup web server
This shows how to connect your application listening on a Unix socket (started with
vf) to Apache web server.
- Step 1:
To setup Apache as a reverse proxy and connect your application to it, you need to enable FastCGI proxy support, which generally means "proxy" and "proxy_fcgi" modules - this is done
only once:
- For Debian (like Ubuntu) and OpenSUSE systems you need to enable proxy and proxy_fcgi modules:
sudo a2enmod proxy
sudo a2enmod proxy_fcgi
- For Fedora systems (or others like Archlinux) enable proxy and proxy_fcgi modules by adding (or uncommenting) LoadModule directives in the Apache configuration file - the default location of this file on Linux depends on the distribution. For Fedora (such as RedHat), Archlinux:
sudo vi /etc/httpd/conf/httpd.conf
For OpenSUSE:
sudo vi /etc/apache2/httpd.conf
Add this to the end of the file:
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
- Step 2:
Edit the Apache configuration file:
- For Debian (such as Ubuntu):
sudo vi /etc/apache2/apache2.conf
- for Fedora (such as RedHat), Archlinux:
sudo vi /etc/httpd/conf/httpd.conf
- and for OpenSUSE:
sudo vi /etc/apache2/httpd.conf
Add this to the end of file ("/sendmail" is the application path (see
request_URL) and "sendmail" is your application name):
ProxyPass "/sendmail" unix:///var/lib/vv/sendmail/sock/sock|fcgi://localhost/sendmail
- Step 3:
Finally, restart Apache. On Debian systems (like Ubuntu) or OpenSUSE:
sudo systemctl restart apache2
On Fedora systems (like RedHat) and Arch Linux:
sudo systemctl restart httpd
Note: you must not have any other URL resource that starts with "/sendmail" (such as for example "/sendmail.html" or "/sendmail_something" etc.) as the web server will attempt to pass them as a reverse proxy request, and they will likely not work. If you need to, you can change the application path to be different from "/sendmail", see
request_URL.
Setup local mail
This example uses email as a part of its function. If your server already has capability to send email, you can skip this.
Otherwise, you can use local mail, and that means email addresses such as "myuser@localhost". To do that, install postfix (or sendmail). On Debian systems (like Ubuntu):
sudo apt install postfix
sudo systemctl start postfix
and on Fedora systems (like RedHat):
sudo dnf install postfix
sudo systemctl start postfix
When the application sends an email to a local user, such as <OS user>@localhost, then you can see the email sent at:
sudo vi /var/mail/<OS user>
Access application server from the browser
Use the following URL(s) to access your application server from a client like browser (see
request_URL). Use actual IP or web address instead of 127.0.0.1 if different.
#Display a send-mail form
http://127.0.0.1/sendmail/mail?action=show_form
Note: if your server is on the Internet and it has a firewall, you may need to allow HTTP traffic - see
ufw,
firewall-cmd etc.
Files
You are now done with the example! What follows are the source files in this project so you can examine how it works:
Send email on the web (mail.vely)
Here, the user can enter information necessary to send an email, such as the recipient email, the subject and message etc. The form to do this is displayed with sub
request "show_form". When the user clicks Submit, the request is sent back with subrequest "submit_form" which uses
input-param to collect all the user data, construct an email string and then execute "sendmail" program to send email.
Note that you must have an MTA (Mail Transfer Agent) installed (such as postfix or sendmail), and your computer must be authorized to send email on the internet. Otherwise you can test this by sending emails to your localhost.
2.0
2018
#include "vely.h"
void mail()
{
out-header default
input-param action
if (!strcmp (action, "show_form")) {
@<h2>Enter email and click Send to send it</h2>
@Note: 'From' field must be the email address from the domain of your server.<br/><br/>
@<form action="<<p-path>>/mail" method="POST">
@ <input type="hidden" name="action" value="submit_form">
@ <label for="from_mail">From:</label><br>
@ <input type="text" name="from_mail" value=""><br>
@ <label for="to_mail">To:</label><br>
@ <input type="text" name="to_mail" value=""><br><br>
@ <label for="subject_mail">Subject:</label><br>
@ <input type="text" name="subject_mail" value=""><br><br>
@ <label for="message">Message:</label><br>
@ <textarea name="message" rows="3" columns="50"></textarea>
@ <br/><br/>
@ <input type="submit" value="Send">
@</form>
} else if (!strcmp (action, "submit_form")) {
input-param from_mail
input-param to_mail
input-param message
input-param subject_mail
write-string define msg
@From: <<p-out from_mail>>
@To: <<p-out to_mail>>
@Subject: <<p-out subject_mail>>
@
<<p-out message>>
end-write-string
num st;
exec-program "/usr/sbin/sendmail" args "-i", "-t" input msg status st
if (st!=0) {
@Could not send email!
} else {
@Email sent!
}
@<hr/>
} else {
@Unrecognized action!<hr/>
}
}
See also
Examples (
example_cookies example_create_table example_docker example_file_manager example_form example_hash example_hello_world example_json example_multitenant_SaaS examples example_sendmail example_shopping example_stock example_utility example_write_report )
SEE ALL (
documentation)
Example: shopping
This is a shopping web service REST API with basic functions:
- add customer
- add item
- create order
- add item to order
- update order
- delete item from order
- delete order
The APIs return a valid JSON reply, even if it's just a single string (such as created ID for a customer, item or order). Listing an order returns a JSON document showing the order details.
The example demostrates usage of REST methods (POST, PUT, GET, DELETE) as well as construction and use of REST URLs, and the code that implements the API.
In a nutshell: PostgreSQL; web browser; Apache; REST API; Application path; Unix sockets; 11 source files, 212 lines of code.
Screenshots of application
Add a customer (named Diana Vega). "Customer ID" is the result JSON text (in this case "3"):
Add two items ("chocolate bar" and "swiss cheese"). "Item ID" is the result JSON text for each:
Add an order for customer created by specifying "Customer ID" (in this case it's "3"):
Add two items to order created by specifying "Order ID" (in this case "5"). The two items are the ones created with "Item ID"s of "3" and "4", and quantities of items ordered are "2" and "1" respectively:
Update an order by specifying "Order ID" (in this case "5"). The items is specified with "Item ID" (in this case "4") and the quantity is updated to "3":
Delete an order with "Order ID" (in this case "3"). The result is JSON text specifying number of orders deleted (in this case "1"), and the number of items in it (in this case "0"):
List a single order by using "Order ID" (in this case "5"). JSON response contains customer and items information:
List all orders. The JSON response contains customer and items information, and in this case there are two orders:
Deletes a line item (i.e. all items of the same kind) in an order. In this case "Item ID" specifies the item ("4") in an order given by "Order ID" ("5"). The result is the number of line items deleted:
Setup prerequisites
Install Vely - you can use standard packaging tools such as
apt,
dnf,
pacman or
zypper.
Because they are used in this example, you will need to install
Apache as a web server and
PostgreSQL as a database.
After installing Vely, turn on syntax highlighting in vim if you're using it:
Get the source code
The source code is a part of Vely installation. It is a good idea to create a separate source code directory for each application (and you can name it whatever you like). In this case, unpacking the source code will do that for you:
tar xvf $(vv -o)/examples/shopping.tar.gz
cd shopping
Setup application
The very first step is to create an application. The application will be named "shopping", but you can name it anything (if you do that, change it everywhere). It's simple to do with
vf:
sudo vf -i -u $(whoami) shopping
This will create a new application home (which is "/var/lib/vv/shopping") and do the application setup for you. Mostly that means create various subdirectories in the home folder, and assign them privileges. In this case only current user (or the result of "whoami" Linux command) will own those directories with 0700 privileges; it means a secure setup.
Setup the database
Before any coding, you need some place to store the information used by the application. First, you will create PostgreSQL database "db_shopping". You can change the database name, but remember to change it everywhere here. And then, you will create database objects in the database.
Note the example here uses peer-authentication, which is the default on all modern PostgreSQL installations - this means the database user name is the same as the Operating System user name.
Execute the following in PostgreSQL database as root (using
psql utility):
echo "create user $(whoami);
create database db_shopping with owner=$(whoami);
grant all on database db_shopping to $(whoami);
\q
" | sudo -u postgres psql
Next, login to database db_shopping and create the database objects for the application:
psql -d db_shopping -f setup.sql
Connect Vely to a database
In order to let Vely know where your database is and how to log into it, you will create
database_config_file named "db_shopping". This name doesn't have to be "db_shopping", rather it can be anything - this is the name used in actual database statements in source code (like
run-query), so if you change it, make sure you change it everywhere. Create it:
echo "user=$(whoami) dbname=db_shopping" > db_shopping
The above is a standard
postgres connection string that describes the login to the database you created. Since Vely uses native PostgreSQL connectivity, you can specify any connection string that your database lets you.
Build application
Use
vv utility to make the application:
vv -q --db=postgres:db_shopping --path="/api/v1/shopping"
Note usage of --db option to specify PostgreSQL database and the database configuration file name.
--path is used to specify the application path, see
request_URL.
Start your application server
To start the application server for your web application use
vf FastCGI process manager. The application server will use a Unix socket to communicate with the web server (i.e. a reverse-proxy):
This will start 3 daemon processes to serve the incoming requests. You can also start an adaptive server that will increase the number of processes to serve more requests, and gradually reduce the number of processes when they're not needed:
See
vf for more options to help you achieve best performance.
If you want to stop your application server:
Setup web server
This shows how to connect your application listening on a Unix socket (started with
vf) to Apache web server.
- Step 1:
To setup Apache as a reverse proxy and connect your application to it, you need to enable FastCGI proxy support, which generally means "proxy" and "proxy_fcgi" modules - this is done
only once:
- For Debian (like Ubuntu) and OpenSUSE systems you need to enable proxy and proxy_fcgi modules:
sudo a2enmod proxy
sudo a2enmod proxy_fcgi
- For Fedora systems (or others like Archlinux) enable proxy and proxy_fcgi modules by adding (or uncommenting) LoadModule directives in the Apache configuration file - the default location of this file on Linux depends on the distribution. For Fedora (such as RedHat), Archlinux:
sudo vi /etc/httpd/conf/httpd.conf
For OpenSUSE:
sudo vi /etc/apache2/httpd.conf
Add this to the end of the file:
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
- Step 2:
Edit the Apache configuration file:
- For Debian (such as Ubuntu):
sudo vi /etc/apache2/apache2.conf
- for Fedora (such as RedHat), Archlinux:
sudo vi /etc/httpd/conf/httpd.conf
- and for OpenSUSE:
sudo vi /etc/apache2/httpd.conf
Add this to the end of file ("/api/v1/shopping" is the application path (see
request_URL) and "shopping" is your application name):
ProxyPass "/api/v1/shopping" unix:///var/lib/vv/shopping/sock/sock|fcgi://localhost/shopping
- Step 3:
Finally, restart Apache. On Debian systems (like Ubuntu) or OpenSUSE:
sudo systemctl restart apache2
On Fedora systems (like RedHat) and Arch Linux:
sudo systemctl restart httpd
Note: you must not have any other URL resource that starts with "/api/v1/shopping" (such as for example "/api/v1/shopping.html" or "/api/v1/shopping_something" etc.) as the web server will attempt to pass them as a reverse proxy request, and they will likely not work. If you need to, you can change the application path to be different from "/api/v1/shopping", see
request_URL.
Access application server from the browser
Use the following URL(s) to access your application server from a client like browser (see
request_URL). Use actual IP or web address instead of 127.0.0.1 if different.
Note: if your server is on the Internet and it has a firewall, you may need to allow HTTP traffic - see
ufw,
firewall-cmd etc.
Here is REST API for the application.
Substitute the loopback "127.0.0.1" with the IP or web address of your server.
To add a customer, use the POST URL to create new "customer" resource:
for example:
The return value is a JSON document containing a single value, and that is <customer ID> of a newly created customer.
To add an item to the list of inventory items, use the POST URL to create new "item" resource:
for example:
The return value is a JSON document containing a single value, and that is <item ID> of a newly created item.
To create an order, use the following POST URL to create a new "order" resource, and use <customer ID> previously obtained:
for example:
The return value is a JSON document containing a single value, and that is <order ID> of a newly created order.
To add an item to an order, use the following POST URL to create a new "order/item" resource, and use <order ID> and <item ID> previously obtained:
for example:
The return value is a JSON document containing a single value, and that is the number of line items created ("1" or "0").
To update an order (meaning change the number of items), use the following PUT URL, and use <order ID> and <item ID> previously obtained:
for example:
The return value is a JSON document containing a single value, and that is the number of line items updated ("1" or "0"). Per REST methodology, this is an idemopotent operation, i.e. it can be repeated any number of times with the same result.
To delete an item from order, use the following DELETE URL, and use <order ID> and <item ID> previously obtained:
for example:
The return value is a JSON document containing a single value, and that is the number of line items delete ("1" or "0").
To list all orders, use the following GET URL:
The return value is a JSON document with all orders, including customer information, as well as items, their descriptions and quantities.
To list a specific order, use the following GET URL:
for example:
The return value is a JSON document with a specific order, including customer information, as well as items, their descriptions and quantities.
To delete a specific order, use the following DELETE URL:
for example:
The return value is a JSON document with a number of orders and number of items deleted.
Files
You are now done with the example! What follows are the source files in this project so you can examine how it works:
Customers resource (customers.vely)
This implements REST API for a customer.
#include "vely.h"
void customers()
{
get-req method to define req_method
if (req_method == VV_POST) add_customer ();
}
Orders resource (orders.vely)
This implements REST API for an order.
#include "vely.h"
void orders()
{
out-header use content-type "application/json"
get-req method to define req_method
if (req_method == VV_POST) {
input-param item_id
if (item_id[0] == 0) create_order (); else add_to_order();
}
else if (req_method == VV_PUT) update_order ();
else if (req_method == VV_GET) list_orders ();
else if (req_method == VV_DELETE) {
input-param item_id
if (item_id[0] == 0) delete_order (); else {
set-input "quantity" = "0"
update_order ();
}
}
}
Items resource (items.vely)
This implements REST API for an item.
#include "vely.h"
void items()
{
out-header use content-type "application/json"
get-req method to define req_method
if (req_method == VV_POST) add_item ();
}
Add a customer (add_customer.vely)
This will add a new customer. Removing a customer is not included, and it should remove all its orders.
#include "vely.h"
void add_customer()
{
out-header use content-type "application/json"
input-param first_name
input-param last_name
run-query @db_shopping ="insert into customers (firstName, lastName) \
values ('%s', '%s') returning customerID" output define customerID : \
first_name, last_name
@"<<p-out customerID>>"
end-query
}
Add an item to inventory (add_item.vely)
Here an item is added to invetory available for sale.
#include "vely.h"
void add_item()
{
out-header use content-type "application/json"
input-param name
input-param description
run-query @db_shopping ="insert into items (name, description) \
values ('%s', '%s') returning itemID" output itemID : name, description
query-result itemID to define item_id
@"<<p-out item_id>>"
end-query
}
Create an order (create_order.vely)
This creates a new order, ready to have items added to it.
#include "vely.h"
void create_order()
{
out-header use content-type "application/json"
input-param customer_id
run-query @db_shopping ="insert into orders (customerId) \
values ('%s') returning orderID" output orderID : customer_id
query-result orderID to define order_id
@"<<p-out order_id>>"
end-query
}
Add item to order (add_to_order.vely)
Add an item to existing order.
#include "vely.h"
void add_to_order()
{
out-header use content-type "application/json"
input-param order_id
input-param item_id
input-param quantity
run-query @db_shopping ="insert into orderItems (orderId, itemID, quantity) values ('%s', '%s', '%s')" \
: order_id, item_id, quantity no-loop affected-rows define arows
@"<<p-num arows>>"
}
Update item quantity in an order (update_order.vely)
If the quantity update is 0, the item is deleted from order, otherwise the quantity is updated.
#include "vely.h"
void update_order()
{
out-header use content-type "application/json"
input-param order_id
input-param item_id
input-param quantity
num arows;
0
if (!strcmp (quantity, "0")) {
run-query @db_shopping ="delete from orderItems where orderID='%s' and itemID='%s'" \
: order_id, item_id no-loop affected-rows arows
} else {
run-query @db_shopping ="update orderItems set quantity='%s' where orderID='%s' and itemID='%s'" \
: quantity, order_id, item_id no-loop affected-rows arows
}
@"<<p-num arows>>"
}
List orders as JSON (list_orders.vely)
All orders are listed along with customer ID and name, and under each order are the items with their names, descriptions and quantity of items ordered.
#include "vely.h"
#include "shopping.h"
void list_orders()
{
out-header use content-type "application/json"
input-param order_id
num curr_order = 0;
@{ "orders": [
if (order_id[0] != 0) {
run-query @db_shopping = "select o.orderID, c.customerID, c.firstName, c.lastName \
from orders o, customers c \
where o.customerID=c.customerID and o.orderId='%s'" \
output define customer_id, first_name, last_name \
row-count define order_count : order_id
_json_from_order (order_id, curr_order, order_count, customer_id,
first_name, last_name);
end-query
} else {
run-query @db_shopping ="select o.orderID, c.customerID, c.firstName, c.lastName \
from orders o, customers c \
where o.customerID=c.customerID order by o.orderId" \
output define order_id, customer_id, first_name, last_name \
row-count define order_count
_json_from_order (order_id, curr_order, order_count, customer_id,
first_name, last_name);
end-query
}
@ ]
@}
}
Represent order as JSON (_json_from_query.vely)
This will take order information from the list of orders (as directed by GET REST API for either all orders or a specific one), and find all items within an order. The JSON text is output.
#include "vely.h"
#include "shopping.h"
void _json_from_order (char *order_id, num curr_order, num order_count,
char *customer_id, char *first_name, char *last_name)
{
@ {
@ "orderID": "<<p-out order_id>>",
@ "customer":
@ {
@ "customerID": "<<p-out customer_id>>",
@ "firstName": "<<p-out first_name>>",
@ "lastName": "<<p-out last_name>>"
@ },
@ "items": [
num curr_item = 0;
run-query @db_shopping ="select i.itemID, t.name, t.description, i.quantity \
from orderItems i, items t where i.orderID='%s' \
and t.itemID=i.itemID" \
output itemID, itemName, itemDescription, itemQuantity : order_id \
row-count define item_count
@ {
@ "itemID": "<<query-result itemID>>",
@ "itemName": "<<query-result itemName>>",
@ "itemDescription": "<<query-result itemDescription>>",
@ "itemQuantity": "<<query-result itemQuantity>>"
@ }<<p-out ++curr_item < item_count ? ",":"">>
end-query
@ ]
@ }<<p-out ++curr_order < order_count ? ",":"">>
}
Include file (shopping.h)
This file has a function declaration for _json_from_query() so it can be used in multiple .vely files.
#include "vely.h"
void _json_from_order (char *order_id, num curr_order, num order_count,
char *customer_id, char *first_name, char *last_name);
See also
Examples (
example_cookies example_create_table example_docker example_file_manager example_form example_hash example_hello_world example_json example_multitenant_SaaS examples example_sendmail example_shopping example_stock example_utility example_write_report )
SEE ALL (
documentation)
Examples
Click on each example for instructions. For a condensed Hello World that you can run from command line in just minutes, try
123_hello_world.
- example_hello_world
command line; web browser; Nginx; Unix sockets; 2 source files, 7 lines of code.
- example_cookies
web browser; Apache; Unix sockets; 1 source files, 49 lines of code.
- example_create_table
PostgreSQL; command line; web browser; Nginx; Unix sockets; 2 source files, 44 lines of code.
- example_file_manager
PostgreSQL; web browser; Apache; TCP sockets; 6 source files, 169 lines of code.
- example_form
PostgreSQL; web browser; Apache; Unix sockets; 4 source files, 86 lines of code.
- example_hash
web browser; Apache; REST API; Unix sockets; 3 source files, 59 lines of code.
- example_json
web browser; Nginx; Unix sockets; 5 source files, 160 lines of code.
- example_multitenant_SaaS
MariaDB; web browser; Apache; Application path; Unix sockets; 7 source files, 312 lines of code.
- example_sendmail
web browser; Apache; Unix sockets; 1 source files, 79 lines of code.
- example_shopping
PostgreSQL; web browser; Apache; REST API; Application path; Unix sockets; 11 source files, 212 lines of code.
- example_stock
MariaDB; command line; web browser; Nginx; Unix sockets; 3 source files, 55 lines of code.
- example_utility
SQLite; command line; Unix sockets; 2 source files, 26 lines of code.
- example_write_report
MariaDB; command line; web browser; Nginx; Unix sockets; 2 source files, 69 lines of code.
- example_docker
See also
Examples (
example_cookies example_create_table example_docker example_file_manager example_form example_hash example_hello_world example_json example_multitenant_SaaS examples example_sendmail example_shopping example_stock example_utility example_write_report )
SEE ALL (
documentation)
Example: stock
Stock ticker example:
- creates a new ticker (stock name and its price) or updates an existing one,
- displays the stock ticker database.
In a nutshell: MariaDB; command line; web browser; Nginx; Unix sockets; 3 source files, 55 lines of code.
Screenshots of application
Stock value is updated:
Showing the stock:
Setup prerequisites
Install Vely - you can use standard packaging tools such as
apt,
dnf,
pacman or
zypper.
Because they are used in this example, you will need to install
Nginx as a web server and
MariaDB as a database.
After installing Vely, turn on syntax highlighting in vim if you're using it:
Get the source code
The source code is a part of Vely installation. It is a good idea to create a separate source code directory for each application (and you can name it whatever you like). In this case, unpacking the source code will do that for you:
tar xvf $(vv -o)/examples/stock.tar.gz
cd stock
Setup application
The very first step is to create an application. The application will be named "stock", but you can name it anything (if you do that, change it everywhere). It's simple to do with
vf:
sudo vf -i -u $(whoami) stock
This will create a new application home (which is "/var/lib/vv/stock") and do the application setup for you. Mostly that means create various subdirectories in the home folder, and assign them privileges. In this case only current user (or the result of "whoami" Linux command) will own those directories with 0700 privileges; it means a secure setup.
Setup the database
Before any coding, you need some place to store the information used by the application. First, you will create MariaDB database "db_stock" owned by user "vely" with password "your_password". You can change any of these names, but remember to change them everywhere here. And then, you will create database objects in the database.
Execute the following logged in as root in
mysql utility:
create database if not exists db_stock;
create user if not exists vely identified by 'your_password';
grant create,alter,drop,select,insert,delete,update on db_stock.* to vely;
use db_stock;
source setup.sql;
exit
Connect Vely to a database
In order to let Vely know where your database is and how to log into it, you will create
database_config_file named "db_stock". This name doesn't have to be "db_stock", rather it can be anything - this is the name used in actual database statements in source code (like
run-query), so if you change it, make sure you change it everywhere. Create it:
echo '[client]
user=vely
password=your_password
database=db_stock
protocol=TCP
host=127.0.0.1
port=3306' > db_stock
The above is a
standard mariadb client options file. Vely uses native MariaDB database connectivity, so you can specify any options that a given database lets you.
Build application
Use
vv utility to make the application:
vv -q --db=mariadb:db_stock
Note usage of --db option to specify MariaDB database and the database configuration file name.
Start your application server
To start the application server for your web application use
vf FastCGI process manager. The application server will use a Unix socket to communicate with the web server (i.e. a reverse-proxy):
This will start 3 daemon processes to serve the incoming requests. You can also start an adaptive server that will increase the number of processes to serve more requests, and gradually reduce the number of processes when they're not needed:
See
vf for more options to help you achieve best performance.
If you want to stop your application server:
Setup web server
This shows how to connect your application listening on a Unix socket (started with
vf) to Nginx web server.
- Step 1:
You will need to edit the Nginx configuration file. For Ubuntu and similar:
sudo vi /etc/nginx/sites-enabled/default
while on Fedora and other systems it might be at:
sudo vi /etc/nginx/nginx.conf
Add the following in the "server {}" section ("/stock" is the application path (see
request_URL) and "stock" is your application name):
location /stock { include /etc/nginx/fastcgi_params; fastcgi_pass unix:///var/lib/vv/stock/sock/sock; }
- Step 2:
Finally, restart Nginx:
sudo systemctl restart nginx
Note: you must not have any other URL resource that starts with "/stock" (such as for example "/stock.html" or "/stock_something" etc.) as the web server will attempt to pass them as a reverse proxy request, and they will likely not work. If you need to, you can change the application path to be different from "/stock", see
request_URL.
Access application server from the browser
Use the following URL(s) to access your application server from a client like browser (see
request_URL). Use actual IP or web address instead of 127.0.0.1 if different.
#Add stock ticker 'XYZ' with stock price 450
http://127.0.0.1/stock/add-stock?name=XYZ&price=450
#Display list of stock tickers
http://127.0.0.1/stock/show-stock
Note: if your server is on the Internet and it has a firewall, you may need to allow HTTP traffic - see
ufw,
firewall-cmd etc.
Access application server from command line
To access your application server from command line (instead through web browser/web server), use "cgi-fcgi" to see the application response:
export REQUEST_METHOD=GET
export SCRIPT_NAME='/stock'
export PATH_INFO='/add-stock'
export QUERY_STRING='name=XYZ&price=450'
cgi-fcgi -connect /var/lib/vv/stock/sock/sock /
export REQUEST_METHOD=GET
export SCRIPT_NAME='/stock'
export PATH_INFO='/show-stock'
export QUERY_STRING=''
cgi-fcgi -connect /var/lib/vv/stock/sock/sock /
Note: to suppress output of HTTP headers, add this before running "cgi-fcgi":
export VV_SILENT_HEADER=yes
If you need to, you can also run your application as a
CGI program.
Run program from command line
Execute the following to run your application from command line (as a command-line utility):
export REQUEST_METHOD=GET
export SCRIPT_NAME='/stock'
export PATH_INFO='/add-stock'
export QUERY_STRING='name=XYZ&price=450'
/var/lib/vv/bld/stock/stock
export REQUEST_METHOD=GET
export SCRIPT_NAME='/stock'
export PATH_INFO='/show-stock'
export QUERY_STRING=''
/var/lib/vv/bld/stock/stock
Note: to suppress output of HTTP headers, add this before running /var/lib/vv/bld/stock/stock program:
export VV_SILENT_HEADER=yes
Note: if running your program as a command-line utility is all you want, you don't need to run an application server.
Files
You are now done with the example! What follows are the source files in this project so you can examine how it works:
Database objects (setup.sql)
Table "stock" with stock name and price is created for this example:
create table if not exists stock (stock_name varchar(100) primary key, stock_price bigint);
Add stock ticker (add_stock.vely)
This requests (/add-stock) saves the names of stock tickers and their prices. It will obtain the stock name and price from the web client via
input-param statement, and then use INSERT SQL to store this data in the database.
2.0
2018
#include "vely.h"
void add_stock()
{
out-header default
@<html>
@<body>
input-param name
input-param price
run-query @db_stock = "insert into stock (stock_name, stock_price) values ('%s', '%s') on duplicate key update stock_price='%s'" : name, price, price error define err no-loop
if (strcmp (err, "0")) {
report-error "Cannot update stock price, error [%s]", err
}
@<div>
@Stock price updated!
@</div>
@</body>
@</html>
}
Show stock tickers (show_stock.vely)
You can view stock tickers in a list. SELECT SQL is used to get all the stocks saved so far, and display them in a table using
run-query and then
query-result to get the query results.
2.0
2018
#include "vely.h"
void show_stock()
{
out-header default
@<html>
@<body>
@<table>
@<tr>
@<td>Stock name</td>
@<td>Stock price</td>
@</tr>
run-query @db_stock = "select stock_name, stock_price from stock" output stock_name, stock_price
@<tr>
@<td>
query-result stock_name
@</td>
@<td>
query-result stock_price
@</td>
@</tr>
end-query
@</table>
@</body>
@</html>
}
See also
Examples (
example_cookies example_create_table example_docker example_file_manager example_form example_hash example_hello_world example_json example_multitenant_SaaS examples example_sendmail example_shopping example_stock example_utility example_write_report )
SEE ALL (
documentation)
Example: utility
In this example, a temperature measuring device will periodically insert temperatures into a database table, along with a timestamp. The purpose is to read this history and output the result from time to time, which can then be piped or sent elsewhere.
In a nutshell: SQLite; command line; Unix sockets; 2 source files, 26 lines of code.
Screenshots of application
Adding a record to the database, for example to record a temperature reading of 91F:
To view the current database in chronological order:
Setup prerequisites
Install Vely - you can use standard packaging tools such as
apt,
dnf,
pacman or
zypper.
Because they are used in this example, you will need to install
Nginx as a web server and
SQLite as a database.
After installing Vely, turn on syntax highlighting in vim if you're using it:
Get the source code
The source code is a part of Vely installation. It is a good idea to create a separate source code directory for each application (and you can name it whatever you like). In this case, unpacking the source code will do that for you:
tar xvf $(vv -o)/examples/utility.tar.gz
cd utility
Setup application
The very first step is to create an application. The application will be named "utility", but you can name it anything (if you do that, change it everywhere). It's simple to do with
vf:
sudo vf -i -u $(whoami) utility
This will create a new application home (which is "/var/lib/vv/utility") and do the application setup for you. Mostly that means create various subdirectories in the home folder, and assign them privileges. In this case only current user (or the result of "whoami" Linux command) will own those directories with 0700 privileges; it means a secure setup.
Setup the database
Before any coding, you need some place to store the information used by the application. First, create SQLite database "db_utility". You can change the database name, but remember to change it everywhere here. And then, create database objects in the database.
Execute the following to create database "utility.db" and also setup the database objects needed for the example:
sqlite3 /var/lib/vv/utility/app/utility.db < setup.sql
The SQLite database will be in the application home directory (see
how_vely_works):
Connect Vely to a database
In order to let Vely know where your database is and how to log into it, you will create
database_config_file named "db_utility". This name doesn't have to be "db_utility", rather it can be anything - this is the name used in actual database statements in source code (like
run-query), so if you change it, make sure you change it everywhere. Create it:
echo '/var/lib/vv/utility/app/utility.db' > db_utility
The above in general is a location of SQLite database and is all that's needed to connect to it.
Build application
Use
vv utility to make the application:
vv -q --db=sqlite:db_utility
Note usage of --db option to specify SQLite database and the database configuration file name.
Run program from command line
Execute the following to run your application from command line (as a command-line utility):
export REQUEST_METHOD=GET
export SCRIPT_NAME='/utility'
export PATH_INFO='/temphist'
export QUERY_STRING='action=record&temp=91'
/var/lib/vv/bld/utility/utility
export REQUEST_METHOD=GET
export SCRIPT_NAME='/utility'
export PATH_INFO='/temphist'
export QUERY_STRING='action=list'
/var/lib/vv/bld/utility/utility
Note: to suppress output of HTTP headers, add this before running /var/lib/vv/bld/utility/utility program:
export VV_SILENT_HEADER=yes
Files
You are now done with the example! What follows are the source files in this project so you can examine how it works:
Recording temperature readings in a table (setup.sql)
There is a "temp" column (temperature reading) and "timest" (time stamp of the reading). Table name is "temps" (temperatures).
create table temps (temp integer, timest text primary key);
Utility for recording temperatures (temphist.vely)
This application will record temperatures, as well as list them. The source code in "temphist.vely" file is fairly self-explanatory. If input parameter "action" is "record", store the temperature into table "temps" and check for any errors. If it is "list", just display all the temperature records in a historical order.
2.0
2018
#include "vely.h"
void temphist() {
out-header default
input-param action
if (!strcmp (action, "record")) {
input-param temp
run-query @db_utility = "insert into temps (temp, timest) values ('%s', current_timestamp)" : temp affected-rows define rc error-text define er no-loop
if (rc != 1) {
@Could not insert temperature reading, error <<p-out er>>.
} else {
@Temperature reading stored.
}
}
else if (!strcmp (action, "list")) {
run-query @db_utility = "select temp, timest from temps order by timest" output temp, timest
@Date: <<query-result timest>>
@Temperature: <<query-result temp>>
end-query
}
}
See also
Examples (
example_cookies example_create_table example_docker example_file_manager example_form example_hash example_hello_world example_json example_multitenant_SaaS examples example_sendmail example_shopping example_stock example_utility example_write_report )
SEE ALL (
documentation)
Example: write report
Builds a report of employees from a database:
- Creates a "reports" directory and a file name with a time stamp, then
- writes the report to a file and also,
- displays the report to a web page.
In a nutshell: MariaDB; command line; web browser; Nginx; Unix sockets; 2 source files, 69 lines of code.
Screenshots of application
Database report is saved into a file (the path to which is displayed) and the report itself is shown in the web page:
Setup prerequisites
Install Vely - you can use standard packaging tools such as
apt,
dnf,
pacman or
zypper.
Because they are used in this example, you will need to install
Nginx as a web server and
MariaDB as a database.
After installing Vely, turn on syntax highlighting in vim if you're using it:
Get the source code
The source code is a part of Vely installation. It is a good idea to create a separate source code directory for each application (and you can name it whatever you like). In this case, unpacking the source code will do that for you:
tar xvf $(vv -o)/examples/write_report.tar.gz
cd write_report
Setup application
The very first step is to create an application. The application will be named "write_report", but you can name it anything (if you do that, change it everywhere). It's simple to do with
vf:
sudo vf -i -u $(whoami) write_report
This will create a new application home (which is "/var/lib/vv/write_report") and do the application setup for you. Mostly that means create various subdirectories in the home folder, and assign them privileges. In this case only current user (or the result of "whoami" Linux command) will own those directories with 0700 privileges; it means a secure setup.
Setup the database
Before any coding, you need some place to store the information used by the application. First, you will create MariaDB database "db_write_report" owned by user "vely" with password "your_password". You can change any of these names, but remember to change them everywhere here. And then, you will create database objects in the database.
Execute the following logged in as root in
mysql utility:
create database if not exists db_write_report;
create user if not exists vely identified by 'your_password';
grant create,alter,drop,select,insert,delete,update on db_write_report.* to vely;
use db_write_report;
source setup.sql;
exit
Connect Vely to a database
In order to let Vely know where your database is and how to log into it, you will create
database_config_file named "db_write_report". This name doesn't have to be "db_write_report", rather it can be anything - this is the name used in actual database statements in source code (like
run-query), so if you change it, make sure you change it everywhere. Create it:
echo '[client]
user=vely
password=your_password
database=db_write_report
protocol=TCP
host=127.0.0.1
port=3306' > db_write_report
The above is a
standard mariadb client options file. Vely uses native MariaDB database connectivity, so you can specify any options that a given database lets you.
Build application
Use
vv utility to make the application:
vv -q --db=mariadb:db_write_report
Note usage of --db option to specify MariaDB database and the database configuration file name.
Start your application server
To start the application server for your web application use
vf FastCGI process manager. The application server will use a Unix socket to communicate with the web server (i.e. a reverse-proxy):
This will start 3 daemon processes to serve the incoming requests. You can also start an adaptive server that will increase the number of processes to serve more requests, and gradually reduce the number of processes when they're not needed:
See
vf for more options to help you achieve best performance.
If you want to stop your application server:
Setup web server
This shows how to connect your application listening on a Unix socket (started with
vf) to Nginx web server.
- Step 1:
You will need to edit the Nginx configuration file. For Ubuntu and similar:
sudo vi /etc/nginx/sites-enabled/default
while on Fedora and other systems it might be at:
sudo vi /etc/nginx/nginx.conf
Add the following in the "server {}" section ("/write_report" is the application path (see
request_URL) and "write_report" is your application name):
location /write_report { include /etc/nginx/fastcgi_params; fastcgi_pass unix:///var/lib/vv/write_report/sock/sock; }
- Step 2:
Finally, restart Nginx:
sudo systemctl restart nginx
Note: you must not have any other URL resource that starts with "/write_report" (such as for example "/write_report.html" or "/write_report_something" etc.) as the web server will attempt to pass them as a reverse proxy request, and they will likely not work. If you need to, you can change the application path to be different from "/write_report", see
request_URL.
Access application server from the browser
Use the following URL(s) to access your application server from a client like browser (see
request_URL). Use actual IP or web address instead of 127.0.0.1 if different.
#Run report
http://127.0.0.1/write_report/write_report
Note: if your server is on the Internet and it has a firewall, you may need to allow HTTP traffic - see
ufw,
firewall-cmd etc.
Access application server from command line
To access your application server from command line (instead through web browser/web server), use "cgi-fcgi" to see the application response:
export REQUEST_METHOD=GET
export SCRIPT_NAME='/write_report'
export PATH_INFO='/write_report'
export QUERY_STRING=''
cgi-fcgi -connect /var/lib/vv/write_report/sock/sock /
Note: to suppress output of HTTP headers, add this before running "cgi-fcgi":
export VV_SILENT_HEADER=yes
If you need to, you can also run your application as a
CGI program.
Run program from command line
Execute the following to run your application from command line (as a command-line utility):
export REQUEST_METHOD=GET
export SCRIPT_NAME='/write_report'
export PATH_INFO='/write_report'
export QUERY_STRING=''
/var/lib/vv/bld/write_report/write_report
Note: to suppress output of HTTP headers, add this before running /var/lib/vv/bld/write_report/write_report program:
export VV_SILENT_HEADER=yes
Note: if running your program as a command-line utility is all you want, you don't need to run an application server.
Files
You are now done with the example! What follows are the source files in this project so you can examine how it works:
Database setup (setup.sql)
For this example, table "employees" is created if it doesn't exist (all its data deleted if it does), and it is populated with some data:
create table if not exists employees (name varchar(50), salary int);
delete from employees;
insert into employees (name, salary) values ('Mike', 50000), ('Joan', 60000), ('Kelly', 70000);
Database report (write_report.vely)
A result set of a query is stored in a string using
write-string with
run-query within in.
write-string simply redirects any output (that would normally go to the client) into a string. This output is then written to a file using
write-file and also sent back to the client.
A few other things are demonstrated here, such as
get-req to obtain the database vendor (i.e. which database is used, as any number of databases can be, such as MariaDB/MySQL, PostgreSQL, SQLite). Also getting current time (
get-time) is shown to produce a timestamp for the file name, and
match-regex to replace spaces with underscores.
2.0
2018
#include "vely.h"
void write_report()
{
out-header default
get-app db-vendor db_write_report to define dbv
@Database vendor used is <<p-out dbv>><br/>
write-string define outmsg
@Employee report<hr/>
run-query @db_write_report="select name, salary from employees order by name" output name, salary
@ -----<br/>
@ Name: <<query-result name>>
@ Salary: <<query-result salary>><br/>
end-query
@-----<br/>
@End report.
end-write-string
get-time to define curr_time format "%A %B %d %Y %l %M %p %Z"
get-req process-id to define pid
match-regex " " in curr_time replace-with "_" result define tstamp
get-app directory to define home_dir
write-string define report_dir
@<<p-out home_dir>>/reports
end-write-string
exec-program "mkdir" args "-p", report_dir status define st
if (st != VV_OKAY && st != VV_ERR_EXIST) {
@Error in creating reports directory
exit-request
}
write-string define report_file
@<<p-out home_dir>>/reports/report_<<pf-out "%lld", pid>><<p-out tstamp>>
end-write-string
write-file report_file from outmsg status st
if (st<0) {
@Error in writing report file <<p-out report_file>> (<<pf-out "%lld", st>>)
exit-request
} else {
@Report file [<<p-out report_file>>] written.<br/>
@Report file content:<br/>
p-out outmsg
}
}
See also
Examples (
example_cookies example_create_table example_docker example_file_manager example_form example_hash example_hello_world example_json example_multitenant_SaaS examples example_sendmail example_shopping example_stock example_utility example_write_report )
SEE ALL (
documentation)
exec-program
Purpose: Execute a program.
exec-program <program path> \
[ args <program arg> [ , ... ] ] \
[ status [ define ] <exit status> ] \
[ ( input <input string> [ input-length <string length> ] ) \
| ( input-file <input file> ) ] \
[ ( output [ define ] <output string> [ output-length [ define ] <output length> ] ) \
| ( output-file <output file> ) ] \
[ ( error [ define ] <error string> ) | ( error-file <error file> ) ]
exec-program executes a program specified in <program path>, which can be a program name without path that exists in the path specified by the PATH environment variable; or an absolute path; or a path relative to the application home directory (see
how_vely_works).
A program can have input arguments (specified with "args" clause), and if there are more than one, they must be separated by a comma. There can be a maximum of 32 input arguments. Each argument <program arg> may contain a comma if it is a string (i.e. double-quoted) or it is an expression within parenthesis.
You can specify a status variable <exit status>, which either must exist, or if it doesn't, use "define" to create it - this variable will have program's exit status. Note that if the program was terminated by a signal, <exit status> will have a value of 128+signal_number, so for example if the program was terminated with signal 9 (i.e. KILL signal), <exit status> will be 137 (i.e. 128+9). Any other kind of abnormal program termination (i.e. program termination that did not end in setting the exit code with exit() or similar) will return 126 as <exit code>.
Specifying program input and output is optional. If program has output and you are not capturing it in any way, the output is redirected to a temporary file that is deleted after exec-program completes.
You can specify an <input string> to be passed to program's standard input (stdin) via "input" clause. If "input-length" is not used, the length of this input is the string length of <input string>, otherwise <string length> bytes is passed to the program, allowing binary input. Alternatively, you can specify a file <input file> (via "input-file" clause) to be opened and directed into program's standard input (stdin).
You can redirect the program's output (which is "stdout") to a file <output file> using "output-file" clause. Alternatively, program's output can be captured in <output string> (via "output" clause) with its length in <output length> (via "output-length" clause), both of which can be created with optional "define". <output string> is
allocated memory.
To get the program's error output (which is "stderr") to a file <error file> using "error-file" clause. Alternatively, program's error output can be captured in <error string> (via "error" clause) which can be created with "define" if not existing. <error string> is
allocated memory.
If <input file> cannot be opened, VV_ERR_READ is reported in <exit status>, and if either <output file> or <error file> cannot be opened, the status is VV_ERR_WRITE.
Do not use system() function as it may be unsafe. Use exec-program instead.
Examples
To simply execute a program that is in the path, without any arguments, input or output:
Run "grep" program using a string as its standard input in order to remove a line that contains "bad line" in it, and outputting the result into "ovar" variable:
exec-program "grep" args "-v", "bad line" "config" input "line 1\nline 2\nbad line\nline 3" output ovar
p-out ovar
Get the list of files in the application home directory into buffer "ovar" (and its length into variable "olen") and then display it:
exec-program "ls" output define ovar output-length define olen
Similar to the above example of listing files, but output results to a file (which is then read and the result displayed), and provide options "-a -l -s" to "ls" program:
exec-program "ls" args "-a", "-l", "-s" output-file "lsout"
read-file "lsout" to define final_res
p-out final_res
Count the lines of file "config" (which is redirected to the standard output of "wc" program) and store the result in variable "ovar" (by means of redirecting the output of "wc" program to it), and then display:
exec-program "wc" args "-l" input-file "config" output define ovar
p-out ovar
See also
Program execution (
exec-program exit-code )
SEE ALL (
documentation)
exit-code
Purpose: Return exit code when running from the
command_line.
exit-code specifies <exit code> (which must be an integer expression) when the program runs from the
command_line.
exit-code can be specified anywhere in the code, and does not mean exiting the program. To exit the program, either use
exit-request or simply allow
request function to reach its end.
When exit-code is not used, the default exit code is 0.
Examples
When the program exits, its exit code will be 12:
exit-code 12
...
exit-request
See also
Program execution (
exec-program exit-code )
SEE ALL (
documentation)
exit-request
Purpose: Exit current request processing.
Exits current request by transferring control directly after the top-level
request dispatcher. If there is an
after_request_handler, it will still execute, unless exit-request is called from
before_request_handler. exit-request will have no effect in
startup_handler because that handler runs before any requests.
exit-request is useful when your request handler has called other functions (i.e. those implemented in
non_request source files), which may have called others (etc.), and there is no need to return in the reverse order, nor to pass any further data back to them; in which case returning one step at a time may be cumbersome and error prone.
In other words, exit-request jumps to the top-level request dispatcher, and the stack of functions called to get to exit-request will be bypassed, thus those functions will not get control back; and they will not perform any additional work, rather simply the next
request will be processed immediately.
Never user C's exit() function, as it will terminate the server process and prevent
exit-code from functioning properly.
Examples
#include "vely.h"
void req_handler()
{
...
exit-request
...
}
See also
Program flow (
exit-request )
SEE ALL (
documentation)
FastCGI client
You can use cgi-fcgi command-line utility to communicate with your
FastCGI application without using a web server. Use it to get responses from the command line. This is useful for shell scripting, debugging, testing etc.
If you have application <app name> running, for example 3 concurrent processes:
then you can send requests to it and receive replies:
export REQUEST_METHOD=GET
export SCRIPT_NAME="/<app name>"
export PATH_NAME="/<request name>"
export QUERY_STRING="par1=val1&par2=val2.."
cgi-fcgi -connect /var/lib/vv/<app name>/sock/sock /<app name>
If you're running your application on TCP/IP instead of Unix socket (as above), for instance on TCP port 3000:
vf -w 3 -p 3000 <app name>
then you can send requests to it and receive replies:
cgi-fcgi -connect 127.0.0.1:3000 /
Examples
The following example will create an application with 3 Vely
request files, and test them as
FastCGI application (both using Unix sockets and TCP) and as a
command_line program. You can execute below bash commands one by one, or save them in a single file and run:
echo '#include "vely.h"
void hello_1()
{
out-header default
@Hello World #1
}' > hello_1.vely
echo '#include "vely.h"
void hello_2()
{
out-header default
@Hello World #2
}' > hello_2.vely
sudo vf -i -u $(whoami) hithere
vv -q
vf -m quit hithere
vf -w 2 hithere
export REQUEST_METHOD=GET
export SCRIPT_NAME="/hithere"
export PATH_INFO="/hello_1"
export VV_SILENT_HEADER=no
cgi-fcgi -connect /var/lib/vv/hithere/sock/sock /hithere
vf -m quit hithere
vf -p 3000 -w 2 hithere
export REQUEST_METHOD=GET
export SCRIPT_NAME="/hithere"
export PATH_INFO="/hello_2"
export VV_SILENT_HEADER=no
cgi-fcgi -connect 127.0.0.1:3000 /
export REQUEST_METHOD=GET
export SCRIPT_NAME="/hithere"
export PATH_INFO="/hello_2"
export VV_SILENT_HEADER=no
/var/lib/vv/bld/hithere/hithere
See also
Running application (
application_setup CGI Client_API command_line containerize_application FastCGI FastCGI_client plain_C_FCGI )
SEE ALL (
documentation)
FastCGI
To run Vely web application with
FastCGI protocol, you need to setup a reverse proxy, i.e. a web server that will forward request and send replies back to clients. Vely application runs as a number of (zero or more) background processes in parallel, processing requests.
Setting up reverse proxy (web server)
To access your application via a reverse proxy (i.e. web server), generally you need to add a proxy directive and restart the web server.
If you use Apache, you need to connect it to your application, see
connect_apache_tcp_socket (for using TCP sockets) and
connect_apache_unix_socket (for using Unix sockets).
If you use Nginx, you need to connect it to your application, see
connect_nginx_tcp_socket (for using TCP sockets) and
connect_nginx_unix_socket (for using Unix sockets).
Starting FastCGI server processes
Use
vf, for example:
which in general will start zero or more background resident process(es) (daemons) that process requests in parallel.
Connection timeout
In a heavy-load environment, a client's connection may be rejected by the server. This may happen if the client runs very slowly due to swapping perhaps. Once a client establishes a connection, it has up to 5 seconds by default to send data; if it doesn't, the server will close the connection. Typically, FastCGI clients send data right away, but due to a heavy load, this time may be longer. To set the connection timeout in milliseconds, set the following variable before starting the application server, for instance:
export "LIBFCGI_IS_AF_UNIX_KEEPER_POLL_TIMEOUT"="8000"
vf -w 1 <app name>
In this case, the timeout is set to 8 seconds.
See also
Running application (
application_setup CGI Client_API command_line containerize_application FastCGI FastCGI_client plain_C_FCGI )
SEE ALL (
documentation)
file-position
Purpose: Set a position or get a current position for an open file.
file-position file-id <file id> \
( set <position> ) | ( get [ define ] <position> ) \
[ status [ define ] <status> ]
file-position will set or get position for a file opened with
open-file, where <file id> is an open file identifier.
If "set" clause is used, file position is set to <position>.
If "get" clause is used, file position is obtained in <position>, which can be created with optional "define".
The optional <status> in "status" clause will be VV_OKAY if set/get succeeded, or VV_ERR_POSITION if not. <status> can be created with optional "define".
Note that setting position past the last byte of file is okay for writing - in this case the bytes between the end of file and the <position> are filled with null-bytes when the write operation occurs.
Examples
Open file "testwrite" and set file byte position to 100, then obtain it later:
open-file "testwrite" file-id define nf
file-position file-id nf set 100
...
file-position file-id nf get define pos
See also
open-file.
See also
Files (
close-file copy-file delete-file file-position file_storage file_uploading lock-file open-file read-file read-line rename-file stat-file temporary_file uniq-file unlock-file write-file )
SEE ALL (
documentation)
File storage
Vely provides a file directory that you can use for any general purpose, including for storing
temporary_files. This directory is also used for automatic upload of files from clients. It provides a two-tier directory system, with sub-directories automatically created to spread the files for faster access.
Files in Vely file directory are located in the sub-directories of:
/var/lib/vv/<app_name>/app/file
If you wish to place this directory in another physical location, see
vv.
You can create files here by means of
uniq-file, where a new unique file is created - with the file name made available to you. This directory is also used for uploading of files from clients (such as web browsers or mobile device cameras) - the uploading is handled automatically by Vely (see
input-param).
In general, a number of sub-directories is created within the file directory that contain files, with (currently) the maximum of 40,000 directories with unlimited files per each directory (so if you had 50,000 files per each directory, then you could store a total of about 2 billion files). This scheme also allows for faster access to file nodes due to relatively low number of sub-directories and files randomly spread per each such sub-directory. The random spreading of files across subdirectories is done automatically.
Do not rename file names or sub-directory names stored under file directory.
See also
Files (
close-file copy-file delete-file file-position file_storage file_uploading lock-file open-file read-file read-line rename-file stat-file temporary_file uniq-file unlock-file write-file )
SEE ALL (
documentation)
File uploading
Purpose: Upload a file to server.
Files uploaded via client (such as a browser, curl etc.) are
input-parameters.
Vely uploads files for you automatically, meaning you do not have to write any code for that specific purpose. An uploaded file will be stored in
file_storage, with path and name of such a file generated by Vely to be unique. For example, a file uploaded might be named "/home/user/file/d0/f31881". When file is uploaded, the following input parameters can be obtained, assuming "name" attribute of "input" element is "myfile":
- "myfile_filename" is the name of the file as provided by the client.
- "myfile_location" is the full path on the server where file is stored (such as "/home/user/file/d0/f31881" for example)
- "myfile_ext" is the extension of the file, for example ".jpg" or ".pdf". Note that ".jpeg" is always showing as ".jpg". The extension is always lower case.
- "myfile_size" is the size of the file uploaded. See vv for setting the maximum file upload size.
For example, for an HTML form which is uploading a file named "myfile", such as
<input type='file' name='myfile'>
your code that handles this might be:
input-param myfile_filename
input-param myfile_location
input-param myfile_ext
input-param myfile_size
You have uploaded file <<p-web myfile_filename>> to a server file at <<p-web myfile_location>>
See also
Files (
close-file copy-file delete-file file-position file_storage file_uploading lock-file open-file read-file read-line rename-file stat-file temporary_file uniq-file unlock-file write-file )
SEE ALL (
documentation)
finish-output
Purpose: Finishes the request output.
finish-output will flush out and conclude all of the request's output (see
output_statement). Any such output afterwards will silently fail to do so. As far as the client is concerned (or the user running a
command_line program), all the output from the request is complete.
This statement is useful when you need to continue work after the request's output is complete. For example, if the request is a long-running one, you can inform the client that the job has started, and then take any amount of time to actually complete the job, without worrying about client timeouts. The client can inquire about the job status via a different request, or be informed via email etc.
Examples
See also
Output (
finish-output flush-output output_statement p-dbl pf-out pf-url pf-web p-num p-out p-path p-url p-web )
SEE ALL (
documentation)
flush-output
Purpose: Flush output.
Use flush-output statement to flush any pending output to the client (such as
FastCGI_client) or from the
command_line application.
This can be useful if the complete output would take longer to produce and intermittent partial output would be needed.
Note that whether the client will actually receive the output in this fashion depends on several factors, including the client itself and any intermediaries such as proxy servers.
Examples
In this case the complete output may take at least 20 seconds. With flush-output, the message "This is partial output" will be flushed out immediately.
@This is partial output
flush-output
sleep(20);
@This is final output
See also
Output (
finish-output flush-output output_statement p-dbl pf-out pf-url pf-web p-num p-out p-path p-url p-web )
SEE ALL (
documentation)
get-app
Purpose: Obtain data that describes the application.
get-app \
name | directory | trace-directory | file-directory \
| db-vendor <database configuration> | upload-size \
| path | process-data \
to [ define ] <variable>
Application-related variables can be obtained with get-app statement. The following application variables can be obtained (they are all strings except upload-size):
- "name" returns the name of your application, as specified when created (see vf).
- "directory" is the directory where your application resides, i.e. the application home directory (see how_vely_works).
- "trace-directory" is the directory where your application trace files are written (if enabled, see how_vely_works).
- "file-directory" is the directory where Vely file storage system is, i.e. file_storage.
- "db-vendor" is the database vendor name of database given by <database configuration> (as used for example in run-query). You can use it to create database specific conditions. The database vendor name can be compared against predefined string constants VV_MARIADB (for MariaDB database), VV_POSTGRES (for Postgres database) and VV_SQLITE (for SQLite database).
- "path" is the leading path of the URL request that can be used to build web forms and links leading back to your application. It returns the same value as p-path.
- "process-data" obtains the pointer to global_process_data.
- "upload-size" is the maximum allowed size of an uploaded file - this is a number.
Note that all the string values above (except "process-data") should be treated as constants - do not change them as that may cause program to malfunction. If you want to alter any of these values, make a copy first (see
copy-string).
Examples
Get the name of Vely application:
get-app name to define appname
Get the vendor of database db:
get-app db-vendor db to define dbv
if (!strcmp (dbv, VV_POSTGRES)) {
}
See also
Application information (
get-app set-app )
SEE ALL (
documentation)
get-cookie
Purpose: Get cookie value.
get-cookie [ define ] <cookie value> = <cookie name>
get-cookie obtains the value of a cookie with the name given by <cookie name>. A cookie would be obtained from an incoming request from the client (such as web browser) or it would be set using
set-cookie.
The value of cookie is stored in <cookie value>. If it does not exist, you can create it within the statement by using "define" clause. <cookie value> is
allocated memory.
Cookies are often used to persist user data on the client, such as for maintaining session security or for convenience of identifying the user or its actions.
Examples
Get value of cookie named "my_cookie_name" - variable my_cookie_value will hold its value:
get-cookie define my_cookie_value="my_cookie_name"
See also
Cookies (
delete-cookie get-cookie set-cookie )
SEE ALL (
documentation)
get-hash
Purpose: Get usage specifics for a hash table.
get-hash <hash > \
( length [ define ] <length> ) \
| ( size [ define ] <size> ) \
| ( average-reads [ define ] <reads> )
get-hash provides usage specifics of a hash table <hash> (created by
new-hash).
Use "length" clause to obtain its <length> (i.e. the number of elements stored in it), "size" clause to obtain its <size> (i.e. the number of "buckets", or possible hash codes) and "average-reads" clause to obtains the average number of <reads> (i.e. how many string comparisons are needed on average to find a key).
Each of these number variables can be created with "define".
This information may be useful in determining the performance of a hash, and whether
resize-hash is indicated.
Examples
get-hash h length define l size define s average-reads define r
See also
Hash table (
get-hash new-hash purge-hash read-hash resize-hash write-hash )
SEE ALL (
documentation)
get-req
Purpose: Obtain data that describes the input request.
get-req \
data | errno | error | cookie-count | cookie <cookie index> \
| arg-count | arg-value <arg index> | input-count | \
| input-value <input index> | input-name <input index> \
| header <header> | referring-url | method \
| content-type | trace-file | process-id | name \
to [ define ] <variable>
Variables related to an input request can be obtained with get-req statement and the result stored into <variable>. The following request variables can be obtained (they are strings except where stated otherwise):
- "data" obtains the pointer to global_request_data.
- "errno" obtains the integer value of operating system "errno" tied to a last Vely statement that can return a status code. The "errno" value is saved internally; and restored here. It means that if "errno" changed for whatever reason since such Vely statement, you will still obtain the correct value. See error_code for an example. Note that errno value is undefined if there is no error, and can be 0 if the error is reported by Vely and not by operating system.
- "error" returns the error message (a string) that correlates to "errno" value. The result of "error" clause is allocated memory.
- "cookie-count" returns the number of cookies. This means any cookies received from the client plus any cookies added (with set-cookie) in the application minus any cookies deleted (with delete-cookie).
- "cookie" returns the cookie value specified by <cookie index> (a number (0, 1, 2...) up to number of cookies), for instance in the block of C code:
num i;
get-req cookie-count to define cookie_c
for (i = 0; i < cookie_c; i++) {
get-req cookie i to define cookie_val
pf-web "cookie %s\n", cookie_val
@<br/>
}
In this example, we get the number of input parameters, and then print out each name/value pair.
- "arg-count" is the number of input arguments to your application (passed from a program caller, see vv).
- "arg-value" is the string value of a single element from the array of input arguments, specified by <arg_index>. This array is indexed from 0 to one less than value obtained by "arg-count". Here is an example of using arg-count and arg-value:
get-req arg-count to define ac
pf-out "Total args [%lld]", ac
num i;
for (i=0; i<ac; i++) {
get-req arg-value i to define av
pf-out "%s\n", av
}
This code will display the number of input arguments (as passed to main() function of your program, excluding the first argument which is the name of the program), and then all the arguments. If there are no arguments, then variable 'ac' would be 0. See vv on passing arguments to your application.
- "input-count" is the number of input parameters (from a request). Together with input-name and input-value, this lets you examine all input parameters in a request, especially in situations where the parameters are dynamic in nature.
- "input-name" is the string value of a single element from the array of input parameter names (from a request), specified by <input_index>. This array is indexed from 0 to one less than value obtained by "input-count". See "input-value" for an example.
- "input-value" is the string value of a single element from the array of input parameter values (from a request), specified by <input_index>. This array is indexed from 0 to one less than value obtained by "input-count". For example:
num i;
get-req input-count to define input_c
for (i = 0; i < input_c; i++) {
get-req input-name i to define input_n
get-req input-value i to define input_v
pf-web "Input parameter #%lld, name is %s, value is %s\n", i, input_n, input_v
@<br/>
}
In this example, we get the number of cookies, and then print out each. The cookie value is the full cookie string, including expiration date, strictness etc.
- "header" is the value of HTTP request header <header> that is set by the client. For example, if the HTTP request contains header "My-Header:30", then hval would be "30":
get-req header "My-Header" to define hval
Note that not all HTTP request headers are set by the caller. For example, SERVER_NAME or QUERY_STRING are set by the web server, and to get such headers, use get-sys.
- "method" is the request method. This is a number with values of VV_GET, VV_POST, VV_PUT, VV_PATCH or VV_DELETE for GET, POST, PUT, PATCH or DELETE requests, respectively. If it is not any of those commonly used ones, then the value is VV_OTHER and you can use get-sys with "web-environment" clause to obtain "REQUEST_METHOD" variable.
If you are writing a REST API, then you can use the method to implement Create, Read, Update, Delete (CRUD) operations, for instance:
get-req method to define request_method
if (request_method == VV_POST) {
}
if (request_method == VV_GET) {
}
if (request_method == VV_PUT) {
}
if (request_method == VV_PATCH) {
}
if (request_method == VV_DELETE) {
}
- "content-type" is the request content type. It is a string and generally denotes the content type of a request-body, if included in the request. Common examples are "application/x-www-form-urlencoded", "multipart/form-data" or "application/json".
- "referring-url" is the referring URL (i.e. the page from which this request was called, for example by clicking a link).
- "trace-file" is the full path of the trace file for this request (if enabled, see vv).
- "process-id" is the "PID" (process ID) number of the currently executing process, as an integer.
- "name" is the request name as specified in the request_URL.
Note that all the string values above (except "data") should be treated as constants - do not change them as that may cause program to malfunction. If you want to alter any of these values, make a copy first (see
copy-string).
Examples
Get the name of current trace file:
get-req trace-file to define trace_file
See also
Request information (
get-req input-param request-body set-input set-req )
SEE ALL (
documentation)
get-sys
Purpose: Obtain data that describes the system.
get-sys \
environment <var name> | web-environment <var name> \
| os-name | os-version \
to [ define ] <variable>
System-describing variables can be obtained with get-sys statement and the result stored into <variable>. The following system variables can be obtained:
- "environment" returns the name of a given environment variable <var name> from the Operating System, such as in the following example, the HOME variable (i.e. the path to user's home directory):
get-sys environment "HOME" to define home_dir
Generally, environmental variable needs to be set before starting the vf program manager, or set during the program execution.
- "web-environment" returns the name of a given environment variable <var name> from the web server, such as in the following example, the QUERY_STRING variable (i.e. the actual query string from URL):
get-sys web-environment "QUERY_STRING" to define home_dir
Note that the same name can be used for both Operating System environment variable (the "environment" clause) and for Web Server environment variable ("web-environment" clause), hence two separate clauses for them.
- "os-name" is the name of Operating System.
- "os-version" is the version of Operating System.
Note that all the string values above should be treated as constants - do not change them as that may cause program to malfunction. If you want to alter any of these values, make a copy first (see
copy-string).
Examples
Get the name of the Operating System
get-sys os-name to define os_name
See also
System information (
get-sys )
SEE ALL (
documentation)
get-time
Purpose: Get time.
get-time to [ define ] <time var> \
[ timezone <tz> ] \
[ year <year> ] \
[ month <month> ] \
[ day <day> ] \
[ hour <hour> ] \
[ minute <minute> ] \
[ second <second> ] \
[ format <format> ]
get-time produces <time var> variable that contains string with time. <time var> is
allocated memory.
If none of "year", "month", "day", "hour", "minute" or "second" clauses are used, then current time is produced. If variable <time var> does not exist, you can use define clause to create it within the statement.
Use timezone to specify that time produced will be in timezone <tz>. For example if <tz> is "EST", that means Eastern Standard Time, while "MST" means Mountain Standard Time. The exact way to get a full list of timezones recognized on your system may vary, but on many systems you can use:
timedatectl list-timezones
So for example to get the time in Phoenix, Arizona you could use "America/Phoenix" for <tz>. If timezone clause is omitted, then time is produced in "GMT" timezone by default. DST (Daylight Savings Time) is automatically adjusted.
Each variable specified with "year", "month", "day", "hour", "minute" or "second" is a time to be added or subtracted. For example "year 2" means add 2 years to the current data, and "year -4" means subtract 4 years, whereas "hour -4" means subtract 4 hours, etc. So for example, a moment in time that is 2 years into the future minus 5 days minus 1 hour is:
get-time to time_var year 2 day -5 hour -1
<format> allows you to get the time in any string format you like, using the specifiers available in C "strftime". For example, if <format> is "%A, %B %d %Y, %l:%M %p %Z", it will produce something like "Sunday, November 28 2021, 9:07 PM MST". The default format is "UTC/GMT" format, which for instance, is suitable for use with cookie timestamps, and looks something like "Mon, 16 Jul 2012 00:03:01 GMT".
Examples
To get current time in "GMT" timezone, in a format that is suitable for use with
set-cookie (for example to set expiration date):
get-time to define mytime
To get the time in the same format, only 1 year and 2 months in the future:
get-time to define mytime year 1 month 2
An example of a future date (1 year, 3 months, 4 days, 7 hours, 15 minutes and 22 seconds into the future), in a specific format (see "strftime"):
get-time to time_var timezone "MST" year 1 month 3 day 4 hour 7 minute 15 second 22 format "%A, %B %d %Y, %l:%M %p %Z"
See also
Time (
get-time )
SEE ALL (
documentation)
Global process data
The purpose of global-process data is to help with use of global cross-request variable(s) anywhere in your code. The difference from
global_request_data is in the scope: global request data is valid only for a single request, while global-process data is valid for all requests processed in sequence by a single process (see
how_vely_works).
While you can create global variables in C (with C's "extern" to make them globally available), Vely's global-process data is an easier and more structured way to share data globally across requests. This way, you do not have to define and maintain global variables. It also makes it more maintainable as such variables are well-encapsulated and easy to track (for instance in code reviews).
What is global-process data
Global-process data is a generic pointer (void*) that points to any memory you wish to be shared among all your code across all the requests that any given process serves. This data cannot be shared between different processes. It is usually a structure containing information globally pertinent to your application that is shared between separate requests. The pointer's scope is a lifetime of the process and thus spans all the
requests it serves. The global-process data pointer is initialized to NULL before
startup_handler executes. It is stored in Vely's internal process context structure so it can be used anywhere.
Setting
set-app process-data <data>
where <data> is a pointer to any memory you wish to use anywhere in your code across all process' requests. This memory must be unmanaged; see
memory_handling and the example below.
Getting
Global-process data can be obtained anywhere in your code with:
get-app process-data to <data>
where <data> is a pointer to any type.
Usage
Typically you would define a structure or a typedef that encapsulates any data that needs to be shared throughout your code in all requests in any given process. Different processes have separate process data that cannot be shared between them. For this reason, global-process data is not the same as global application data; rather it is global within any given process and not beyond that process.
In
startup_handler, you would create a variable (or an array) of this type by using unmanaged
new-mem that producess cross-request memory - this is done by using "off" clause in
manage-memory. Use
new-mem for any members that need allocating. Initialize anything that needs it. Finally, switch back to managed memory by using "on" clause in manage-memory statement.
The reason for using unmanaged memory is because otherwise the data would be automatically released at the end of the following request and would not stay allocated for the life of the process.
Next, save the pointer to the variable (or an array) you created by using
set-app.
Finally, anywhere you need to set or get any data, use
get-app with "process-data" clause to get the pointer and manipulate or read your global-process data. Don't forget that any manipulation must be in unmanaged mode as well.
Examples
Suppose your application has an include file my.h in which you define type "procdata":
#ifndef _MY
#define _MY
typedef struct s_procdata {
bool some_flag;
bool another_flag;
char *ptr;
} procdata;
#endif
Your
startup_handler (i.e. file _startup.vely) might look like this - note that my.h is included, providing your type definition "procdata":
#include "vely.h"
#include "my.h"
void _startup () {
procdata *rd;
manage-memory off
new-mem rd size sizeof(procdata)
manage-memory on
rd->some_flag = false;
rd->another_flag = false;
set-app process-data rd
}
In the above code, a new pointer "rd" of type "procdata" is created with
new-mem. Data initialization takes place - anything that needs initialization should be initialized. Finally, pointer "rd" is saved to process' internal structure with
set-app, so it can be used in any request for the life of the process.
In your code, wherever it's needed, you can obtain this data into a local pointer of the same type "procdata" (in this case pointer name is "mydata"). You can do that with
get-app and then examine or set any global-process variable you wish:
#include "vely.h"
#include "my.h"
void mycode () {
...
procdata *mydata;
get-app process-data to mydata
if (mydata->another_flag) {
mydata->some_flag = true;
manage-memory off
resize-mem my_data->ptr size 1024
manage-memory on
}
}
See also
Process (
global_process_data )
SEE ALL (
documentation)
Global request data
The purpose of global-request data is to facilitate using global variable(s) within a single
request anywhere in your code. This is different from
global_process_data which is used across many requests.
You can of course create global variables in C (and use C's "extern" to make them available anywhere). Vely's global-request data is an easier and more structured way to share data globally within a single request, without having to actually define and maintain global variables. It also makes it more maintainable because the usage of shared global information is well-encapsulated and easy to track (for instance in code reviews).
What is global-request data
Global-request data is a generic pointer (void*) that points to any memory you wish to be shared among all your code within a single request. This memory is usually a structure containing information globally pertinent to your application. The pointer's scope is a single
request, and it is initialized to NULL before each request. It is stored in Vely's internal request structure so it can be used anywhere.
Setting
where <data> is a pointer to any memory you wish to use anywhere in your code.
Getting
Global-request data can be obtained anywhere in your code with:
where <data> is a pointer to any type.
Usage
Typically you would define a structure or a typedef that encapsulates any data that needs to be shared throughout your code during a single request processing.
Then in
before_request_handler, you would create the variable (or an array) of this type by using
new-mem - this way the memory is automatically released at the end of the request. Use
new-mem for any members that need allocating, thus eliminating the possibility of memory leaks. Initialize anything that needs it.
Next, save the pointer to the variable (or an array) you created by using
set-req.
Finally, anywhere you need to set or get any data, use
get-req to get the pointer and manipulate or read your global-request data.
Examples
Suppose your application has an include file my.h in which you define type "reqdata":
#ifndef _MY
#define _MY
typedef struct s_reqdata {
bool some_flag;
bool another_flag;
char *ptr;
} reqdata;
#endif
Your
before_request_handler might look like this - note that my.h is included, providing your type definition "reqdata":
#include "vely.h"
#include "my.h"
void _before () {
reqdata *rd;
new-mem rd size sizeof(reqdata)
rd->some_flag = false;
rd->another_flag = false;
set-req data rd
}
In the above code, a new pointer "rd" to type "reqdata" is created with
new-mem. Data initialization takes place - anything that needs initialization should be initialized. Finally, pointer "rd" is saved to request's internal structure with
set-req, so it can be used anywhere during request processing.
In your code, wherever it's needed, you can obtain this data into a local pointer of the same type "reqdata" (in this case pointer name is "mydata"). You can do that with
get-req and then examine or set any global variable you wish:
#include "vely.h"
#include "my.h"
void mycode () {
...
reqdata *mydata;
get-req data to mydata
if (mydata->another_flag) {
mydata->some_flag = true;
my_data->ptr = "some data";
}
}
See also
Requests (
after_request_handler before_request_handler building_URL getting_URL global_request_data non_request normalized_URL request request_URL startup_handler vely_dispatch_request )
SEE ALL (
documentation)
hash-string
Purpose: Hash a string.
hash-string <string> to [ define ] <result> \
[ binary [ <binary> ] [ output-length [ define ] <output length> ] \
[ digest <digest algorithm> ]
hash-string produces by default a SHA256 hash of <string> (if "digest" clause is not used), and stores the result into <result> which can be created with optional "define". <result> is
allocated memory. You can use a different <digest algorithm> in "digest" clause (for example "SHA3-256"). To see a list of available digests:
openssl list -digest-algorithms
If "binary" clause is used without optional boolean expression <binary>, or if <binary> evaluates to true, then the <result> is a binary string that may contain null-characters. With the default SHA256, it is 32 bytes in length, while for instance with SHA3-384 it is 48 bytes in length, etc.
Without "binary" clause, or if <binary> evaluates to false, the <result> is null-terminated and each binary byte is converted to two hexadecimal characters ("0"-"9" and "a"-"f"), hence <result> is twice as long as with "binary" clause.
The actual length of <result> (regardless of whether "binary" clause is used or not) can be obtained in the optional "output-length" clause in <output length>, which can be created with optional "define".
Examples
String "hash" will have a hashed value of the given string, an example of which might look like "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855":
hash-string "hello world" to define hash
Using a different digest:
hash-string "hello world" to define hash digest "sha3-384"
Producing a binary value instead of a null-terminated hexadecimal string:
hash-string "hello world" to define hash digest "sha3-384" binary output-length define outlen
See also
Encryption (
decrypt-data derive-key encrypt-data hash-string random-crypto random-string )
SEE ALL (
documentation)
How vely works
Creating an application
A Vely application <app name> is created by an application owner, i.e. an Operating System user who will own its processes and data, by using
vf, such as:
sudo vf -i -u $(whoami) <app name>
This will create directory structure described here. Vely application can be used as a
FastCGI server, a
command_line program or a
CGI program. Note that vf is the only Vely utility requiring sudo privileges, and only for application creation.
If you will build an application from source code, the above vf command must run in the directory that contains the application source code. Each application must be in its own separate directory which contains all of its source code.
How Vely works: code, requests and processes
In a single step, Vely code (.vely files) is precompiled entirely into C, linked, and ready to run as a command-line program or as an application server, or both. Error reporting is based on Vely source code (.vely files), though you can specify generated code lines to be used.
vv will preprocess .vely source files and generate C code for supported
statement_APIs (database, file, strings etc.). Then, vv builds both a command-line program and a FastCGI application server executable for application <app name> (the name <app name> is specified when running
vf with "-i" flag to create the application). Executables created are located in directory:
/var/lib/vv/bld/<app name>
A FastCGI application runs as a daemon, i.e. it runs in the background and does not exit, serving incoming requests. Typically, a number of FastCGI processes are started to serve incoming processes in parallel; each process serves a single request at a time and thus can serve great many requests during its lifetime; in adaptive mode, the number of processes running may vary according to the load - including zero running processes for near-zero memory usage.
A command-line program works exactly the same way as a FastCGI application; it takes the same input and produces the same output. A command-line program runs as a single process and processes a single request.
.vely files and requests
Files with the .vely extension are Vely source files. You must have at least one Vely source file (.vely file). If a Vely source file starts with an underscore, it is a
non_request file, which can implement anything; otherwise it is a
request file. A request file implements a request handler, which handles a
request_URL sent by the client (such as reverse proxies like Apache or Nginx) or provided to
command_line program via environment variables.
Requests and request handlers
To specify the request handler, specify its name in the URL after the application path (see
request_URL). A request handler must implement a function named after the base name of .vely file. For example, in a URL such as:
/stocks/add-stock?ticker=ABC&price=130
the application path is "/stocks" (by default it's the application name) and the request name is "add_stock" (hyphens are converted to underscore by Vely). The request name handler must be implemented as function
in file add_stock.vely.
This correlation between requests and file names makes it easy to find the code you want and to maintain it. Vely will generate the code that will automatically route input requests to the appropriate code based on the request name, so in the above example Vely will automatically invoke function add_stock() implemented in file "add_stock.vely" with input parameters "ticker" and "price" (and their values) available via
input-param statement:
void add_stock() {
input-param ticker
input-param price
...
}
Non-request source code
Not every source code file was meant to handle a request. You might have a number of
non_request files that implement other code, such as commonly used functions. Such files are non-request source code and their names must start with underscore ("_").
For example, file "_display_table.vely" might implement a common HTML table displaying code, which is used in various request handlers.
Some non-request source files have predefined meaning: "_startup.vely" (
startup_handler), "_before.vely" (
before_request_handler) and "_after.vely" (
after_request_handler).
Building an application, Makefile
Vely builds an application by looking at .vely and .h files present in the current directory. Therefore you don't have to write a Makefile.
You don't have to specify what source code your application uses - all .vely files will be automatically picked up and compiled, and
request handlers (.vely files that don't start with an underscore) are used to generate
vely_dispatch_request.vely file (a request-dispatcher). Thus, it is easy to view at a glance what kind of requests an application serves and what is its general purpose. Files with .h extension are standard C include files and are automatically included.
You can see the default implementations for all auto-generated files in directory
/var/lib/vv/bld/<app name>
An example of building a Vely application is:
"-q" options is the most important one - it specifies that you intend to build. In this instance, MariaDB database (named "notes") is used.
Vely builds an application using standard Linux "make" mechanism. Note that when you change the command line options (see
vv) in a build statement like above, it has the effect of "make clean", recompiling source files.
Directories and files
While you can keep and compile Vely source files in any directory, the directories used by Vely are always under /var/lib/vv directory. A Vely application is always owned by a single Operating System user, while different applications can be owned by different users. This is the directory structure:
- /var/lib/vv/bld/<app name>
Build directory is where your FastCGI application executable is built, with name <app name>.fcgi. The command-line executable is named <app name>. This is a scratch-pad directory, so do not alter files in it, or use for anything else.
- /var/lib/vv/<app name>
Application directory. All application data, including internals such as sockets, are kept here. Each such directory is owned by its owner with 700 privileges, preventing other users from accessing its files.
- /var/lib/vv/<app name>/app
Application home directory. This is the current working directory when your application runs. Copy here any files and directories your application needs.
- /var/lib/vv/<app name>/app/file
file_storage used for uploads and new document creation. Do not write there directly; Vely does that for you, such as with uniq-file for instance, or when uploading files.
- /var/lib/vv/<app name>/app/trace
Process tracing is written in per-process files if "--trace" option is used (see vv). Note that a special file "backtrace" is appended to when program crashes (or report-error statement is used), and this file shows a full source backtrace, allowing for easier debugging. In order to see Vely source code lines, you must use "--debug" option. You can use "--c-lines" in order to show the source code lines of the generated C code, instead of Vely code.
- /var/lib/vv/<app name>/app/db
Database configuration directory contains the database_config_files used by Vely to connect to databases.
While Vely directories are fixed, you can effectively change their location by creating a soft link. This way, your directories and files can be elsewhere, even on a different disk. For example, to house your files on a different disk:
ln -s /home/disk0/file /var/lib/vv/<app name>/app/file
Memory allocation
Vely uses its own memory allocator based on standard Linux malloc family of functions. It is used internally and for allocating the results of any statements (each statement's documentation specifies which results, if any, are such). The memory will be automatically freed upon the completion of the request - you can also explicitly free them if you need to. This approach avoids costly errors, crashes and memory leaks. See
memory_handling.
Data types
Vely
statement_APIs use mostly strings (char* type only, i.e. no "const char*") and numbers ("num" type, which is "long long" for maximum range of uses, "num32" for a 32-bit integer, "dbl" for a double precision number), "bool" (with true and false value as provided by stdbool.h) and other data types as specified. When a "number" is specified anywhere without an additional qualification, it is assumed to be of "num" type. If a type is not specified, it is either a string or a number, depending on the context in which it is used - in those cases the type is not specified for simplicity.
Names of objects
Do not use object names (such as variables, functions, types) that start with "_vv", "_vely", "vv" or "vely" as those are reserved by Vely.
See also
General (
deploying_application how_vely_works quality_control rename_files SELinux vely vely_architecture vely_removal vf vv why_C_and_Vely )
SEE ALL (
documentation)
Inline code
Purpose: Inline Vely code in string statements.
You can write Vely code within other Vely statements that build strings, whether for output (
output_statement) or to build other strings (
write-string), by using << and >> delimiters. There is no need for white space between the delimiters and Vely code, i.e. you could either write
or
to the same effect.
Examples
query-result statement displays the result of a query, and in the following code it's used to display results on the same line as other output (i.e. as inline):
run-query
@<tr>
@ <td>
@ First name is << query-result firstName >>
@ </td>
@ <td>
@ Last name is << query-result# employees, lastName >>
@ </td>
@</tr>
end-query
In the code below, "some_function()" is a C function that uses Vely code to output some text, and it's used inline to output "Hello world":
@Hello <<.some_function();>>
(note the usage of
dot statement to use any C expression, and finishing with semi-colon as a C statement). Function "some_function()" would simply output "world":
void some_function()
{
@world
}
A
write-string can be built with other Vely statements inlined, in this case we print the value of another string, resulting in "There is 42 minutes left!":
char * mins="42";
(( define my_string
There is <<p-out mins>> minutes left!
))
See also
Language (
dot inline_code statement_APIs syntax_highlighting unused-var )
SEE ALL (
documentation)
input-param
Purpose: Get input parameter.
To obtain input parameters from an incoming
request, use input-param. Whether it's a web or command-line application, input parameters are specified as name/value pairs (see
request_URL). The value of input parameter <name> will be in the string variable with the same name. Input parameter values are trimmed for whitespace (both on left and right).
Input parameter name can be made up of alphanumeric characters, hyphen or underscore only and cannot start with a digit. Note that a hyphen is automatically converted to underscore, so an input parameter "some-parameter" in the HTTP request will be known in your code as "some_parameter". For example in URL:
http://<your web server>/<app name>/some-request?my-par1=val1&my-par2=val2
the request name will be "some_request" (handled by some_request.vely source code), and input parameters will be "my_par1" and "my_par2":
input-param my_par1
input-param my_par2
File uploads
File uploads are handled as input parameters as well, see
file_uploading.
Using input parameters
As an example, for HTML form input parameters named "param1" with value "value1" and "param2" with value "value2":
<input type='hidden' name='param1' value='value1'>
<input type='hidden' name='param2' value='value2'>
you can get these parameters and print out their values by using:
input-param param1
input-param param2
p-web param1
p-web param2
the output of which is:
A request may be in the form of a web link URL, and getting the parameter values is the same:
http://<your web server>/<app name>/<request name>¶m1=value1¶m2=value2
Duplicate parameter names
If there are multiple input parameters with the same name, such as
http://<web address>/<app name>/<request name>?par=val1&par=val2
the value of input parameter "par" is undefined. Do not specify multiple input parameters with the same name.
See also
Request information (
get-req input-param request-body set-input set-req )
SEE ALL (
documentation)
json-utf8
Purpose: Convert JSON text to UTF8 string.
json-utf8 <text> \
[ status [ define ] <status> ] \
[ error-text [ define ] <error text> ]
json-utf8 will convert JSON string value <text> to UTF8. <text> itself will hold the resulting UTF8 string. If you don't wish <text> to be modified, make a copy of it first (see
copy-string). See
utf8-json for the reverse conversion and data standards information.
You can obtain the optional <status> in "status" clause, which can be created with optional "define". <status> is VV_OKAY if successful, or VV_ERR_UTF8 if there was an error, in which case optional <error text> in "error-text" clause (which can be created with optional "define") will contain the error message.
Examples
char txt[] = "\u0459\\\"Doc\\\"\\n\\t\\b\\f\\r\\t\\u21d7\\u21d8\\t\\u25b7\\u25ee\\uD834\\uDD1E\\u13eb\\u2ca0\\u0448\\n\\/\\\"()\\t";
json-utf8 txt status define txt_status error-text define txt_error
char utf8[] = "љ\"Doc\"\n\t\b\f\r\t⇗⇘\t▷◮𝄞ᏫⲠш\n/\"()\t";
if (strcmp (utf8, txt) || txt_status != VV_OKAY || txt_error[0] != 0) {
@Error in converting JSON string to UTF8
}
See also
UTF8 (
json-utf8 utf8-json )
SEE ALL (
documentation)
License
Licensing and copyright
Vely is Free Open Source software licensed under
Eclipse Public License 2.
Vely is copyright (c) 2017 Dasoftver LLC.
Discussion
The following discussion is not legal advice.
Vely makes use of the following dynamically-linked libraries (with copyright by their respective owners), and only if your application actually uses them:
These libraries are installed as Vely package dependencies by the Operating System packager (such as apt, dnf, pacman or zypper), but are not distributed with Vely. Vely does not link to any outside static libraries.
Vely uses FNV-1a hash function, which is released in the public domain (see
wikipedia page) and is not patented (see
Landon Noll's web page).
Vely source code uses
SPDX, an open ISO standard for communicating sofware bill of material information (such as licenses), to improve on open source licensing compliance for companies and communities.
With regards to any application written using Vely, the prevailing (the most restrictive, i.e. final) license, from the Open Source License Compliance perspective, is the EPL-2 license.
If you change Vely code
itself (i.e. create a derivative work) and distribute it, then the result of such changes should be made open source under the EPL-2 license. Note that code generated by Vely is not considered derivative work.
EPL-2 is know as "business friendly" license that makes it easy and straightforward to use Vely in commercial applications. If you are a software developer using Vely to write any applications, commercial or otherwise, you do not have to release your source code.
For a complete list of requirements, please refer to the EPL-2 license.
See also
License (
license )
SEE ALL (
documentation)
lock-file
Purpose: Locks file exclusively.
lock-file <file path> id [ define ] <lock id> status [ define ] <status>
lock-file attempts to create a file (deleting all contents of the previous file, if existed), and exclusively lock it. If successful, no other process can do the same unless current process ends or calls
unlock-file. This statement is non-blocking, thus you can check in a sleepy loop for success.
<file path> should be either an existing or non-existing file with a valid file path.
<lock id> (in "id" clause) is a file descriptor associated with locked file.
<status> (in "status" clause) represents the status of file locking: VV_OKAY if successfully locked, VV_ERR_FAILED if cannot lock (likely because another process holds a lock), VV_ERR_INVALID if the path of the file is invalid (i.e. if it is empty), VV_ERR_CREATE if lock file cannot be created.
Generally, this statement is used for process control. A process would use lock-file to start working on a job that only one process can do at a time; once done, by using
unlock-file statement, another process will be able to issue a successful lock-file call. Typically, lock-file is issued in a sleepy loop, waiting for a resource to be released.
Note that file lock is not carried across children processes (i.e. if your process creates children, such as with
exec-program, then such children must obtain their own lock). In addition, if a process serving a request terminates before the request could issue
unlock-file, the lock will be automatically released.
You can use any file name (likely choose a name that signifies the purpose of a lock, as long as you have permissions to create it), and create any number of them. This way you can create as many "binary semaphore"-like objects as you like.
Examples
get-app directory to define dir
write-string define fname
@<<p-out dir>>/.lock
end-write-string
num lockid;
while (1) {
lock-file fname id lockid status define lockst
if (lockst == VV_OKAY) {
20
@WORKING
sleep (20);
@DONE
break;
} else if (lockst == VV_ERR_FAILED) {
sleep(1);
@WAITING
continue;
} else if (lockst == VV_ERR_OPEN || lockst == VV_ERR_INVALID) {
@BAD LOCK
return;
}
}
unlock-file id lockid
return;
See also
Files (
close-file copy-file delete-file file-position file_storage file_uploading lock-file open-file read-file read-line rename-file stat-file temporary_file uniq-file unlock-file write-file )
SEE ALL (
documentation)
manage-memory
Purpose: Turn on/off managed memory.
manage-memory [ on | off | <expression> ]
By default, Vely memory is managed - this means any outstanding memory used by your request will be freed when the request ends (see
memory_handling). You can change this behavior for a time by using manage-memory.
If "on" clause is used, any memory allocated afterwards by any Vely statement is freed automatically (the default behavior). This is managed memory mode.
If "off" clause is used, any memory allocated afterwards by any Vely statement is not freed automatically; this behavior will be in effect until the end of the request or until manage-memory with "on" clause is called. This is unmanaged memory mode.
If boolean <expression> is given, and if it evaluates to true then it's the same as "on" clause; if it evaluates to false then it's the same as "off" clause.
- Freeing, reallocating, using with other C API
If memory is unmanaged, you can use other memory handling statements on such memory (such as
delete-mem or
resize-mem) in unmanaged memory mode only - do not directly use C's free(), realloc() and similar. However, unmanaged memory can be used with any other C API (i.e. libraries); this is useful if you receive already allocated memory from such libraries or need to pass malloc-allocated memory to them.
- Scope
manage-memory has effect in the current request only. Regardless of whether managed or unmanaged memory was used last before the request ended, both the
after_request_handler and the following request will run with the default behavior (i.e. managed). This is true even when your request exits with
exit-request or
report-error.
If you have a
startup_handler, you can use unmanaged memory; once the execution returns from the startup handler, all memory requests will be managed again.
If you have a
before_request_handler or
after_request_handler, you can use unmanaged memory; once the execution returns from either handler, it will be managed again.
- Request data
Note that request-specific variables provided by Vely are always managed internal memory. As such, if you want to use them as unmanaged memory, you must make a copy first (for instance with
copy-string). This includes
input-param,
request-body and
get-req. See below an example to save a request's input parameter as unmanaged memory available to all future requests.
- Using
You may want to store managed memory pointers in
global_process_data in order to be useful across many requests - this is one purpose of unmanaged memory. Another is to use with external C APIs.
Important: using unmanaged memory carries additional responsibilities for the developer, because any such
allocated memory must be explicitly deleted when not needed, or there will be a memory leak. Use it only in special circumstances; its use is generally discouraged otherwise. Such circumstances typically involve data that should survive automatic memory deallocation at the end of the request or with external C API.
Examples
In this example, the input parameter to a very first request issued in a process is copied to variable "var", the value of which then persists for all following requests:
input-param ipar
static char *var = NULL;
if (var == NULL) {
manage-memory off
copy-string ipar to var
manage-memory on
}
p-out var
Create memory and pass it to another API:
manage-memory off
new-mem define var size 1024
call_some_API(&var);
...
delete-mem var
manage-memory on
See also
Memory (
delete-mem manage-memory memory_handling new-mem resize-mem )
SEE ALL (
documentation)
match-regex
Purpose: Find, or find and replace patterns in strings using regex (regular expressions).
match-regex <pattern> in <target> \
[ \
( replace-with <replace pattern> \
result [ define ] <result> \
[ status [ define ] <status> ] ) \
| \
( status [ define ] <status> \
[ case-insensitive [ <case-insensitive> ] ] [ single-match [ <single-match> ] ] ) \
] \
[ cache ] \
[ clear-cache <clear cache> )
match-regex searches <target> string for regex <pattern>. If "replace-with" is specified, then instance(s) of <pattern> in <target> are replaced with <replace pattern> string, and the result is stored in <result> string, which must be different from <target>. <result> is
allocated memory.
Both the search and replacement use
extended regex syntax (ERE) from the built-in Linux regex library. The number of found or found/replaced patterns can be obtained in number <status> variable.
If "replace-with" is not specified, then the number of matched <pattern>s within <target> is given in <status> number variable, which in this case must be specified.
If "case-insensitive" is used without optional boolean expression <case-insensitive>, or if <case-insensitive> evaluates to true, then searching for pattern is case insensitive. By default, it is case sensitive.
If "single-match" is specified without optional boolean expression <single-match>, or if <single-match> evaluates to true, then only the very first occurrence of <pattern> in <target> is processed. Otherwise, all occurrences are processed.
<result> and <status> variables can be created within the statement with "define" clause.
If the pattern is bad (i.e. <pattern> is not a correct regular expression), Vely will error out with a message.
Caching and performance
If "cache" clause is used, then regex compilation of <pattern> will be done only once and saved for future use. There is a significant performance benefit when match-regex executes repeatedly with "cache" (such as in case of web applications or in any kind of loop). If <pattern> changes and you need to recompile it once in a while, use "clear-cache" clause. <clear cache> is a "bool" variable; the regex cache is cleared if it is true, and stays if it is false. For example:
bool cl_c;
if (q == 0) cl_c = true; else cl_c = false;
match-regex ps in look_in replace-with "Yes it is \\1!" result res cache clear-cache cl_c
In this case, when "q" is 0, cache will be cleared, and the pattern in variable "ps" will be recompiled. In all other cases, the last computed regex stays the same.
While every pattern is different, when using cache even a relatively small one was shown in tests to speed up the match-regex by about 500%, or 5x faster. Use cache whenever possible as it brings parsing performance close to its theoretical limits.
Subexpressions and back-referencing
Subexpressions are referenced via compatible ERE extension, which is a backslash followed by a number. Because C treats backslash followed by a number as an octal number, you must use double backslash (\\). For example:
match-regex "(good).*(day)" \
in "Hello, good day!" \
replace-with "\\2 \\1" \
result res
will produce string "Hello, day good!" as a result in "res" variable. Each subexpression is within () parenthesis, so for instance "day" in the above pattern is the 2nd subexpression, and is back-referenced as \\2 in replacement.
There can be a maximum of 23 subexpressions.
Note that backreferences to non-existing subexpressions are ignored - for example \\4 when there are only 3 subexpressions. Vely is "smart" about using two digits and differentiating between \\1 and \\10 for instance - it takes into account the actual number of subexpressions and their validity, and selects a proper subexpression even when two digits are used in a backreference.
Examples
Use match-regex statement to find out if a string matches a pattern, for example:
match-regex "SOME (.*) IS (.*)" in "WOW SOME THING IS GOOD HERE" status define st
In this case, the first parameter ("SOME (.*) IS (.*)") is a pattern and you're matching it with string ("WOW SOME THING IS GOOD HERE"). Since there is a match, status variable (defined on the fly as integer) "st" will be "1" (meaning one match was found) - in general it will contain the number of patterns matched.
Search for patterns and replace them by using replace-with clause, for example:
match-regex "SOME (.*) IS ([^ ]+)" in "WOW SOME THING IS GOOD HERE FOR SURE" replace-with "THINGS ARE \\2 YES!" result define res status define st
In this case, the result from replacement will be in a new string variable "res" specified with the result clause, and it will be
WOW THINGS ARE GOOD YES! HERE FOR SURE
The above demonstrates a typical use of subexpressions in regex (meaning "()" statements) and their referencing with "\\1", "\\2" etc. in the order in which they appear. Consult regex documentation for more information. Status variable specified with status clause ("st" in this example) will contain the number of patterns matched and replaced. As is the case with all Vely statements, you could use "define" clause to create a result/output variable within the statement, or omit it if it's created elsewhere.
Matching is by default case sensitive. Use "case-insensitive" clause to change it to case insensitive, for instance:
match-regex "SOME (.*) IS (.*)" in "WOW some THING IS GOOD HERE" status define st case-insensitive
In the above case, the pattern would not be found without "case-insensitive" clause because "SOME" and "some" would not match. This clause works the same in matching-only as well as replacing strings.
If you want to match only the first occurrence of a pattern, use "single-match" option:
match-regex "SOME ([^ ]+) IS ([^ ]+)" in "WOW SOME THING IS GOOD HERE AND SOME STUFF IS GOOD TOO" status define st single-match
In this case there would be two matches by default ("SOME THING IS GOOD" and "SOME STUFF IS GOOD") but only the first would be found. This clause works the same for replacing as well - only the first occurrence would be replaced.
See also
Regex (
match-regex )
SEE ALL (
documentation)
Memory handling
Managed and unmanaged
Memory allocated by Vely statements (and internally by Vely) can be managed or unmanaged. By default it's managed and it might be the only kind you use. Managed memory is tracked and always fully freed at the end of a request, even if you don't do it. In fact that is preferrable in most cases (more on this below). This is the default behavior. In this case, your program is in "managed memory mode".
Unmanaged memory is rarely used; more on it further down.
Allocated memory
Some Vely statements allocate memory for their results. Each statement's documentation specifies the clause(s) that allocate memory. Those that have not been so specified do not allocate memory. If memory cannot be allocated, your request will error out (see
error_handling); this does not affect other requests.
Managed memory
All such memory is automatically freed at the end of each
request, so in general you may not need to free memory at all. If needed, you can free memory with
delete-mem or reallocate with
resize-mem, or use statements that explicitly do that (such as
delete-query for instance). However, unless your program uses lots of memory for longer, it may make sense to not do that and let Vely release it at the end of the request. The reasons for this are:
- releasing memory programmatically is in some cases error prone and may lead to malfunctions and crashes,
- releasing memory programmatically in some cases prolongs the duration of the request, making some of the allocated memory stay so for longer. Having requests of shorter duration and releasing memory all at once at the end may be preferable.
In the end, it is up to you whether to release memory explicitly, let Vely do it, or a bit of both.
Managed memory is freed even if it is no longer accessible to the program, thus preventing memory leaks; this is important for stability of long-running processes.
Internal process memory
Memory needed for cross-request purposes, such as prepared database statements or process configuration data, is kept for the life of the process.
Memory reuse
Any of the Vely
statement_APIs that return allocated memory will always create it new. In other words, the previously allocated memory will not be reused by default.
This is so that common and hard-to-track bugs relating to memory reuse, proper sizing and re-allocation can be avoided, such as passing non-Vely allocated pointers, bad pointers or memory of insufficient allocation. In addition, this approach may increase performance because allocating memory is generally faster than reallocating; given requests are short and frequent by nature, it means increased productivity and performance.
Either way, you can always explicitly reuse memory during request processing by freeing previously allocated memory of any statement that does so, see
delete-mem or any other specialized statement (such as
delete-query or
delete-json for example).
Open file descriptors
Any files opened by
open-file statement are automatically closed by Vely at the end of the request. This enhances stability of long-running server processes because Linux system by default offers only about 1000 open files to a process. A bug can quickly exhaust this pool and cause a malfunction or a crash - Vely prevents this by closing any such open files when the request ends. Note that this is true whether your program is currently in managed or unmanaged memory mode.
Unmanaged memory
Unmanaged memory is not tracked and you have to manually free it. It uses the same allocation as standard C's malloc() memory. Memory allocated is unmanaged when "off" clause is used in
manage-memory - this is "unmanaged memory mode". Unmanaged memory is used only in special circumstances and in general should not be used without a good reason. See
global_process_data.
Memory allocated in managed mode can only be freed or reallocated while in managed mode. And conversely, memory allocated in unmanaged mode can only be freed or reallocated while in unmanaged mode. Attempting otherwise will cause your program to
error out.
By default, memory usage is managed. Use
manage-memory with "off" clause to enter unmanaged mode, and "on" clause to revert to managed mode. Here is an example of unmanaged memory mode:
manage-memory off
new-mem var size 1024
manage-memory on
If you are using unmanaged memory, then you must explicitly manage it by using
delete-mem or
resize-mem, or use statements that explicitly do that (such as
delete-query for instance) - these operations must be performed in unmanaged memory mode as well.
See also
Memory (
delete-mem manage-memory memory_handling new-mem resize-mem )
SEE ALL (
documentation)
new-fifo
Purpose: Create FIFO list.
new-fifo [ define ] <list>
new-fifo initializes new FIFO <list> (First In First Out). <list> is a pointer to type "vely_fifo", and can be created with optional "define".
FIFO <list> contains data stored on a first-in, first out basis. It is useful for storing temporary information in memory which can be quickly retrieved. Note that a list is accessible to the current process only.
Generally information is stored in a FIFO list, and retrieved (possibly many times) in the same order in which it was stored.
The internal positions for
write-fifo and
read-fifo actions are separate so you can keep adding data to the list, and obtain it in any number of retrieval passes by using
rewind-fifo.
Allocated internals
<list> is
allocated memory along with additional internal memory, which can be released if
purge-fifo is used with <list> from a previously executed new-fifo.
Examples
See also
FIFO (
new-fifo purge-fifo read-fifo rewind-fifo write-fifo )
SEE ALL (
documentation)
new-hash
Purpose: Create hash table.
new-hash [ define ] <hash> size <size>
new-hash initializes hash table named <hash>, which is a pointer to type "vely_hash" and can be created with optional "define". <size> is the number of "buckets" in the hash table. All elements with the same hash code are stored in a linked list within the same bucket. Greater table size usually means less elements per bucket and better performance. However, memory usage grows with a bigger hash table, so its size should be balanced based on the program needs.
Vely uses high-performing
FNV1_a hash algorithm. Each element in a bucket list is lightweight, containing pointers to a key, value and next element in the linked list.
Note that a hash table is accessible to the current process only. <size> must be at least 10; if less, it will be set to 10.
Allocated internals
<hash> is
allocated memory along with additional internal memory, which can be released if
purge-hash is used with <hash> from a previously executed new-hash.
Examples
Create a new hash with 500 buckets:
new-hash define h size 500
See
read-hash for more examples.
See also
Hash table (
get-hash new-hash purge-hash read-hash resize-hash write-hash )
SEE ALL (
documentation)
new-json
Purpose: Parse JSON text.
new-json [ define ] <json> from <text> \
[ length <length> ] \
[ status [ define ] <status> ] \
[ error-text [ define ] <error text> ] \
[ error-position [ define ] <error position> ] \
[ max-hash-size <max-hash-size> ] \
[ noencode ]
new-json will parse <text> into <json> variable (a pointer to type "vely_json") which can be created with optional "define".
The length of <text> may be specified with "length" clause in <length> variable, or if not, it will be calculated as the string length of <text>.
The "status" clause specifies the return <status>, which is VV_OKAY if successful or VV_ERR_JSON if there is an error. The number variable <error position> in "error-position" clause is the byte position in <text> where error was found, in which case <error text> in "error-text" clause is the error message. Both can be created with optional "define".
See
read-json on obtaining values from JSON document.
No copying
String <text> is modified during parsing for performance reasons. It is parceled out and <json> contains pointers to it that hold the actual primitive values (string, numbers, boolean, null). This way no data is ever copied for faster parsing. If you don't wish <text> to be modified, make a copy of it before parsing it (see
copy-string). In many cases though, this is not necessary, allowing for better performance.
Hash table
Hash table is used to provide fast access to any value in JSON text, with the number of lookups being close to 1, which means near-direct memory access. By default, the maximum size of a hash table is limited to 10000. If your JSON document has considerably more values than that, use "max-hash-size" clause to specify the maximum hash table size. Hash table mechanism is the same as used internaly in
new-hash.
Encoding
"noencode" clause will not encode strings, i.e. convert from JSON Unicode strings to UTF8 (see
read-json), nor will it perform any validity checks on strings. This may be useful as a performance boost, however it is not recommended in general.
Limitations
The maximum depth of nested structures in JSON document (i.e. objects and arrays) is 32, and the maximum length of normalized leaf node name is 1024 (see
read-json for more on normalized names). There is no limit on document size.
Duplicate normalized names are valid, however, only one of them is reported, and the very last value encountered is actually stored (the others from duplicate names are discarded).
Creating JSON
To create a JSON document, you can use
write-string to programmatically construct one - use
utf8-json to create JSON-compatible Unicode string values.
Allocated internals
<json> is
allocated memory along with additional internal memory, which can be released if
delete-json is used with <json> from a previously executed new-json.
Examples
Parse text "json_text1" and store the result in "json_var" variable, get status, error text and the location of error:
...
new-json json_var from json_text1 status define st error-text errt error-position errp
read-json json_var key "glossary"."title" value d
See also
JSON (
delete-json new-json read-json )
SEE ALL (
documentation)
new-mem
Purpose: Allocate memory.
new-mem [ define ] <memory> size <size> \
[ block-size <block size> ] \
[ init ]
new-mem allocates <memory> of <size> blocks, each block of <block size> bytes as specified in "block-size" clause, thus allocating <size>*<block size> bytes aligned suitably for any pointer type. By default, block size is 1, which means in that case <size> is the number of bytes. <memory> is
allocated memory.
When "init" is used, the memory is zero-initialized. If you need the memory initialized, always use "init" as it is generally faster than creating memory and then initializing it (for instance with "memset()").
If "define" is used, variable <memory> is created if it does not exist. The pointer returned is always "void*" and can be used for any purpose; always cast it to your desired type.
If an existing pointer is used (i.e. without "define"), then such pointer can be of any type.
Examples
Allocate memory of 300 bytes, producing a "void *". Then the data is copied into it - note the casting to "char*";
new-mem define mystr size 300
strcpy ((char*)mystr, "Some string");
Initialize memory of 1000 bytes (filled with all zeroes):
new-mem define mymem size 1000 init
Allocate an array of 1000 integers:
int *mymem;
new-mem mymem size 1000 block-size sizeof(int)
Allocate an array of 1000 integers initialized to 0 and then a single element of the array is assigned a value:
int *mymem;
new-mem mymem size 1000 block-size sizeof(int) init
mymem[50] = 23;
The memory can be created as "void *" and then assigned to any type:
new-mem define mymem size 1000 block-size sizeof(int) init
int *newmem = (int*)mymem;
See also
Memory (
delete-mem manage-memory memory_handling new-mem resize-mem )
SEE ALL (
documentation)
Non request
Non-request source files implement common code used in
request-handling .vely files. Their names always start with an underscore ('_').
So for instance, file '_display.vely' would never handle any requests directly - such file may be (for example) implementing common code that displays various HTML statements.
A non-request source file can implement any number of functions which can be named in any fashion. However, Vely will declare a prototype for a function with the same name, saving you to the effort to do that, should you decide to implement it. For example, if you have function "void _display_table()" in file "_display.table.vely":
#include "vely.h"
void _display_table() {
...
}
then the prototype for function "void _display_table()" will be automatically generated and you do not have to do it manually. You would implement any other functions related to it in the same source file.
When Vely code is compiled, both request and non-request source code is automatically picked up - all files with .vely extensions are used.
Examples
In the following example, a list on customer names is shown (note functions header() and footer()), and the code is implemented in
request source file show.vely:
#include "vely.h"
void show() {
run-query @db="select name from cust" output name
header();
query-result name
footer();
end-query
}
Functions header() and footer() are implemented in non-request source file _display.vely:
#include "vely.h"
void header() {
@Name:<br/>
}
void footer() {
@<hr/>
}
Both files (show.vely and _display.vely) will be automatically picked up, compiled and linked.
See also
Requests (
after_request_handler before_request_handler building_URL getting_URL global_request_data non_request normalized_URL request request_URL startup_handler vely_dispatch_request )
SEE ALL (
documentation)
Normalized URL
A
request_URL can be written entirely as a query string. This is normalized URL. In this case, request name is omitted as a path segment and is specified as a "req" input parameter along with all other input parameters. Query string follows the application path.
For instance a URL like:
https://some.web.site/shopping/buy-item?name=ABC&price=600
can be written as:
https://some.web.site/shopping?req=buy_item&name=ABC&price=600
A
request name "buy_item" (hyphens get converted to underscores) is now the value of parameter "req", which can be specified anywhere in a query string, i.e. it does not have to be the first parameter.
Note that parameter name "req" has a special meaning only in normalized URLs. You can use it just like any other input parameter otherwise.
See also
Requests (
after_request_handler before_request_handler building_URL getting_URL global_request_data non_request normalized_URL request request_URL startup_handler vely_dispatch_request )
SEE ALL (
documentation)
on-error
Purpose: Either exit request or continue processing when there is an error in executing a query.
on-error ( exit | continue ) [ @<database> ]
When a database statement (like
run-query) fails, either exit request processing if "exit" is used, or continue if "continue" is used. "Exiting" is equivalent to calling
report-error with the message containing details about the error. "Continuing" means that your program will continue but you should examine error code (see "error" clause in
run-query) and handle any issues.
The default action is "exit". You can switch back and forth between "exit" and "continue". Typically, "exit" is preferable because errors in database SQL code generally mean application issues, i.e. bugs that need fixing, however "continue" may be used when application wants to attempt to recover from errors or perform other actions.
Note that you can override the effect of on-error for a specific query by using "on-error-continue" and "on-error-exit" clauses in
run-query.
Database
Optional <database> is specified in "@" clause and is the name of the
database_config_file.
Examples
The following will not exit when errors happen but rather continue execution (and you must check every error henceforth):
See also
Database (
begin-transaction commit-transaction current-row database_config_file database_queries delete-query on-error prepared_statements query-result rollback-transaction run-query )
Error handling (
error_code error_handling on-error report-error )
SEE ALL (
documentation)
open-file
Purpose: Open file for reading and writing.
open-file <file> file-id [ define ] <file id> \
[ new-truncate ] \
[ status [ define ] <status> ]
Opens <file> for reading and writing and creates an open file variable identified by <file id>, which can be created with optional "define". If you supply your own <file id>, it should be of type "vely_file*".
<file> can be a full path name, or a path relative to the application home directory (see
how_vely_works).
You can obtain the status of file opening via optional <status> (in "status" clause), which can be created with optional "define". The <status> is VV_OKAY if file is opened, or VV_ERR_OPEN if could not open file.
If "new-truncate" clause is used, a new file is created if it doesn't exist, or it is truncated if it does.
Examples
open-file "testwrite" file-id define nf new-truncate
25000
num i;
for (i = 1; i <= 25000; i++) {
(( define line
some text in line <<p-out i>>
)) bytes-written define line_len notrim
write-file file-id nf from line length line_len
}
file-position set 0 file-id nf
25000
for (i = 1; i <= 25000; i++) {
read-file file-id nf to define one_item
p-out one_item
}
close-file file-id nf
See also
Files (
close-file copy-file delete-file file-position file_storage file_uploading lock-file open-file read-file read-line rename-file stat-file temporary_file uniq-file unlock-file write-file )
SEE ALL (
documentation)
out-header
Purpose: Output HTTP header.
out-header default
out-header use \
[ content-type <content type> ] \
[ download [ <download> ] ] \
[ etag [ <etag> ] ] \
[ file-name <file name> ] \
[ ( cache-control <cache control> ) | no-cache ] \
[ status-id <status id> ] \
[ status-text <status text> ] \
[ custom <header name>=<header value> [ , ... ] ]
A web page must have an HTTP header output before any other response. Note that while cookies may be set after out-header, they still must be set before outputting the data. If your application uses out-header multiple times, all but the very first one are ignored.
If out-header is not called prior to the actual output, either the header or the output itself (or both) may not be output, and you might get an error. Be sure to always first use out-header prior to any output for a
request.
Use out-header for dynamically generated web pages. If you wish to output a file (such as an image or a PDF document), do not use this statement; rather use
send-file instead.
The HTTP header is sent back to a client who initiated a request. You can specify any custom headers with "use" clause.
Default header
If "default" clause is in place, a default header is constructed, which uses a status of 200/OK and content type of
and cache control of
Cache-Control:max-age=0, no-cache; Pragma: no-cache
and also sends any cookies produced by
set-cookie and
delete-cookie. The default header is typical for dynamically generated web pages, and most of the time you would use the default header.
Headers
The following are subclauses that allow setting any custom header:
- <content type> is content type (such as "text/html" or "image/jpg" etc.) If you are sending a file to a client for download and you don't know its content type, you can use "application/octet-stream" for a generic binary file.
- If "download" is used without optional boolean expression <download>, or if <download> evaluates to true, then the file is sent to a client for downloading - otherwise the default is to display file in client.
- <file name> is the name of the file being sent to a client. This is not the local file name - it is the file name that client will use for its own purposes.
- <cache control> is the cache control HTTP header. "no-cache" instructs the client not to cache. Only one of "cache-control" and "no-cache" can be used. An example of <cache control>:
send-file "somepic.jpg" headers cache-control "max-age: 3600"
- If "etag" is used without optional boolean expression <etag>, or if <etag> evaluates to true, then "ETAG" header will be generated (a timestamp) and included, otherwise it is not. The time stamp is of last modification date of the file (and typically used to cache a file on client if it hasn't changed on the server). "etag" is useful to let the client know to download the file only once if it hasn't changed, thus saving network and computing resources.
- <status id> and <status text> are status settings for the response (such as "425" for "status-id" and "Too early" for "status-text").
- To set any type of generic HTTP header, use "custom" subclause, where <header name> and <header value> represent the name and value of a single header. Multiple headers are separated by a comma. The maximum number of such headers is 64. You must not use "custom" to set headers already set elsewhere (such as "etag" for instance), as that may cause unpredictable behavior. For instance this sets two custom headers:
out-header use custom "CustomOption3"="CustomValue3", "Status"="418 I'm a teapot"
"custom" subclause lets you use any custom headers that exist today or may be added in the future, as well as any headers of your own design.
You can use
silent-header before output-header in order to suppress its output.
Examples
To output the default HTTP header for a dynamically generated page (a typical usage):
To set a custom header for a web page that changes cache control and adds two new headers:
out-header use content-type "text/html" cache-control "max-age:3600" custom "some_HTTP_option"="value_for_some_HTTP_option", "some_HTTP_option_1"="value_for_some_HTTP_option_1"
See also
Web (
call-web out-header send-file silent-header )
SEE ALL (
documentation)
Output statement
Purpose: Output text.
Outputting free form text from Vely code is done by starting the line with "@" or "!". The text is output unencoded to the client.
With "@" statement, any
inline_code executes and any output from those statements is output.
With "!" statement, all text is output verbatim, and any inline code is not executed. This is useful when the text printed out should not be checked for any
inline_code (such as << ... >>).
All trailing whitespaces are trimmed from each line. If you need to write trailing whitespaces, with "@" statement you can use
p-out as
inline_code. Maximum line length is 8KB - this is the source code line length, the actual run-time output length is unlimited.
Note that all characters are output as they are written, including the escape character (\). If you wish to output characters requiring an escape character, such as new line and tab (as is done in C by using \n, \t etc.), use
p-out as
inline_code.
Examples
Outputting "Hello there" from Vely code:
You can use other Vely statements inlined and mixed with the text you are outputting:
char *weatherType="sunny";
@Today's weather is <<p-out weatherType>>
which would output
With "!" statement, the text is also output, and this example produces the same "Hello there" output as "@":
In contrast to "@" statement, "!" statement outputs all texts verbatim and does not execute any inline code:
char *weatherType="sunny";
!Today's weather is <<p-out weatherType>>
which would output
Today's weather is <<p-out weatherType>>
See also
Output (
finish-output flush-output output_statement p-dbl pf-out pf-url pf-web p-num p-out p-path p-url p-web )
SEE ALL (
documentation)
pf-out
Purpose: Outputs a formatted string without encoding.
pf-out [ bytes-written [ define ] <bytes> ] [ to-error ] <format> <expression> [ , <expression> ]...
pf-out formats a string according to the <format> string and a list of variable-type <expression>s (in the same way C's "printf()" does) and then outputs the result without any encoding (meaning a string is output exactly as it is, and the client may interpret such text in any way it sees fit). The result is sent to a client that made the request. If another string is built by using
write-string, then the result is output into the buffer that builds a new string.
<format> string must be present. If you'd like to know the number of bytes written out, use the optional "bytes-written" clause prior to <format>, in which case <bytes> holds the number of bytes written. If this variable is not defined, use "define" clause to create it within the statement. There must be at least one <expression> (it means if you want to print out a simple string literal you still have to use "%s" as format).
If the optional "to-error" clause is used, the output is sent to "stderr", or standard output stream.
Examples
To output data (the string output is "the number is 20" and the number of bytes written is in variable "bwritten"):
pf-out bytes-written define bwritten "%s is %d", "the number", 20
The following is writing to client, outputting text for which the browser will interpret tags "<br/>" and "<hr/>" as a line break and a horizontal line and display them as such:
pf-out "<hr/> %s <br/>", "This is a non-encoded output"
Create a query text string by means of
write-string statement:
void get_table_data (const char *table_name, num id_num)
{
write-string define qry_txt
@select * from <<pf-out "%s where id="%lld", table_name, id_num>>
end-write-string
}
See also
Output (
finish-output flush-output output_statement p-dbl pf-out pf-url pf-web p-num p-out p-path p-url p-web )
SEE ALL (
documentation)
pf-url
Purpose: Outputs a URL-encoded formatted string.
pf-url [ bytes-written [ define ] <bytes> ] [ to-error ] <format> <expression> [ , <expression> ]...
pf-url is the same as
pf-out, except that the output is URL-encoded. This means such output is suited for use in URL parameters.
Examples
Create a URL based on arbitrary strings used as URL parameters - for instance space would be encoded as "%20" in the final output:
@<a href='<<p-path>>/update?val=<<pf-url "Purchased %s for %lld dollars", piece_desc, price>>'>Log transaction</a>
See
pf-out for more examples.
See also
Output (
finish-output flush-output output_statement p-dbl pf-out pf-url pf-web p-num p-out p-path p-url p-web )
SEE ALL (
documentation)
pf-web
Purpose: Outputs a web-encoded formatted string.
pf-web [ bytes-written [ define ] <bytes> ] [ to-error ] <format> <expression> [ , <expression> ]...
pf-web is the same as
pf-out, except that the output is web-encoded (or HTML-encoded). This means such output is suited for use in web pages - the text will be displayed verbatim without HTML-markup being interpreted.
Examples
Display text containing HTML tags without them being rendered in the browser:
pf-web "We use %s markup", "<hr/>"
See
pf-out for more examples.
See also
Output (
finish-output flush-output output_statement p-dbl pf-out pf-url pf-web p-num p-out p-path p-url p-web )
SEE ALL (
documentation)
Plain C FCGI
Purpose: Building FastCGI programs in C.
Plain C programs (linked with fcgi, the FastCGI library) can run as server applications with
vf FastCGI program manager, which is used to start, stop and manage such applications.
Your program should include "fcgi_stdio.h" include file, and handle incoming requests in a loop by accepting them via FCGI_Accept() function, which is provided by fcgi library. Also, your program must handle SIGTERM signal properly and terminate when signalled without disruption to request processing.
An example of a simple program that fulfills these requirements is shown here.
Flags "busy" and "end_program" are used to handle a termination signal. "busy" is set to 1 while in the processing request loop; if termination signal happens then, "end_program" will be set to 1 so the program can exit once the request is processed. If termination signal happens during any other time (most likely while waiting for request in FCGI_Accept()), the program will exit right away.
Examples
The code below is a "Hello World" example that runs as a server - save it to file plain_fcgi.c:
#include "fcgi_stdio.h"
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
static int busy = 0;
static int end_program = 0;
void signal_handler(int sig) {
if (sig == SIGTERM) {
if (busy == 0) exit(0);
end_program = 1;
return;
}
}
void main(void) {
struct sigaction psa;
memset (&psa, 0, sizeof (psa));
psa.sa_handler = signal_handler;
sigaction(SIGTERM, &psa, NULL);
while (FCGI_Accept() >= 0) {
busy = 1;
printf("Content-type: text/html\r\n"
"\r\n"
"<title>Hello World!</title>"
"<h1>Hello there!</h1>");
busy = 0;
if (end_program) exit(0);
}
}
Initialize application
Before vf can run your program as a server, your application must be initialized - this is done only once:
sudo vf -i -u $USER plain_fcgi
Compile application
To compile and link this C program on any distro other than OpenSUSE:
gcc -o /var/lib/vv/bld/plain_fcgi/plain_fcgi plain_fcgi.c -lfcgi
For OpenSUSE, use the following:
gcc -o /var/lib/vv/bld/plain_fcgi/plain_fcgi plain_fcgi.c -lfcgi -I /usr/include/fastcgi
Start application
To start your server application with 2 parallel workers:
Or if you are using TCP to connect to your application, in this case TCP port 2300:
vf -w 2 -p 2300 plain_fcgi
Configure web server (reverse proxy)
To setup a web server to use your application, your must set it up - see
application_setup. Make sure to replace <app name> with the actual application name, in this case "plain_fcgi".
Run application
To see your application running, enter this in your browser, assuming your web server is accessible as localhost, i.e. 127.0.0.1 (otherwise replace 127.0.0.1 with your server hostname):
http://127.0.0.1/plain_fcgi
If you wish to stop the FastCGI server and your application:
Whenever you recompile your C program, vf will automatically reload it. For more information on all the options available, see
vf.
See also
Running application (
application_setup CGI Client_API command_line containerize_application FastCGI FastCGI_client plain_C_FCGI )
SEE ALL (
documentation)
p-out
Purpose: Outputs a string without encoding.
p-out outputs a string expression given by <string>, without any encoding (meaning a string is output exactly as it appears). The string is sent to a client that made the request. If this is within
write-string, then <string> is output into the buffer that builds a new string.
Examples
To output data verbatim to a client:
char *mydata="Hello world";
p-out mydata
Writing to client, outputting text followed by a horizontal rule - the text is output to a client (such as browser) as it is, and the browser will interpret tags "<br/>" and "<hr/>" as a line break and a horizonal line and display them as such:
p-out "This is a non-encoded output<br/>"
p-out "<hr/>"
Create a query text string by means of
write-string statement:
void get_table_data (const char *table_name)
{
write-string define qry_txt
@select * from <<p-out table_name>>
end-write-string
}
See also
Output (
finish-output flush-output output_statement p-dbl pf-out pf-url pf-web p-num p-out p-path p-url p-web )
SEE ALL (
documentation)
p-path
Purpose: Outputs URL application path.
p-path outputs a URL application path (see
request_URL), i.e. the leading path segment(s) prior to request name.
If no "--path" option in
vv is used to specify URL application path, then it is the same as application name prepended with a forward slash:
p-path provides the leading part of a URL path after which request name and its parameters can be specified. It is used in HTML forms and URLs (either for HTML or API) to refer back to the same application.
Examples
Create URL in web link:
@<a href="<<p-path>>/add-note">Add Note</a>
In HTML forms:
...
<form action="<<p-path>>/add-note" method="POST" enctype="multipart/form-data">
...
See also
Output (
finish-output flush-output output_statement p-dbl pf-out pf-url pf-web p-num p-out p-path p-url p-web )
SEE ALL (
documentation)
Prepared statements
Prepared queries
A prepared query differs only in that it is natively prepared for the database you are using. It is pre-compiled and (among other things) its execution plan is created once, instead of each time a query executes. The statement is cached going forward for the life of the process (with the rare exception of re-establishing a lost database connection). It means effectively an unlimited number of requests will be reusing the query statement.
In practicality it means the execution of a query may be faster due to being prepared only once and executed many times.
Note that databases do not allow prepared queries for DDL (Data Definition Language), as there is not much benefit in general, hence only DML queries (such as INSERT, DELETE etc.) and SELECT can be prepared.
Caching of queries
In order to cache a query statement, Vely will save query text that actually executes the very first time it runs. Then, regardless of what query text you supply in the following executions, it will not mutate anymore. It means from that moment onward, the query will always execute that very same query text, just with different input parameters.
In general, majority of queries are static and not dynamic, so this works just fine. When you use prepared statements with dynamic queries though, you should be aware that query text is immutable. For instance, code in a request that is called many times may look like:
char *qry;
static char is_query_built = 0;
if (!is_query_built) {
qry = create_query();
is_query_built = 1;
}
run-prepared-query @mydb = qry output col1, col2, col3 : inp1, inp2
end-query
In the above example, query text "qry" is built only once (by using static boolean "is_query_built"), because the actual query text is needed only for the very first execution of query "myquery", even when connection is lost and re-established. This improves performance beyond what prepared statement may already do. You can, of course, build it every time (i.e. not use the boolean or a similar mechanism), but the query text that actually executes will not change after the very first run. The input parameter variables (such as "inp1" and "inp2" in this example) may change, of course.
In a majority of code, a query may be static as in:
run-prepared-query @mydb = "select col1 from test where someID='%s'" output col1 : id_value
end-query
then clearly the query text is a string literal and will never change. All that changes is the input parameters (in this case "id_value"), and of course the output (in this case "col1"). Most queries are like this.
SQL injections
Note that regardless of whether you use prepared statements or not, the execution of your queries is guarded against SQL injections:
- in case of non-prepared queries, the separation of data and logic is emulated and input parameters are sanitized by Vely; and the query sent to the server is a single text statement,
- in case of prepared queries, the separation of data and logic is native to the database and thus the two are separated the entire way, so there is no need to sanitize the input; the query identification (not the query itself) and data are sent to the server separately.
Performance
In most cases, prepared statement will exhibit better performance, and this is particularly true in Vely, where Vely FCGI processes (see
vely_architecture and
how_vely_works) keep a single database connection (and thus a single session) open for the life of the process (re-creating it only when the connection is lost). Because of this, a prepared statement is done so once; and then reused and re-run many times over afterwards.
In some cases, you might not want to use prepared statements. Some reasons may be:
- your statements are often changing and dynamically constructed to the point where managing a great many equivalent prepared statements may be impractical - for example there may be a part of your query text that comes from outside your code,
- your dynamic statements do not execute as many times, which makes prepared statements slower, since they require two trips to the database server to begin with,
- your query cannot be written as a prepared statement,
- in some cases prepared statements are slower because the execution plan depends on the actual data used, in which case non-prepared statement may be a better choice,
- in some cases the database support for prepared statements may still have issues compared to non-prepared,
- typically prepared statements do not use query cache, so repeating identical queries with identical input data may be faster without them.
PostgreSQL
You may get an error like:
could not determine data type of parameter $N
when preparing statements for PostgreSQL. This is an issue with Postgres server, and has nothing to do with Vely, for example in statement:
select col1 from test where someId>='%s' and col1 like concat( '%s' ,'%')
you might get an error "could not determine data type of parameter $2". An issue like this may be that Postgres cannot determine the type, or it may be a bug in Postgres; regardless, this is not a Vely issue. In this case $2 is the second '%s' input parameters and you should specify the type manually for Postgres, generally in form of
In this case, the type in question is "text", so your statement would be:
select col1 from test where someId>='%s' and col1 like concat( '%s'::text ,'%')
This solution generally works for any Postgres client, not just Vely, regardless of how is the positional input parameter specified.
See also
Database (
begin-transaction commit-transaction current-row database_config_file database_queries delete-query on-error prepared_statements query-result rollback-transaction run-query )
SEE ALL (
documentation)
purge-fifo
Purpose: Delete FIFO list data.
purge-fifo <list> [ delete ]
purge-fifo will delete all elements from the FIFO <list>, created by
new-fifo. The list is then empty and you can still put data into it, and get data from it afterwards, without having to call
new-fifo again.
If you use "delete" clause, then all the internal memory of a <list> is freed, and you must call
new-fifo in order to use it again.
Note that in any case, purge-fifo will not free any possibly
allocated memory for keys or values stored in its elements (see
write-fifo).
See
memory_handling for more on when (not) to delete memory explicitly like this; the same rules apply as for
delete-mem.
Examples
See
read-fifo.
See also
FIFO (
new-fifo purge-fifo read-fifo rewind-fifo write-fifo )
SEE ALL (
documentation)
purge-hash
Purpose: Purge hash table.
purge-hash <hash> [ delete ]
purge-hash deletes all elements from <hash> table that was created with
new-hash. Note that it does not free any possibly
allocated memory for keys or values (see
write-hash).
After purge-hash, the hash is empty and you can use it without calling
new-hash again. Note however, that "average-reads" statistics (see
get-hash) is not reset - it keeps being computed and remains for the life of the hash.
If you use "delete" clause, then all the internal memory of a <hash> is freed, and you must call
new-hash in order to use it again; in this case all statistics are reset.
See
memory_handling for more on when (not) to delete memory explicitly like this; the same rules apply as for
delete-mem.
Examples
Create hash, put some data in it and then delete the data:
new-hash h size 100
write-hash h key "mykey" value "myvalue"
purge-hash h
See
read-hash for more examples.
See also
Hash table (
get-hash new-hash purge-hash read-hash resize-hash write-hash )
SEE ALL (
documentation)
Quality control
Quality control
Each candidate for a Vely release must build successfully for a given Linux distribution and on its native architecture, without simulators or cross-compilers. A suite of functional tests (including bug fixes to prevent regressions) must run without errors; currently, there are 1543 such tests.
Vely is tested on following distros: archlinux_rolling, debian_10, debian_11, fedora_37, fedora_38, mageia_8, opensuse_tumbleweed, redhat_8, redhat_9, ubuntu_18, ubuntu_20, ubuntu_22, windows_11 however it may work on other distros derived from this list (see
installation page). Both builds and tests are automated to prevent gaps in coverage.
Valgrind and Google Address Sanitizer (ASAN) are used to help eliminate memory violations and leaks.
Vely documentation is automated to a good degree, with internal/external link checking, HTML/troff make and reference/index building.
See also
General (
deploying_application how_vely_works quality_control rename_files SELinux vely vely_architecture vely_removal vf vv why_C_and_Vely )
SEE ALL (
documentation)
query-result
Purpose: Get query results.
query-result <column name> \
[ ( to [ define ] <result> ) \
| ( urlencode | noencode | webencode ) ] \
[ length [ define ] <length> ]
Use query-result to obtain query results. An instance of query-result obtains a string value of a single query column named <column name>. Note that <column name> can be a computed column, and is not necessarily the same as a table column.
If query-result is used without "to" clause, then the result is printed out. If "to" clause is used, then the result is not printed out, rather it is stored into a string variable <result>, which can be created with optional "define" clause. <result> is
allocated memory.
Without "to" clause, the result can be either not encoded (if "noencode" clause is used), URL encoded (if "urlencode" is used) or web (HTML) encoded (if "webencode" is used) - by default, webencode is used. With "to" clause (i.e. if the result is stored in a variable <result>), then no encoding takes place (you can then use
encode-url or
encode-web to convert it as desired).
query-result is often used as
inline_code, especially when output.
A NULL value is always represented as an empty string ("").
Since queries can be nested, any result must always be used directly under the inner-most
run-query, to which it refers.
Optional "length" clause places the binary length of a result into number variable <length>, with optional "define". Note the length is the same regardless of encoding and always represents the number of bytes used for data, not counting any trailing null byte for strings. In case of binary data, <length> is the actual number of bytes used by such data, regardless of if (and where and how) is such binary data represented in string form.
Examples
Display table columns in an HTML table:
@<table>
run-query @mydb="select firstName, lastName from employee" output firstName, lastName
@ <tr>
@ <td>
query-result firstName
@ </td>
@ <td>
query-result lastName length define lastName_len
@ (length of last name is <<p-num lastName_len>>)
@ </td>
@ </tr>
end-query
@</table>
Using
inline_code query-result:
run-query @mydb="select fileName, description, fileSize from files where fileID='%s'" output fileName, description, fileSize : file_id
@Are you sure you want to delete file <<query-result fileName>> (<<query-result description>>) of size <<query-result fileSize>>? Click <a href="<<p-path>>/delete_file?action=delete&file_id=<<p-url file_id>>">Delete</a> or click the browser's Back button to go back.<br/>
end-query
Nested queries and usage of define clause:
run-query @mydb="select id from user" output id
query-result id to define id
run-query @mydb="select timeout from settings where id='%s'" output timeout : id
query-result timeout
end-query
end-query
Obtain query results first in variables and then use them to build HTML output:
run-query @mydb="select firstName, lastName from employee" output firstName, lastName
query-result firstName to define first_name
query-result lastName to define last_name
@Employee (
p-web first_name
@,
p-web last_name
@) found<br/>
end-query
See also
Database (
begin-transaction commit-transaction current-row database_config_file database_queries delete-query on-error prepared_statements query-result rollback-transaction run-query )
SEE ALL (
documentation)
random-crypto
Purpose: Obtain a random string for cryptographic use.
random-crypto to [ define ] <random string> \
[ length <string length> ]
random-crypto obtains a random string of length <string length>. This statement uses a cryptographically secure pseudo random generator (CSPRNG) from OpenSSL library. If "length" clause is omitted, the length is 20 by default. You can create <random string> with "define" clause. <random string> is
allocated memory.
The value generated is always binary and may contain null-characters and is null-terminated.
Use this statement only when needed for specific cryptographic uses. In all other cases, use
random-string which is 2-3 times faster.
Examples
Get a 20-digit long random binary value:
random-crypto to define str length 20
See also
Encryption (
decrypt-data derive-key encrypt-data hash-string random-crypto random-string )
SEE ALL (
documentation)
random-string
Purpose: Obtain a random string.
random-string to [ define ] <random string> \
[ length <string length> ] \
[ number | binary ]
random-string obtains a random string of length <string length>. If "length" clause is omitted, the length is 20 by default. You can create <random string> with "define" clause. <random string> is
allocated memory.
If "number" clause is used, then the resulting string is composed of digits only ("0" through "9").
If "binary" clause is used, then the resulting string is binary, i.e. each byte can have an unsigned value of 0-255.
By default, if neither "number" or "binary" is used, the resulting string is alphanumeric, i.e. digits ("0" through "9") and alphabet letters ("a"-"z" and "A"-"Z") are used only.
Random generator is based on the Linux random() generator seeded by local process properties such as its PID and time. A single process is seeded once, and thus any number of requests served by the same process will use a subset of the process' random sequence. Due to joint entropy, each result given to any request is random, not just within a single request, but among any number of different requests.
Note that in any case, the random string is null-terminated.
Examples
Get a 100-digit long random value (as an alphanumeric string):
random-string to define str length 100
pf-out "%s\n", str
Get a random number of length 10 in string representation:
random-string to define str length 10 number
pf-out "%s\n", str
Get a random binary value that is 8 bytes in length - this value may contain null bytes (i.e. it will contain bytes with values ranging from 0 to 255) and will also be null-terminated regardless:
random-string to define str length 8 binary
See also
Encryption (
decrypt-data derive-key encrypt-data hash-string random-crypto random-string )
SEE ALL (
documentation)
read-fifo
Purpose: Reads key/value pair from a FIFO list.
read-fifo <list> \
key [ define ] <key> \
value [ define ] <value>
read-fifo retrieves an element from the FIFO <list>, storing it into <key> pointer (in "key" clause) and <value> pointer (in "value" clause), both of which can be created as strings with optional "define".
Once an element has been retrieved, the next use of
read-fifo will obtain the following one, in the same order they were put in. If the obtained <key> is NULL, the end of the list was reached.
read-fifo starts with the first element put in, and moves forward from there, unless
rewind-fifo is called, which positions back to the first one.
Examples
In this example, a FIFO list is created, and two key/value pairs added. They are then retrieved in a loop and printed out (twice with rewind), and then the list is purged:
new-fifo mylist
write-fifo mylist key "key1" value "value1"
write-fifo mylist key "some2" value "other2"
while (1) {
read-fifo mylist key define k value define v
if (k == NULL) {
break;
}
@Obtained key <<p-out k>> with value <<p-out v>>
}
rewind-fifo mylist
while (1) {
read-fifo mylist key define k value define v
if (k == NULL) {
break;
}
@Again obtained key <<p-out k>> with value <<p-out v>>
}
purge-fifo mylist
See also
FIFO (
new-fifo purge-fifo read-fifo rewind-fifo write-fifo )
SEE ALL (
documentation)
read-file
Purpose: Read file into a string variable.
read-file <file> | ( file-id <file id> ) \
to [ define ] <content> \
[ position <position> ] \
[ length <length> ] \
[ status [ define ] <status> ]
Without file-id
This is a simple method of reading a file. File named <file> is opened, data read, and file is closed.
<file> can be a full path name, or a path relative to the application home directory (see
how_vely_works).
Data read is stored into <content>, which can be created with optional "define". <content> is
allocated memory. Note that file can be binary or text and <content> may have null-bytes. Regardless, a null-byte is always placed after the data read.
If "position" and "length" clauses are not specified, read-file reads the entire <file> into <content>, which can be created with optional "define".
If "position" clause is used, then reading starts at byte <position>, otherwise it starts at the beginning of the file. Position of zero (0) represents the beginning of the file.
If "length" clause is used, then <length> number of bytes is read, otherwise the rest of the file is read. If <length> is 0, <content> is empty string and <status> is 0.
If "status" clause is used, then the number of bytes read is stored to <status>, unless error occurred, in which case <status> has the error code. The error code can be VV_ERR_POSITION (if <position> is negative, outside the file, or file does not support it), VV_ERR_READ (if <length> is negative or there is an error reading file) or VV_ERR_OPEN if file is not open.
With file-id
This method uses a <file id> that was created with
open-file. You can then read (and write) file using this <file id> and the file stays open until
close-file is called, or the
request ends (i.e. Vely will automatically close any such open files).
Data read is stored into <content>, which can be created with optional "define". <content> is
allocated memory. Note that file can be binary or text and <content> may have null-bytes. Regardless, a null-byte is always placed after the data read.
If "position" clause is used, then data is read starting from byte <position>, otherwise reading starts from the current file position determined by the previous reads/writes or as set by using "set" clause in
file-position. Note that after each read or write, the file position is advanced by the number of bytes read or written.
If "length" clause is used, then <length> number of bytes is read, otherwise the rest of the file is read. If <length> is 0, <content> is empty string and <status> is 0.
Note that when you reach the end of file and no more bytes can be read, <status> is 0.
If "status" clause is used, then the number of bytes read is stored to <status>, unless error occurred, in which case <status> has the error code. The error code can be VV_ERR_POSITION (if <position> is negative, outside the file, or file does not support it), VV_ERR_READ (if <length> is negative or there is an error reading file) or VV_ERR_OPEN if file is not open.
Examples
To read the entire file and create both the variable that holds its content and the status variable:
read-file "/home/user/some_file" to define file_content status define st
if (st>=0) {
@Read:
@<hr/>
p-web file_content
@<hr/>
} else {
@Could not read (<<pf-out "%lld" st>>)
}
"define" in both the content variable and the status are optional, so it could be:
num st;
char *file_content;
read-file "/home/user/some_file" to file_content status st
To read 10 bytes starting at position 20:
read-file "/home/user/some_file" to file_content position 20 length 10
See
open-file for an example with "file-id" clause.
See also
Files (
close-file copy-file delete-file file-position file_storage file_uploading lock-file open-file read-file read-line rename-file stat-file temporary_file uniq-file unlock-file write-file )
SEE ALL (
documentation)
read-hash
Purpose: Get data from hash table.
read-hash <hash> \
key <key> \
value [ define ] <value> \
[ delete [ <delete> ] ] \
[ status [ define ] <status> ] \
[ old-key [ define ] <old key> ]
read-hash <hash> traverse begin
read-hash <hash> traverse \
key [ define ] <key> \
value [ define ] <value>
Without "traverse" clause
read-hash will obtain an element from <hash>, which is a <value> pointer (in "value" clause) based on a <key> (in "key" clause). <hash> was created by
new-hash. <value> can be created as a string with optional "define", but in general can be a pointer of any type. An optional "old-key" clause will obtain the pointer to a key used in a previous
write-hash statement; it can be created with an optional "define".
You can also delete an element from the hash by using optional "delete" clause - the <value> is still obtained though it is no longer in the hash table. The hash element is deleted if "delete" clause is used without optional boolean expression <delete>, or if <delete> evaluates to true. Note that "delete" clause will not free any possibly
allocated memory for either key or value stored in the element (see
write-hash).
If no <key> was found in the hash table, optional <status> (in "status" clause) is VV_ERR_EXIST and <value> is NULL, otherwise it is VV_OKAY. A number <status> can be created with optional "define".
Note that while <key> and <old key> will contain matching strings, the <old key> will contain a pointer to a key used in a prior
write-string statement, which may be different than <key>.
With "traverse" clause
read-hash with "traverse" clause obtains <key> and <value> of the current element, and then positions to the next one. Use "begin" clause to position at the very first element. This is useful if you wish to get all the key/value pairs from a hash table - note they are not extracted in any particular order. When there are no more elements, <key> is NULL.
You may search, add or delete elements while traversing a hash table, and this will be reflected in all elements not yet traversed.
Examples
In this example, new hash is created, a key/value pair is written to it, and then the value is obtained and the element deleted; return status is checked:
new-hash h size 300
write-hash h key "X0029" value "some data"
read-hash h key "X0029" value define res status define f delete
if (f == VV_ERR_EXIST) {
@No data in hash!
} else {
@Deleted value is <<p-out res>>
}
The following will traverse the entire hash and display all the data:
read-hash h traverse begin
while (1) {
read-hash h traverse key define k value define r
if (k == NULL) break;
pf-out "Key [%s] data [%s]\n", k, r
}
See also
Hash table (
get-hash new-hash purge-hash read-hash resize-hash write-hash )
SEE ALL (
documentation)
read-json
Purpose: Parse JSON text and get values.
read-json <json> key <key> \
value [ define ] <value> \
[ status [ define ] <status> ]
[ type [ define ] <type> ]
read-json <json> traverse begin
read-json <json> traverse \
key [ define ] <key> \
value [ define ] <value> \
[ status [ define ] <status> ] \
[ type [ define ] <type> ]
read-json will obtain the values from <json> variable that was produced by parsing JSON text with
new-json.
Without "traverse" clause
key/value pairs are accessed with "key" clause specifying a normalized key name, which is the value's name preceded with the names of all objects and array members leading up to it, separated by a comma, with each name quoted. The actual <value> is obtained with "value" clause, and the <type> of value can be obtained with optional "type" clause.
The <status> variable in "status" clause is either VV_OKAY on success (meaning value is obtained) or VV_ERR_EXIST if not found. The value is stored in string variable <value> specified in "value" clause.
<value>, <type> and <status> can be created with optional "define".
Optional number <type> in "type" clause will contain the type of data, which can be VV_JSON_TYPE_STRING, VV_JSON_TYPE_NUMBER, VV_JSON_TYPE_REAL, VV_JSON_TYPE_BOOL or VV_JSON_TYPE_NULL for a string, integer, decimal, boolean and null value respectively.
With "traverse" clause
This allows sequential access to JSON values. No key is specified, rather key/value pairs are obtained in sequence from beginning to the end. <key> can be created with optional "define". The other clauses used are the same as without "traverse".
Random access via built-in hash
You can access randomly any number, string, boolean or null value within a JSON document without having to create a matching structure in your program. Internally, Vely creates a hash table for fast and direct access to any key/value pair (see
new-json).
Keys (normalized names)
<key> in "key" clause is a normalized name of any given leaf node in JSON text. This means every non-leaf node is included (such as arrays and objects), separated by a dot ("."), and arrays are indexed with "[]". An example would be:
"menu"."popup"."menuitem"[1]."onclick"
For instance, the above is a normalized name in this JSON text with the value of "OpenDoc()":
{"menu": {
"id": "file",
"value": "File",
"popup": {
"menuitem": [
{"value": "New", "onclick": "CreateNewDoc()"},
{"value": "Open", "onclick": "OpenDoc()"},
{"value": "Close", "onclick": "CloseDoc()"}
]
}
}
the normalized names for all leaf nodes are:
"menu"."id"
"menu"."value"
"menu"."popup"."menuitem"[0]."value"
"menu"."popup"."menuitem"[0]."onclick"
"menu"."popup"."menuitem"[1]."value"
"menu"."popup"."menuitem"[1]."onclick"
"menu"."popup"."menuitem"[2]."value"
"menu"."popup"."menuitem"[2]."onclick"
The code to parse this JSON text might then look like:
char json_text[] =\
"{\"menu\":\
{\"id\": \"file\",\
\"value\": \"File\",\
\"popup\":\
{\"menuitem\":\
[{\"value\": \"New\", \"onclick\": \"CreateNewDoc()\"},\
{\"value\": \"Open\", \"onclick\": \"OpenDoc()\"},\
{\"value\": \"Close\", \"onclick\": \"CloseDoc()\"}\
]\
}\
}\
}\n";
new-json define json_var from json_text status define st error-text define errt error-position define errp
if (st != VV_OKAY) {
@Could not parse json, error <<p-out errt>> at position <<p-num errp>>
} else {
@Json parsed okay.
}
read-json json_var key "menu"."popup"."menuitem"[1]."onclick" value define val status st
if (st != VV_OKAY) {
@Could not find json key
} else {
@Key value is <<p-out val>>
}
The result is:
Json parsed okay.
Key value is OpenDoc()
Values
All values are always returned as strings. This is because in many cases that's how they are used in an application and converting them to other types (like numbers) and back again would affect performance.
Vely checks type validity - for example an invalid integer or decimal number will produce an error. If you need to convert a value to a number you can use C's library functions like atoll() or atof().
String values are returned encoded as UTF8, and any escaped characters (like \n or \t) are converted to their binary equivalent (use "noencode" in
new-json to skip this). Such encoded strings can be output anywhere, from a terminal to a client (like browser).
Walk through JSON document
Use "traverse" clause to access JSON nodes sequentially, one by one, from the beginning to the end. Use "traverse begin" to rewind to the beginning, and then read data using "traverse" with "key" clause.
The following code loops through all the leaf nodes of a JSON document - you can also use it to examine a JSON document with unknown structure:
new-json define jv from json_text
read-json jv traverse begin
while (1)
{
read-json jv traverse key define k value define v type define t status define s
if (s != VV_OKAY) break;
pf-out "Name [%s], value [%s], type [%lld]\n", k, v, t
}
Examples
read-json jv key "glossary"."title" value d
See also
JSON (
delete-json new-json read-json )
SEE ALL (
documentation)
read-line
Purpose: Read text file line by line in a loop.
read-line <file> to [ define ] <line content> [ status [ define ] <length/status> ] [ delimiter <delimiter> ]
<any code>
end-read-line
read-line starts the loop in which a text <file> is read line by line into <line content> (which can be created with "define" if it doesn't exist), with end-read-line ending this loop. Once the end of file has been reached, or an error occurs, the loop exits.
The length of a text line can be obtained with optional <length/status>, which will be VV_ERR_READ if there is an error in reading file, or VV_ERR_OPEN if file cannot be opened, or VV_OKAY if end-of-file has been reached.
Check <length/status> variable within the loop to obtain the length of the currently read-in line, or after the loop for successful completion or an error condition.
Buffer allocated for each line read cannot be used in other iterations (i.e. when other lines are read) or outside the code between read-line and end-read-line. For that reason, if you want to save a read-in line for use elsewhere, save a copy of it using
copy-string.
<delimiter> separates the lines in the file, and is by default new line, however it can be any character (note that it is not a string, but a single character).
Each line is null-terminated and new line (or a <delimiter>) remains if it was present (last line may not have it). There is no limit on line length, and Vely will automatically adjust the size of <line content> to accommodate the line length.
<file> can be a full path name, or a path relative to the application home directory (see
vv).
Use standard C break and continue statements to exit and continue the loop.
Examples
To read a text file line by line, and display as a web page with line breaks:
read-line "/home/user/dir/file" to define one_line status define len
@Line length is <<p-num len>>, line is <<p-web one_line>><br/>
end-read-line
To read a text file delimited by "|" character:
read-line "/home/user/dir/file" to define one_line status define len delimiter '|'
...
See also
Files (
close-file copy-file delete-file file-position file_storage file_uploading lock-file open-file read-file read-line rename-file stat-file temporary_file uniq-file unlock-file write-file )
SEE ALL (
documentation)
rename-file
Purpose: Renames a file.
rename-file <from file> to <to file> [ status [ define ] <status> ]
rename-file will rename <from file> to <to file>. Optional <status> variable is VV_OKAY on success and VV_ERR_RENAME on failure, and can be created with optional "define".
<from file> and <to file> must be specified with full paths unless they are in the current working directory (see
vv), in which case a name alone will suffice, and can be in different directories.
Examples
Rename files:
rename-file "/home/u1/d1/f1" to "/home/u1/d2/f2" status define st
if (st == VV_OKAY) {
@Rename successful. <br/>
}
Rename files in the current working directory:
rename-file "f1" to "f2" status define st
if (st == VV_OKAY) {
@Rename successful. <br/>
}
See also
Files (
close-file copy-file delete-file file-position file_storage file_uploading lock-file open-file read-file read-line rename-file stat-file temporary_file uniq-file unlock-file write-file )
SEE ALL (
documentation)
Rename files
From time to time you may need to rename files within your project.
Vely source code files can be
request handlers (with file names that do not start with an underscore) and
non_request files (with names that start with an underscore).
A Vely
non_request file can be renamed freely. Whatever it's name is, it will be picked up automatically and compiled as a part of the project - so you don't have to worry about file names.
A Vely
request file can be renamed as well, and whatever it's name is, it will also be picked up and compiled as a part of the project. You must however, change the name of the handler function within such file to match the name of the file. For example a file "process_request.vely" must have a handler function implemented in it with the signature of "void process_request()". If you rename this file to "my_request.vely" then you must change the handler function to have a signature of "void my_request()".
See also
General (
deploying_application how_vely_works quality_control rename_files SELinux vely vely_architecture vely_removal vf vv why_C_and_Vely )
SEE ALL (
documentation)
report-error
Purpose: Reports a fatal error.
report-error <format>, <expression> [ , ... ]
To report a fatal error, and write the relevant description of it in the trace file (see
how_vely_works) regardless of whether tracing is enabled or not, use report-error. The error message is output in the same fashion as in
pf-out, where <format> (of string type) and <expression>s (of any type) are used as in C's "printf()" function.
Note that you must always have at least one <expression>, even when the entire output is just a string constant, so for example you would write:
report-error "%s", "Bad value for number of processes"
The reason for this is to avoid formatting errors, and to use formatting in a consistent fashion.
See
error_handling when report-error is called.
Examples
report-error "Too many input parameters, encountered total of [%lld]", num_count
See also
Error handling (
error_code error_handling on-error report-error )
SEE ALL (
documentation)
request-body
Purpose: Get the body of an HTTP request.
request-body <request body> [ length [ define ] <body length> ]
request-body stores the request body of an HTTP request into string <request body> which can hold text or binary data and which is created for that purpose. In either case, optional "length" clause provides the length in bytes of the body in <body length> variable, which can be created with optional "define".
If the content type of the request is "multipart/form-data", the request body is empty because all the data (including any attached files) can be obtained by using
input-param (see
file_uploading for files). In all other cases, request body is available.
Typical use of request-body is when some text or binary information is attached to the request, such as JSON for example, though it can be anything else, for example an image, some text, or a PDF document. Usually request body is present for POST, PUT or PATCH requests, but you can also obtain it for GET or DELETE requests, if supplied (for instance identifying a resource may require more information than can fit in a query string), or for any custom request method.
Examples
String variable "reqb" will hold request body of a request and "reqb_length" will be its length in bytes:
request-body reqb length define reqb_length
See also
Request information (
get-req input-param request-body set-input set-req )
SEE ALL (
documentation)
Request
Vely application runs by processing requests. A request always takes form of an HTTP request, meaning a URL and an optional HTTP request body. This is regardless of whether it's a web or command line application.
A request name is always specified in the URL path. By default a request URL path is comprised of application name and request name, so it may look like:
http://<your website>/<app name>/<request name>?some_param=some_value...
See
request_URL for more details on the structure of a URL, and
building_URL on using URL in your application.
vely_dispatch_request (a main Vely request dispatcher) uses request name to route the request to the appropriate function that handles it.
This handling is based on the names of .vely files, i.e. source code files that make up your project. The request name always matches the file name that handles it.
So for example, file "get_stock.vely" handles request "get_stock" by implementing a function "void get_stock()" in it. A request that is meant to call this function would have "/get_stock" in its URL path right after the application path (see
request_URL). The routing of this request to the namesake function will be done automatically by Vely. For instance, if application name is "stocks" and request name "get_stock", such request would be called by URL like:
http://<your website>/stocks/get_stock?some_param=some_value...
Thus file "get_stock.vely" must implement a function "void get_stock()", and in the code below, it simply outputs text "Hello from get_stock: some_value":
void get_stock()
{
input-param some_param
@Hello from get_stock: <<p-out some_param>>
}
If the name of .vely file starts with an underscore ("_"), then it is a
non_request file and it will not handle a request. See
how_vely_works.
You can use hyphens in URLs and they are converted to underscore by Vely. So for example the above URL could be written with "get-stock" instead of "get_stock":
http://<your website>/stocks/get-stock?some_param=some_value...
A request can have any number of input parameters, and based on them, perform any number of actions. A "request" generally refers to a combination of input parameters. Those requests that share the same request name but have different set of parameter names are its subrequests.
See also
Requests (
after_request_handler before_request_handler building_URL getting_URL global_request_data non_request normalized_URL request request_URL startup_handler vely_dispatch_request )
SEE ALL (
documentation)
Request URL
Application path
A
request URL is a URL that calls your Vely code. The leading part of its path is called "application path" and is determined by the caller; it is usually specified in a web proxy configuration or in an environment for command-line programs.
By default, application path is assumed to be the application name (see
how_vely_works), and would be:
So if your application name is "shopping", then the default application path is:
You would then specify this application path in the client, for instance:
- in Apache configuration file (see for example connect_apache_unix_socket):
ProxyPass "/shopping" unix:///var/lib/vv/shopping/sock/sock|fcgi://localhost/shopping
- in Nginx configuration file (see for example connect_nginx_unix_socket):
location /shopping { include /etc/nginx/fastcgi_params; fastcgi_pass unix:///var/lib/vv/shopping/sock/sock; }
- in a command-line program as the SCRIPT_NAME environment variable (see command_line):
export SCRIPT_NAME="/shopping"
Request name
Request name is always the first path segment after the application path, for instance:
https://some.web.site/shopping/buy-item
In this case the request name is "buy_item". Note that hyphen ("-") in the URL name is always converted to underscore ("_") for a Vely request name. This is true for all input parameters, see
input-param.
A request name can be can be made up of alphanumeric characters, hyphen or underscore only and cannot start with a digit. Often it is in the same form as the names of Vely
statement_APIs, which is that of "verb"-"object", for example "add-note", "show-orders" etc. This form clearly communicates the functional purpose of the request in the URL itself, as well as the names of source code files that handle them, which in this case would be "add_note.vely", "show_orders.vely" etc. Note however you can name a request any way you like.
URL payload
The actual URL payload, i.e. the input parameters, follow after the request name. For example, the full URL may be:
https://some.web.site/shopping/buy-item?name=ABC&price=600
Here, the required request name is "buy_item" and there are two other parameters ("name" and "price") with values of "ABC" and "600" respectively (see
request).
The input parameters can also be specified as path segments in the form of "/name/value". This is often done when writing a
REST API. So for example, the above URL can be:
https://some.web.site/shopping/buy-item/name/ABC?price=600
You can specify some or all parameters as path segments, so for example to specify all of them, the above URL can be written as:
https://some.web.site/api/v2/shopping/buy-item/name/ABC/price/600
The very last path segment can miss a value, and by default it would be "true", so if you add a parameter "edit":
https://some.web.site/api/v2/shopping/buy/name/ABC/price/600/edit
then this would be the same URL as before with the additional parameter "edit" of value "true".
If you specify input parameters as path segments, keep in mind that the path is meant to represent a logical hierarchy, where each path segment precedes in meaning the previous. In other words, input parameters representing a broader logical notion come first in the path, and those with a narrower one come afterwards. This order may represent the resource hierarchy (as it is often done with REST), but it does not have to.
Customizing application path
The application path can be customized to be any path you wish, but it must have the application name as the last segment:
This helps identify application at a glance. For example, if your application name is "shopping", the application path may be
and would be specified like this:
- in Apache configuration file (see for example connect_apache_unix_socket):
ProxyPass "/api/v2/shopping" unix:///var/lib/vv/shopping/sock/sock|fcgi://localhost/shopping
- in Nginx configuration file (see for example connect_nginx_unix_socket):
location /api/v2/shopping { include /etc/nginx/fastcgi_params; fastcgi_pass unix:///var/lib/vv/shopping/sock/sock; }
- in a command-line program as the SCRIPT_NAME environment variable (see command_line):
export SCRIPT_NAME="/api/v2/shopping"
For web requests, the URL sent by the caller might look something like this:
https://some.web.site/api/v2/shopping/buy-item?name=ABC&price=600
To tell Vely what the custom application path is, specify it in the "--path" parameter in
vv when making your application, in this case:
vv -q --path="/api/v2/shopping"
Parameters
It is up to you how you structure your parameters, i.e. the order in a query path or path segments, and which ones (if any) are in a query string. Regardless of your choices, the code handling a request is the same. In the example used here, you can obtain the parameters in request handler source file "buy_item.vely":
#include "vely.h"
void buy_item() {
out-header default
input-param name
input-param price
run-query @mydb = "insert into orders (name, price) values ('%s', '%s')" : name, price no-loop
@OKAY
}
Normalized URL
You can also write URLs where the entire request, including the request name, is in the query string. See
normalized_URL.
See also
Requests (
after_request_handler before_request_handler building_URL getting_URL global_request_data non_request normalized_URL request request_URL startup_handler vely_dispatch_request )
SEE ALL (
documentation)
resize-hash
Purpose: Resize hash table.
resize-hash <hash> size <new size>
resize-hash will resize hash table <hash> (created by
new-hash) to size <newsize>, which refers to the number of "buckets", or possible hash codes derived from keys stored.
When a number of elements stored grows, the search performance may decline if hash table size remains the same. Consequently, if the number of elements shrinks, the memory allocated by the hash table may be wasted. Use
get-hash to obtain its current size, its length (the number of elements currently stored in it) and the statistics (such as average reads) to determine if you need to resize it.
Resizing is generally expensive, so it should not be done too often. The goal is to amortize this expense through future gain of lookup performance. For that reason it may be better to resize proportionally (i.e. by a percentage), unless you have a specific application reason to do otherwise, or to avoid exponential growth.
Examples
resize-hash h size 100000
See also
Hash table (
get-hash new-hash purge-hash read-hash resize-hash write-hash )
SEE ALL (
documentation)
resize-mem
Purpose: Resize memory.
resize-mem <memory> size <size>
resize-mem resize memory <memory> that must have been previously allocated by Vely. Do not use it on memory allocated by any C-library functions (such as malloc(), calloc() or realloc()). If memory cannot be allocated, or if the pointer is not a valid one, the program will error out.
The pointer returned is void* and can be used for any purpose.
Examples
Allocate and resize memory:
new-mem define mystr size 300
resize-mem mystr size 1000
See also
Memory (
delete-mem manage-memory memory_handling new-mem resize-mem )
SEE ALL (
documentation)
rewind-fifo
Purpose: Rewind FIFO list to the beginning.
rewind-fifo will position at the very first data element put into <list> which was created with
new-fifo. Each time
read-fifo is used, the internal position moves to the next element in the order they were put in. rewind-fifo rewinds back to the very first one.
Examples
See
read-fifo.
See also
FIFO (
new-fifo purge-fifo read-fifo rewind-fifo write-fifo )
SEE ALL (
documentation)
rollback-transaction
Purpose: Rollbacks a SQL transaction.
rollback-transaction [ @<database> ] [ on-error-continue | on-error-exit ] [ error [ define ] <error> ] [ error-text [ define ] <error text> ]
rollback-transaction will roll back a transaction started with
begin-transaction.
Database
Optional <database> is specified in "@" clause and is the name of the
database_config_file.
Error handling
The error code is available in <error> variable in optional "error" clause - this code is always "0" if successful. The <error> code may or may not be a number but is always returned as a string value. <error> is
allocated memory. In case of error, error text is available in optional "error-text" clause in <error text>, which is
allocated memory.
"on-error-continue" clause specifies that
request processing will continue in case of error, whereas "on-error-exit" clause specifies that it will exit. This setting overrides database-level
on-error for this specific statement only. If you use "on-error-continue", be sure to check the error code.
<error> and <error text> can be created with optional "define".
Note that if database connection was lost, and could not be reestablished, the request will error out (see
error_handling).
Examples
begin-transaction @mydb
run-query @mydb="insert into employee (name, dateOfHire) values ('Terry', now())"
run-query @mydb="insert into payroll (name, salary) values ('Terry', 100000)"
rollback-transaction @mydb
See also
Database (
begin-transaction commit-transaction current-row database_config_file database_queries delete-query on-error prepared_statements query-result rollback-transaction run-query )
SEE ALL (
documentation)
run-query
Purpose: Execute a query and loop through result set.
run-query \
[ @<database> ] \
= <query text> \
[ ( output [ define ] <column name> [ , ... ] ) | unknown-output ] \
[ no-loop ] \
[ error [ define ] <error> ] \
[ error-text [ define ] <error text> ] \
[ affected-rows [ define ] <affected rows> ] \
[ row-count [ define ] <row count> ] \
[ on-error-continue | on-error-exit ] \
[ name <query name> ] \
[ column-count [ define ] <column count> ] \
[ column-names [ define ] <column names> ] \
[ column-data [ define ] <column data> ] \
[ : <input parameter> [ , ... ] ]
<any code>
[ end-query ]
run-prepared-query \
... ( the same as run-query ) ...
run-query executes a query specified with <query text>, which is a dynamic query text (i.e. computed at run-time), or it is a constant string value.
Database
Optional <database> is specified in "@" clause and is the name of the
database_config_file. It is optional if there is only one database used (see
vv), in which case it is the default.
Output
- output clause
The optional "output" clause is a comma-delimited list of the query's output columns. These column names are used in
query-result to get the columns values. The column names do not need to match the actual query column names, rather you can name them anyway you want, as long as they positionally correspond.
If optional "define" is used, then string variables with the same name are created for each column and query's output assigned to them, in which case each name must be a valid C identifier name. For example:
run-query @db = "select firstName, lastName from employees" output define first_name, last_name
...
end-loop
is the same as:
run-query @db = "select firstName, lastName from employees" output first_name, last_name
query-result first_name to define first_name
query-result last_name to define last_name
...
end-loop
"define" is useful in "output" clause to quickly and efficiently create query's output variables in very little code.
Note that the result obtained via "define" is always unencoded. If you need different encoding or other details about the result, use
query-result.
- unknown-output clause
If for some reason you don't know the number of output columns of the query (for instance in "SELECT * from ..." kind of query), use "unknown-output" clause, in which case you can use "column-data", "column-names" and "column-count" to get the query metadata in order to obtain the results. If you use neither "output" nor "unknown-output" clause, then your query has no output columns, for example it might be an "INSERT" or "DELETE" statement, or a DDL statement like "CREATE TABLE".
Input
The query's input parameters (if any) are specified with '%s' in the <query text> (note that single quotes must be included). The actual input variables are provided after optional semicolon (":"), in a list separated by a comma. Each input variable is a string regardless of the actual column type, as the database engine will interpret the data according to its usage. Each input variable is trimmed (left and right) before used in a query. Each <input parameter> may contain a comma if it is a string (i.e. quoted) or it is an expression within parenthesis.
Looping through data
"end-query" ends the loop in which query results are available (see
query-result). The optional "no-loop" clause includes implicit "end-query", and in that case no "end-query" can be used. This is useful if you don't want to access any output columns, but rather only affected rows (in INSERT or UPDATE for example), row count (in SELECT) or error code. "end-query" is also unnecessary for DDL statements like "CREATE INDEX" for instance.
Affected rows
The optional "affected-rows" clause provides the number of <affected rows> (such as number of rows inserted by INSERT). The number of rows affected is typically used for DML operations such as INSERT, UPDATE or DELETE. For SELECT, it may or may not be the same as "row-count" which returns the number of rows from a query. See your database documentation for more.
<affected rows> can be created with optional "define".
Rows returned
The number of rows returned by a query can be obtained in optional <row count> in "row-count" clause. <row count> can be created with optional "define".
Error handling
The error code is available in <error> variable in optional "error" clause - this code is always "0" if successful. The <error> code may or may not be a number but is always returned as a string value. <error> is
allocated memory. In case of error, error text is available in optional "error-text" clause in <error text>, which is
allocated memory.
"on-error-continue" clause specifies that
request processing will continue in case of error, whereas "on-error-exit" clause specifies that it will exit. This setting overrides database-level
on-error for this specific statement only. If you use "on-error-continue", be sure to check the error code.
<error> and <error text> can be created with optional "define".
Note that if database connection was lost, and could not be reestablished, the request will error out (see
error_handling).
Naming query
A query can be named with an optional "name" clause by specifying <query name>. By default, a query is assigned a generated name. When a query is named, you can use other statements such as
delete-query that reference the name. A query name must be unique and you will receive an error if it is reused with different queries.
run-prepared-query
run-prepared-query is the same as run-query except for a few important differences; see
prepared_statements.
Querying when column names are not known
If you do not know the column names (or even how many of them there are), use optional "column-count" clause to obtain the number of columns in <column count>, "column-names" to obtain the list of column names in <column names>, and "column-data" for the actual query results in <column data>. Use optional "define" to create any of them. To get a column count for instance:
run-query @mydb ="select name, lastName yearOfHire from employee" no-loop column-count define col_count
@Number of columns: <<p-num col_count>><br/>
<column names> is an array of strings containing names of query columns. Column names can be obtained only if "unknown-output" is used. If "define" is used, the array of strings is created, otherwise you need to do it yourself, as in:
char **col_names;
run-query @mydb ="select name, lastName yearOfHire from employee" no-loop column-names col_names
Note that column names are the names of query columns, which may or may not match any actual table column names, since query outputs can have aliases (and they must have them if the output is computed). In the following example, the output will be "employeeFirstName" and "employeeLastName" as they are aliases:
run-query @mydb="select firstName employeeFirstName, lastName employeeLastName from employee" unknown-output \
column-names to define col_names no-loop
@Column names are <<p-web col_names[0]>> and <<p-web col_names[1]>>
The array of <column names>, as well as each member of this array, are
allocated memory.
"column-data" clause will store a query result into an array of strings <column data>. Typical use of column-data is when a query text is constructed on the fly and the exact list of result columns of a query is unknown (see "unknown-output" clause). In that case, when running the query you can obtain the query metadata, such as number of rows (with "row-count" clause), the number of columns (with "column-count") and the output column names (with "column-names"). This way the query result is described and you can interpret the data obtained with "column-data".
"column-data" gets all the query's data laid out in a single data array, organized by repeating rows. For instance, suppose that table queried has 2 columns and the data is stored into "col_data" array. In that case, "col_data[0]" and "col_data[1]" would be the two columns' values from the first row, "col_data[2]" and "col_data[3]" would be the two columns' values from the second row, "col_data[4]" and "col_data[5]" would be the the columns' values from the third row etc. If "define" is used, then the <column data> variable is created. If it's not used, then you have to define the string array yourself, as in:
char **col_data;
run-query @mydb="select firstName employeeFirstName, lastName employeeLastName from employee" unknown-output \
column-data to col_data
This example obtains the number of rows and columns, as well as column names for a query. In this case the query is "select * from <table name>", where <table name> is provided at run-time - hence you might not know column names of the result set. Based on the number of rows and columns, all data resulting from the query is displayed in a "for" loop:
void get_table_data (const char *table_name)
{
(( define qry_txt
@select * from <<p-out table_name>>
))
run-query @mydb = qry_txt unknown-output row-count define row_count no-loop \
column-count define col_count column-names define col_names column-data define col-data
num i;
num j;
for (j = 0; j <row_count; j++) {
for (i = 0; i <col_count; i++) {
pf-out "colname %s, coldata %s\n", col_names[i], col_data[j*col_count+i]
}
}
}
The array of <column data>, as well as each member of this array, are
allocated memory.
Notes
"=" and "@" clauses may or may not have a space before the data that follows. So for example, these are both valid:
"@""="
run-query @db ="select firstName, lastName from employee where employeeId='%s'" output firstName, lastName : empid
"@""="
run-query @ db = "select firstName, lastName from employee where employeeId='%s'" output firstName, lastName : empid
Allocated internals
Internal memory used for a query is
allocated memory, which can be released with
delete-query.
Examples
Select first and last name (output is firstName and lastName) based on employee ID (specified by input parameter empid):
input-param empid
run-query @db = "select firstName, lastName from employee where employeeId='%s'" output firstName, lastName : empid
@Employee is <<query-result firstName>> <<query-result lastName>>
end-query
Prepared query without a loop and obtain error code and affected rows:
run-prepared-query @db = qry no-loop error define ecode affected-rows define arows : stock_name, stock_price, stock_price
When only a single database is used (a single
database_config_file for an application), then "@" clause can be omitted:
run-query =myqry no-loop : stock_name, stock_price, stock_price
See also
Database (
begin-transaction commit-transaction current-row database_config_file database_queries delete-query on-error prepared_statements query-result rollback-transaction run-query )
SEE ALL (
documentation)
SELinux
If you do not use SELinux, you can ignore this.
If you do use SELinux, read this. SELinux is MAC (Mandatory Access Control), which means anything that isn't allowed is prohibited. This is as opposed to DAC, Discretionary Access Control, where everything is allowed except what's prohibited. MAC generally works on top of DAC, and they are expected to work in a complementary fashion. Vely deploys both methods for enhanced security.
Vely comes with a SELinux policy out-of-the-box, which covers its general functioning. However, you can write any code with Vely, and if you are using SELinux, you may run afoul of its other policies, which may not be conducive to your code. In that case, use temporarily a permissive mode (via setenforce), and then audit2allow to get a clue on what is the issue and then take action to allow actions requested.
Note that OpenSUSE package does not come with SELinux policy as of this release, because OpenSUSE at this time does not come with a default base policy and SELinux installation.
General
Vely policy files (.te, .fc files, .if file is empty) can be found here:
ls $(vv -l)/selinux/*.{te,fc}
As a part of installing Vely, the following SELinux types will be installed:
- vvfile_t: all files within Vely directory (/var/lib/vv) are labeled with this type.
- vv_t: domain type (process type) of all Vely executables that communicate with other processes (be it Unix or TCP sockets). Only files labeled vvfile_t can run as this process type.
- vvport_t: port type that any Vely FastCGI (FCGI) process is allowed to bind, accept and listen. No other process types are allowed to do so.
Vely policy:
- allows Vely processes unconfined access. This is expected as Vely is a general purpose framework. It means you do not have to do anything to connect to database, use files, connect to other servers etc.
- allows web servers (httpd_t domain type) to connect to sockets labeled with vvfile_t, but does not allow any other access. This allows communication between reverse-proxy web servers and Vely applications.
- allows web servers to connect to any Vely FCGI process that is listening on a TCP port (see vv), but does not allow any other access (i.e. to any other ports).
Vely policy allows normal functioning of Vely features only, but does not introduce any unnecessary privileges to the rest of the system.
Note: Vely installation does not distribute .pp (compile) policy files, because it is not currently part of distro repos (which may change in the future). Due to changes in SELinux and difference in versions installed across derived distros, Vely will compile source .te and .fc files during the installation, ensuring the best possibility of successful SELinux policy setup.
Unix domain sockets
Using Unix domain sockets for Vely FCGI processes to communicate with a web server (see
vv) is the default method and no further action is needed.
Unix TCP sockets
Using TCP sockets for Vely FCGI processes to communicate with a web server (see
vv) requires you to label such ports as vvport_t, for example if you plan to use port 2109:
sudo semanage port -a -t vvport_t -p tcp 2109
When you no longer need a port, for example if you are switching to another port (for instance 2209), remove the old one and add the new one:
sudo semanage port -d -t vvport_t -p tcp 2109
sudo semanage port -a -t vvport_t -p tcp 2209
Changing or adding directories
If you are adding directories to be used by Vely program, or changing a directory, for example using a different storage instead of /var/lib/vv (see
how_vely_works), you need to label files in new directories:
sudo semanage fcontext -a -t vvfile_t "/your/new/dir(/.*)?"
sudo restorecon -R /your/new/dir
To remove context from such directories (if you are not using them anymore), use:
sudo semanage fcontext -d -t vvfile_t "/your/new/dir(/.*)?"
sudo restorecon -R /your/new/dir
See also
General (
deploying_application how_vely_works quality_control rename_files SELinux vely vely_architecture vely_removal vf vv why_C_and_Vely )
SEE ALL (
documentation)
send-file
Purpose: Send file to client.
send-file <file> [ headers \
[ content-type <content type> ] \
[ download [ <download> ] ] \
[ etag [ <etag> ] ] \
[ file-name <file name> ] \
[ ( cache-control <cache control> ) | no-cache ] \
[ status-id <status id> ] \
[ status-text <status text> ] \
[ custom <header name>=<header value> [ , ... ] ]
]
When a client requests download of a file, you can use send-file to provide <file>, which is its location on the server, and is either a full path or relative to the application home directory (see
vv). Note however that you can never use dot-dot (i.e. "..") in <file> - this is a security measure to avoid path-traversal attacks. Thus the file name should never have ".." in it, and if it does, the program will error out and stop.
Headers
The following are subclauses that allow setting any custom header:
- <content type> is content type (such as "text/html" or "image/jpg" etc.) If you are sending a file to a client for download and you don't know its content type, you can use "application/octet-stream" for a generic binary file.
- If "download" is used without optional boolean expression <download>, or if <download> evaluates to true, then the file is sent to a client for downloading - otherwise the default is to display file in client.
- <file name> is the name of the file being sent to a client. This is not the local file name - it is the file name that client will use for its own purposes.
- <cache control> is the cache control HTTP header. "no-cache" instructs the client not to cache. Only one of "cache-control" and "no-cache" can be used. An example of <cache control>:
send-file "somepic.jpg" headers cache-control "max-age: 3600"
- If "etag" is used without optional boolean expression <etag>, or if <etag> evaluates to true, then "ETAG" header will be generated (a timestamp) and included, otherwise it is not. The time stamp is of last modification date of the file (and typically used to cache a file on client if it hasn't changed on the server). "etag" is useful to let the client know to download the file only once if it hasn't changed, thus saving network and computing resources.
- <status id> and <status text> are status settings for the response (such as "425" for "status-id" and "Too early" for "status-text").
- To set any type of generic HTTP header, use "custom" subclause, where <header name> and <header value> represent the name and value of a single header. Multiple headers are separated by a comma. The maximum number of such headers is 64. You must not use "custom" to set headers already set elsewhere (such as "etag" for instance), as that may cause unpredictable behavior. For instance this sets two custom headers:
out-header use custom "CustomOption3"="CustomValue3", "Status"="418 I'm a teapot"
"custom" subclause lets you use any custom headers that exist today or may be added in the future, as well as any headers of your own design.
Cookies
Any cookies set prior to send-file (see
set-cookie and
delete-cookie) will be sent along with the file to the web client.
Examples
To send a document back to the browser and show it, use send-file with the "show" clause:
send-file "/home/vely/files/myfile.jpg" headers content-type "image/jpg"
An example to display a PDF document:
char *pdf_doc="/home/mydir/myfile.pdf";
send-file pdf_doc headers content-type "application/pdf"
If you want to send a file to the client for download (with the dialog), use "download" clause. This way the document is not displayed but the "Save As" (or similar) window shows up, for example to download a "PDF" document:
send-file "/home/user/file.pdf" headers download content-type "application/pdf"
See also
Web (
call-web out-header send-file silent-header )
SEE ALL (
documentation)
set-app
Purpose: Set process data.
set-app process-data <data>
set-app sets information related to an application or a process that runs it. A process may handle one or more requests, hence such data is cross-request and process-wide in scope. Some of this information is used by Vely and some is used by you in your code.
If you wish to set up
global_process_data, you can do so by providing <data> in the "process-data" clause, which is a pointer to any type. Internally, this pointer is saved as void*, so it can be cast to anything. <data> can be retrieved later anywhere in any request served by the process with
get-app (see "process-data" clause).
Process data has to be unmanaged memory because it is available across requests, see
memory_handling.
Examples
To set global process data, for instance in _startup.vely (i.e. in
startup_handler):
my_type *mydata;
manage-memory off
new-mem mydata size sizeof(my_type)
manage-memory on
set-app process-data mydata
See
global_process_data for more details and an example.
See also
Application information (
get-app set-app )
SEE ALL (
documentation)
set-cookie
Purpose: Set cookie.
set-cookie <cookie name>=<cookie value> \
[ expires <expiration> ] \
[ path <path> ] \
[ same-site "Lax"|"Strict"|"None" ] \
[ no-http-only [ <no-http-only> ] ] \
[ secure [ <secure> ] ]
To set a cookie named <cookie name> to value <cookie value>, use set-cookie statement. A cookie can be set before or after sending out a header (see
out-header). However a cookie must be set prior to outputting any actual response (such as with
output_statement or
p-out for example), or the program will error out and stop.
Cookie's <expiration> date (as a a string, see
get-time) is given with "expires" clause. The default is session cookie meaning the cookie expires when client session closes.
Cookie's <path> is specified with "path" clause. The default is the URL path of the
request_URL.
Whether a cookie applies to the same site is given with "same-site" clause along with possible values of "Lax", "Strict" or "None".
By default a cookie is not accessible to client scripting (i.e. "HttpOnly") -you can change this with "no-http-only" clause. That will be the case if "no-http-only" clause is used without optional bool expression <no-http-only>, or if <no-http-only> evaluates to true.
Use "secure" if a secure connection (https) is used, in order to specify this cookie is available only with a secure connection. That will be the case if "secure" is used without optional bool expression <secure>, or if <secure> evaluates to true.
Cookies are commonly used for session maintenance, tracking and other purposes. Use
get-cookie and
delete-cookie together with set-cookie to manage cookies.
Examples
To set a cookie named "my_cookie_name" to value "XYZ", that will go with the reply (back to the client, such as a browser) and expire in 1 year and 2 months from now, use:
get-time to define mytime year 1 month 2
char *my_cookie_value="XYZ";
set-cookie "my_cookie_name"=my_cookie_value expires mytime path "/" same-site "Lax"
A cookie that can be used by JavaScript (meaning we use no-http-only clause):
set-cookie "my_cookie_name"=my_cookie_value no-http-only
See also
Cookies (
delete-cookie get-cookie set-cookie )
SEE ALL (
documentation)
set-input
Purpose: Set or overwrite input parameters.
set-input <param name> = <param value>
set-input sets or overwrites an
input-parameter. <param name> is a string that holds parameter name and <param value> is a string that holds parameter value.
If an input parameter with <param name> already exists, it's value is overwritten with <param value>.
If an input parameter with <param name> does not exist, a new parameter is created and its value is set to <param value>.
Note that no copy of <param value> is made. If it will go out of scope before it's used, make a copy first (see
copy-string).
set-input is useful when input parameters supplied through a
request need to be altered or added-to so your code can process them more efficiently, to write less code etc.
Once set-input sets parameter value, using
input-param will obtain it as if it was provided through a request URL.
Note that
input-param creates a variable, so you cannot use it twice in the same scope. Typically, set-input is used to set input parameter values, while another code block or function will use input-param to obtain those values.
Examples
Set the value of input parameter named "quantity" to "0", which is also the output:
set-input "quantity" = "0"
...
input-param quantity
p-out quantity
Set the value of input parameter named "description" to "not available", which is also the output:
char *name = "description";
set-input name = "not available"
...
input-param description
p-out description
See also
Request information (
get-req input-param request-body set-input set-req )
SEE ALL (
documentation)
set-req
Purpose: Set request data.
set-req sets information used to process a request. Some of this information is used by Vely and some is used by you in your code.
If you wish to set up
global_request_data, you can do so by providing <data> in the "data" clause, which is a pointer to any type. Internally, this pointer is saved as void*, so it can be cast to anything. <data> can be retrieved later anywhere during a request with
get-req (see "data" clause).
Examples
To set global request data:
my_type *mydata;
...
set-req data mydata
See
global_request_data for more details and an example.
See also
Request information (
get-req input-param request-body set-input set-req )
SEE ALL (
documentation)
silent-header
Purpose: Do not output HTTP headers.
silent-header will suppress the output of HTTP headers, such as with
out-header, or in any other case where headers are output. The effect applies to current
request only; if you use it conditionally, then you can have it on or off dynamically. If you want to suppres the headers across the entire command-line application, use VV_SILENT_HEADER environment variable; see
command_line.
Note that you still have to use
out-header regardless, as it is expected to be there if you output anything; however its actual output will be suppressed.
There are many uses for silent-header, among them:
- A command line program may use it to produce generic output, without any headers,
- the output from command line program may be redirected to a web file (such as html), in case of dynamic content that rarely changes,
- a web program may output a completely different (non-HTTP) set of headers, etc.
Examples
See also
Web (
call-web out-header send-file silent-header )
SEE ALL (
documentation)
split-string
Purpose: Split a string into pieces based on a delimiter.
split-string \
( <string> with <delimiter> to [ define ] <result> \
| delete <result> )
split-string will find all instances of <delimiter> in <string> and then split it into pieces. The <result> is a pointer to a variable of type "vely_split_str" and it has two members: integer variable "num_pieces" which is the number of pieces <string> was broken into, and string array "pieces", which holds the actual pieces, each null-terminated. If variable <result> does not exist, you can create it with define clause. A delimiter within double quotes ("..") is not counted, i.e. it is skipped.
Note that <string> is altered by placement of null-terminators and will not hold the same value (rather it will hold only the leading portion of what it did before split-string took place). Each element of "pieces" array points to memory occupied by <string>. Hence, split-string does not copy any data and is very fast in performing the kind of parsing described here. You can copy string beforehand if you don't want it altered (see
copy-string).
All pieces produced will be trimmed both on left and right. If a piece is double quoted, then double quotes are removed. For instance:
char clist[] = "a , b, \"c , d\" , e"
split-string clist with "," to define res
After this, the variable "res" will be an array of strings with these values:
res->num_pieces is 4
res->pieces[0] points to "a"
res->pieces[1] points to "b"
res->pieces[2] points to "c , d"
res->pieces[3] points to "e"
Also, since <string> is altered, it cannot be a constant - rather it must always be a variable, for example, if you do this with the intention to split this string based on "," as a delimiter:
char *str = "string,to,split";
your program will report an error (SIGSEGV most likely, or segmentation fault). You should do:
char str[] = "string,to,split";
split-string is useful for parsing CSV (Comma Separated Values) or any other kind of separated values, where separator can be any string of any length, for example if you're parsing an encoded URL-string, then "&" may be a separator, as in the example below.
Allocated internals
<result> is
allocated memory along with additional internal memory, which can be released if "delete" clause is used on a <result> from a previously executed split-string. See
memory_handling for more on when (not) to delete memory explicitly like this; the same rules apply as for
delete-mem.
Examples
The following will parse a string containing name/value pairs (such as "name=value") separated by string "&":
"&"
char instr[]="x=23&y=good&z=hello_world";
"amp;"
split-string instr with "&" to define assignment
num i;
for (i = 0; i < assignment->num_pieces; i++) {
split-string assignment->pieces[i] with "=" to define data
pf-out "Variable %s has value %s\n", data->pieces[0], data->pieces[1]
}
The result is:
Variable x has value 23
Variable y has value good
Variable z has value hello world
See also
Strings (
copy-string count-substring lower-string split-string trim-string upper-string write-string )
SEE ALL (
documentation)
Startup handler
Purpose: Execute your code once before any request handlers do.
To specify your code to execute once before any
requests are handled, create a source file "_startup.vely" and implement a function "void _startup()", which will be automatically picked up and compiled with your application.
Startup handler will execute just before the first request. It will not execute when the application starts, but when it receives the very first request.
Important: if you need cross-request global variables that would be available for the life of the process, i.e. to any request served by this process, do not use result(s) of any Vely statements, because such memory is released at the end of each request, and thus would become invalid after very first request served by this process. If you must, you can use either global variables or use C's "malloc()" functions. Note however, that this is rarely needed and generally should be avoided.
Examples
Here is a simple implementation of startup handler that just outputs "Hi there!!":
#include <vely.h>
void _startup()
{
out-header default
@Hi there!!
}
See also
Requests (
after_request_handler before_request_handler building_URL getting_URL global_request_data non_request normalized_URL request request_URL startup_handler vely_dispatch_request )
SEE ALL (
documentation)
Statement APIs
Statement APIs, or Vely statements (such as
write-file or
run-query etc.) are programming statements written within C code in files with .vely extension. They are pre-processed by
vv into C code, and then compiled into a native executable. A statement does something useful, for example runs a database query, searches a string with regex, calls a web service, parses JSON, creates a unique file, etc. (see
documentation).
Statements vs API
Writing a single Vely statement instead of multiple C API calls is easier to read and maintain, less error prone, safer at run-time and more productive. The functionality, scope and default behavior of each statement are chosen to reflect those benefits for typical practical needs of application development. They are meant to be building blocks that enable productivity and collaboration.
A statement is not a macro or a simple substitution for one or more API calls. A statement can perform multiple functions that are logically connected, but would require use of different API calls and in different configurations to achieve the functionality. For example
run-query can perform a simple query with no input or output parameters, or a complex one with them, and with or without a number of other options. Achieving these functionalities with APIs would require different ones used in different ways, increasing chances of human error. Note that sorting out how to do things like this is performed at compile time - no performance is lost at run-time figuring out the best way to achieve Vely statement's stated goal.
Statement name is always in the form of two words with hyphen in between:
- imperative "verb"-"object" (call-web or run-query for instance), where the action applies to an object,
- descriptive "adjective"-"noun" ("input-param", "query-result" for example), where the action is further provided in clause(s),
- transformational "noun"-"noun", where one object transforms (converts) into another,
along with the clauses supplying the rest of the predicate, such as subject or subordinate clause(s). The two-word model is chosen for simplicity in order to avoid unwieldy do-this-that-way-along-with-something-else or such kind of statements, which unfortunately sometimes proliferate in APIs.
This design mimics natural speech and is typical of declarative languages, as it is easier to write and read than APIs. Just like in a natural language, the action asked in a single statement can be very simple, moderate or involved depending on the need, and the "sentence" used to carry it out is easy to comprehend at a glance, and easy to construct based on "what" needs to be done rather than "how". Even so, the C code generated is still done with performance in mind first and foremost, and the statement design is ultimately always guided by that goal.
Vely statements are the core of the language functionality. Here's a formal description of what they are.
Statement structure
Vely statements generally have three components separated by space(s):
- a name
- an object argument
- a number of clauses
A statement starts with a name, which designates its main purpose. An object argument denotes the object of the purpose stated in the name. Each clause consist of a clause name, which specifies some aspect of the statement's purpose and it may be followed by no additional data, or it may be accompanied with one or more data arguments. A clause may have subclauses, which follow the same structure and are associated with the clause by appearing immediately after it. Most clauses are separated by space(s), however some (like "=" or "@") may not need space(s) before any data; the statement's documentation would clearly specify this.
An object argument must immediately follow the statement's name, while clauses may be specified in any order.
For example, in the following Vely code:
encrypt-data orig_data input-length 6 output-length define encrypted_len password "mypass" salt newsalt to define res binary
encrypt-data is the statement's name, and "orig_data" is its object argument. The clauses are:
- input-length 6
- output-length define encrypted_len (with "define" being a subclause of "output-length" clause)
- password "mypass"
- salt newsalt
- to define res (with "define" being a subclause of "to" clause)
- binary
The clauses can be in any order, so the above can be restated as:
encrypt-data orig_data to define res password "mypass" salt newsalt output-length define encrypted_len binary input-length 6
Vely documentation provides a concise BNF-like notation of how each statement works, which in case of
encrypt-data is (backslash simply allows continuing to multiple lines):
encrypt-data <data> to [ define ] <result> \
[ input-length <input length> ] \
[ output-length [ define ] <output length> ] \
[ binary [ <binary> ] ] \
( password <password> \
[ salt <salt> [ salt-length <salt length> ] ] \
[ iterations <iterations> ] \
[ cipher <cipher algorithm> ] \
[ digest <digest algorithm> ]
[ cache ]
[ clear-cache <clear cache> ) \
[ init-vector <init vector> ]
Optional clauses are enclosed with angle brackets (i.e between "[" and "]"), and data arguments (in general C expressions) are stated between "<" and ">". If only one of a number of clauses may appear, such clauses are separated by "|", and each clause possibly enclosed with "(" and ")" if it consists of more than one keywords or arguments. Generally a clause continues until the next clause, which means until all subclauses and arguments are exhausted.
The most common subclause is an optional "define", which always precedes an output variable, i.e. a variable that stores (one of) the results of the statements. If used, it creates such variable within the statement. It is commonly used to shorten the code written.
Keywords (other than statement names such as encrypt-data above) are generally specific to each statement (or a group of statements in which they are used). So, keyword "salt", for example, has meaning only within encrypt-data statement, where it is used to specify the data for the "salt" clause. In order to have the complete freedom to choose your variable names so they don't clash with keywords, you can simply surround them (or the expressions in which they appear) in parenthesis (i.e. "(" and ")") and use any names you want, without worrying about keywords, for example:
const char *password = "some password";
const char *salt = "0123456789012345";
encrypt-data "some data" password (password) salt (salt) to define (define)
p-out define
In this example, keywords "password", "salt" and "define" are used as variable names as well; and in p-out statement, variable named "define" is used freely, even if it is a keyword for other statements - but it is not for the p-out statement.
It is recommended to use supplied Vely statements over your C code for the same functionality.
Note that while you can use tab characters at the beginning of the line (such as for indentation), as well as in string literals, do not use tabs in Vely code as they are not supported for lack of readability - use plain spaces.
Look and feel
Vely statements have decidedly non-C look and feel, unlike what's common with typical API interface. This is by design. They stand out when reading code in a way that clearly communicates their purpose, with the intent of increased readability and more expressive and condensed functionality. On the other hand, Vely statements are decidedly C, as they are completely integrated with C code and translate to pure C in the end.
Constructs in code blocks
Note that, Vely statements, after translated into C code by
vv, are generally made of multiple C statements, hence Vely statements can never be treated as single-line statements. Thus, for example, Vely will emit an error if you write:
if (some condition) vely-statement
if (some condition)
vely-statement
Instead, write:
if (some condition) {
vely-statement
}
Integration with C
Vely is loosely integrated with C, with the integration points chosen for maximum practical benefit. It does not parse C to work, mostly because that does not present significant benefits and it would slow down code generation.
Vely does, however, process certain C elements. It processes strings in C expressions used in Vely statements, as well as parenthesis to make sure that statement clauses are parsed properly.
Probably the most important aspect of integrating with gcc is line number reporting. Line numbers and error messages reported by gcc and gdb are referring to source Vely files, making it easy to find the exact source code line of any issue.
Note that at the same time, if you use --c-lines option (see
vv), it forces generated C code line reporting. That is rarely necessary because gcc messages are typically detailed and sufficient.
Splitting statement into multiple lines
To split a statement into multiple lines (including string continuations), use a backslash (\), for instance:
encrypt-data orig_data input-length 6 \
output-length define encrypted_len \
password "my\
pass" salt \
newsalt to define res binary
Note that all statements are always left-trimmed for whitespace. Thus the resulting string literal in the above example is "mypass", and not "my pass", as the whitespaces prior to line starting with "pass" are trimmed first. Also, all statements are right-trimmed for white space, except if backslash is used at the end, in which case any spaces prior to backslash are conserved. For that reason, in the above example there is a space prior to a backslash where clauses need to be separated.
Error handling
A statement that fails for reasons that are generally irrecoverable will fail, for example out of memory or disk space, bad input parameters etc. Vely philosophy is to minimize the need to check for such conditions by preventing the program from continuing. This is preferable, as forgetting to check usually results in unforeseen bugs and safety issues, and the program should have stopped anyway.
Errors that are correctable programmatically are reported and you can check them, for example when opening a file that may or may not exist.
Overall, the goal is to stop execution when necessary and to offer the ability to handle an issue when warranted, in order to increase run-time safety and provide instant clues about conditions that must be corrected.
Code processing, error reporting, debugging
A statement is pre-processed into C code, which is then compiled into a native executable using gcc compiler. The final C code is a mix of your own code and generated Vely code.
Error reporting by default shows line numbers in your .vely source code files, which generally makes it easy to pinpoint an error. If you want to see generated C code and make error reporting refer to it, use "--c-lines" option of
vv utility; this is useful if the error refers to details of generated code. You can also obtain the line number from .vely source file, and then examine generated source code file by looking for #line directives. Generated final C file is located in:
/var/lib/vv/bld/<app name>/__<source file base name>.o.c
For instance if application name is "myapp" and source file is "mycode.vely", then generated code is in file:
/var/lib/vv/bld/myapp/__mycode.o.c
If an error is reported as being on line 43 of file "mycode.vely", then look for lines in the above .c file that look like:
The code adjacent to those lines is the generated code for the Vely statement at line 43 in "mycode.vely".
Vely will perform many sanity checks when possible while preprocessing your .vely files, such as enforce the presence and count of required arguments in clauses, check if conflicting clauses are used, report imbalance of opening/closing clauses in statements that use them (such as for example
write-string,
read-line or with
database_queries), reject unknown statements, report incorrect usage of statements and similar. However, most of the checking of your code is ultimately done by gcc (Linux C compiler), such as typing, expression syntax, C language correctness, linkage issues and others; in doing so, gcc will report the correct line number, as stated above (either a line number in your .vely source file, or a generated C code file).
When debugging (such as with using gdb), stepping through your code is similar to error reporting: by default gdb will treat Vely statement as a "single-statement" and step over it as such. If you use "--c-lines" option, then you will be able to step through final C code.
See also
Language (
dot inline_code statement_APIs syntax_highlighting unused-var )
SEE ALL (
documentation)
stat-file
Purpose: Get information about a file.
stat-file <file> \
size | type | path | name \
to [ define ] <variable>
stat-file gets information about <file>, which is either the full path of a file or directory, or a name relative to the application home directory (see
how_vely_works).
Clause "size" will store file's size in bytes to <variable>, or it will be VV_ERR_FAILED (if operation failed, likely because file does not exist or you have no permissions to access it).
Clause "type" will store file's type to number <variable>, and it can be either VV_FILE (if it's a file) or VV_DIR (if it's a directory) or VV_ERR_FAILED (if operation failed, likely because file does not exist or you have no permissions to access it).
Clause "path" gets the fully resolved path of the <file> (including symbolic links), and "name" is the name (a basename, without the path). The results of both "path" and "name" clauses are
allocated memory. If path cannot be resolved, then <variable> is an empty string.
Examples
To get file size in variable "sz", which is created here:
stat-file "/home/user/file" size to define sz
To determine if the object is a file or a directory:
stat-file "/home/user/some_name" type to define what
if (what == VV_FILE) {
@It's a file!
} else if (what == VV_DIR) {
@It's a directory!
} else {
@Doesn't exist!
}
Get the fully resolved path of a file to string variable "fp", which is created here.
stat-file "../file" path to define fp
See also
Files (
close-file copy-file delete-file file-position file_storage file_uploading lock-file open-file read-file read-line rename-file stat-file temporary_file uniq-file unlock-file write-file )
SEE ALL (
documentation)
Syntax highlighting
For syntax highlighting of Vely programs in Vim, do this once:
The above will create a syntax file in your local Vim syntax directory:
and also update your local $HOME/.vimrc file to use this syntax for files with .vely extension. All files updated are local, i.e. they affect only the current user. Each user who wants this feature must issue the above command.
The Vely highlighting syntax is tested with Vim 8.1.
See also
Language (
dot inline_code statement_APIs syntax_highlighting unused-var )
SEE ALL (
documentation)
Temporary file
To create a temporary file, use
uniq-file with a "temporary" clause. Temporary files are the same as any other files in the
file_storage (and are organized in the same fashion), except that they are all under the subdirectory named "t":
/var/lib/vv/<app_name>/app/file/t
A temporary file is not automatically deleted - you can remove it with
delete-file statement when not needed (or use a periodic shell script to remove old temporary files). The reason for this is that the nature of temporary files varies, and they may not necessarily span a given time frame (such as a lifetime of a
request, or a lifetime of a process that serves any number of such requests), and they may be used across number of requests for a specific purpose. Thus, it is your responsibility to remove a temporary file when it's appropriate for your application to do so.
The reason for storing temporary files in a separate directory is to gain a separation of temporary files (which likely at some point can be freely deleted) from other files.
See
uniq-file for an example of creating a temporary file.
See also
Files (
close-file copy-file delete-file file-position file_storage file_uploading lock-file open-file read-file read-line rename-file stat-file temporary_file uniq-file unlock-file write-file )
SEE ALL (
documentation)
trace-run
Purpose: Emit trace.
trace-run [ <format>, <expression> [ , ... ] ]
trace-run formats a tracing message according to the <format> string and a list of variable-type <expression>s (in the same way C's printf() does) and then writes the result into current process' trace_file (if enabled, see
vv) without any encoding (meaning a string is output exactly as it is).
trace-run can be used without any clauses, in which case a location (file name and line number) is recorded in the trace file - this is useful when you only want to know if the execution passed through code.
If trace-run has any other clauses, then <format> string must be present and there must be at least one <expression> (it means if you want to trace a simple string literal you still have to use "%s" as format).
For tracing to have effect, debugging and tracing must be enabled (see
vv). For location of trace files, see
how_vely_works.
Examples
trace-run "Program wrote %lld bytes into file %s", num_bytes, file_name
trace-run
See
example_hello_world for an example in debugging and tracing.
See also
Debugging (
debugging trace-run )
SEE ALL (
documentation)
trim-string
Purpose: Trim a string.
trim-string <string> [ length [ define ] <new length> ] [ result [ define ] <result> ]
trim-string trims <string>, both on left and right. You can optionally get the length of the changed string by using "length" clause, in integer variable <new length>, which can be created with optional "define".
Without "result" clause, trimming is in place, i.e. if necessary the bytes within <string> are shifted to the left, and the result is <string>. With "result" clause, <result> points to trimmed value within <string>, i.e. there is no movement of memory - this is a faster trim, but the pointer to the result is no longer <string>.
Examples
The variable "str" will be "some string" and "new_len" will be 11:
char str[] = " some string ";
trim-string str length define new_len
With "result" clause, the variable "str" will be "some string" and "new_len" will be 11, and "res" will point to (str+2):
char str[] = " some string ";
trim-string str length define new_len result define res
See also
Strings (
copy-string count-substring lower-string split-string trim-string upper-string write-string )
SEE ALL (
documentation)
uniq-file
Purpose: Create a new empty file with a unique name.
uniq-file [ define ] <file name> [ temporary ]
One of the common tasks in many applications is creating a unique file (of any kind, including temporary). uniq-file statement does that - it creates a new unique file of zero size, with <file name> being its fully qualified name, which is always within the
file_storage. <file name> is
allocated memory.
If string variable <file name> does not exist, it can be created with "define" clause. The file itself is created empty. If "temporary" clause is used, then the file created is a
temporary_file.
The file has no extension. You can rename it after it has been created to reflect its usage or purpose.
All files created are setup with owner and group read/write only permissions.
Examples
The following creates an empty file with auto-generated name that will be stored in "mydoc" variable. String variable "mydoc" is defined in the statement. The string "some data" is written to a newly created file:
uniq-file define mydoc
write-file mydoc from "some data"
To create a temporary file:
uniq-file define temp_file temporary
...
"temp_file"
..
delete-file temp_file
See also
Files (
close-file copy-file delete-file file-position file_storage file_uploading lock-file open-file read-file read-line rename-file stat-file temporary_file uniq-file unlock-file write-file )
SEE ALL (
documentation)
unused-var
Purpose: Prevent compiler error if variable is not used.
unused-var <variable name>
unused-var prevents C compiler from erroring out if <variable name> is unused. Generally, you don't want to have unused variables - they typically indicate errors or clutter. However, in some cases you might need such variables as a reminder for a future stage of a project, or for some other reason it is unavoidable. In any case, you can use unused-var to shield such instances from causing errors.
Examples
In the following, variable "bw" is created and the number of bytes written is stored in it. Such variable is not used at the moment, however if you would use it in the future and want to keep it, use unused-var to prevent compiler errors:
pf-out bytes-written define bw "Hi %s\n", "world"
unused-var bw
See also
Language (
dot inline_code statement_APIs syntax_highlighting unused-var )
SEE ALL (
documentation)
utf8-json
Purpose: Convert UTF8 string to JSON text.
utf8-json <utf8> \
to [ define ] <json> \
[ length <length> ] \
[ status [ define ] <status> ] \
[ error-text [ define ] <error text> ]
utf8-json will convert <utf8> text to <json> text which can be created with optional "define". <json> is
allocated memory.
<utf8> is a string that may contain UTF8 characters (as 2, 3 or 4 bytes representing a unicode character). Encoding creates a string that can be used as a value in JSON documents. utf8-json is performed according to
RFC7159 (JSON standard) and
RFC3629 (UTF8 standard).
Note that hexadecimal characters used in JSON for Unicode (such as \u21d7) are always lowercase. Solidus character ("/") is not escaped, although
json-utf8 will correctly process it if the input has it escaped.
<json> is always allocated in utf8-json with storage sufficient to hold it.
The optional length of <utf8> can be specified with <length> in "length" clause. If <length> is not specified, it is calculated as string length of <utf8>. Note that a single UTF-8 character can be anywhere between 1 to 4 bytes. For example "љ" is 2 bytes in length.
The status of encoding can be obtained in optional <status> variable (a number), which can be created with optional "define". <status> is the string length of the result in <json> or -1 if error occurred, in which case the error text can be obtained in <error text> in optional "error-text" clause, which can be created with optional "define".
Examples
Convert UTF8 string to JSON value and verify the expected result:
char utf8_str[] = "\"Doc\"\n\t\b\f\r\t⇗⇘\t▷◮𝄞ᏫⲠш\n/\"()\t";
utf8-json utf8_str status define encstatus to define json_text
(( define expected_result
@\"Doc\"\n\t\b\f\r\t\u21d7\u21d8\t\u25b7\u25ee\ud834\udd1e\u13eb\u2ca0\u0448\n/\"()\t
))
if (!strcmp (json_text, expected_result) && encstatus!=-1) {
@decode-json worked okay
}
See also
UTF8 (
json-utf8 utf8-json )
SEE ALL (
documentation)
Vely architecture
Architecture overview
A Vely program works as a
request-reply processor. It can be either an application server or a command-line program that processes GET, POST, PUT, PATCH, DELETE or any other HTTP requests.
When it is an application server, it runs as
FastCGI (FCGI) server processes that clients access through reverse proxies (typically web servers like Apache or Nginx). Any number of such Vely FCGI processes can run, including a dynamic number determined by the request load (ranging from 0 resident processes to any maximum number specified). Each Vely FCGI process handles one request at a time, and any number of such processes work in parallel, which means code and libraries used do not have to be thread-safe. Vely application layer is separate from the presentation (eg. web server) and data (eg. database) layers. These layers can run on multiple tiers, separated not only functionally, but physically as well (meaning they can run on different real or virtual hardware), improving scalability, reliability and security.
When a Vely application runs in the
command_line, a request is handled by a single execution of a compiled program. This option may be suitable for batch jobs, for use in shell scripts, as well as any other situation where it is more useful or convenient to execute a command-line program. Note that a command-line program can double as
CGI (Common Gateway Interface) programs as well; this option may be suitable when high performance is not needed, or for other reasons.
A Vely program can access database, internet/network, file system or any other computing resource.
Execution flowchart
Before the very first request a Vely program serves, startup code in optional _startup.vely executes. Every request must have an application name and a request name in its
request_URL (for instance "do_something"). Before handling this request, an optional before-request handler in _before.vely executes. Vely's request dispatcher will route the request to a function with the same name (in this case "void do_something()") that must reside in a file with the same name (which is do_something.vely). After the code in that file executes, an after-request handler in _after.vely executes (optional). This concludes request processing. Note that any .vely file name that starts with an underscore is a non-request file, that is, it doesn't process any requests and its code is used in other .vely files.
Performance and stability
Vely applications are native executables by design. This approach avoids performance loss associated with other schemes, such as byte-code, interpreters and similar. Consequently, small memory footprint is achieved with minimal code overhead and by using on-demand dynamic libraries whenever possible; less memory consumption and higher performance allow for better scaling in high-demand environments, with cloud applications and IOT (Internet of Things) being an example.
FastCGI server processes generally stay up across any number of requests, increasing response time. The balance between the number of processes and the memory usage during high request loads can be achieved with adaptive feature of
vf, Vely's FastCGI process manager. Since Vely processes running in parallel are single-threaded, programming with Vely does not present challenges of thread-based programming and increases run-time stability.
Vely supports use of prepared database queries with true automatic unlimited reuse across any number of requests; database connections are persistent and stay connected for the life of the server process.
As far as stability, Vely
statement_APIs are designed for safety, ease of use, encapsulation and to some extent abstraction of tasks required, making it more difficult to write unstable code. Handling of
allocated memory includes memory tracking and garbage collection, preventing memory leaks which can be fatal to long running processes; similarly, files open with file-handling statements are automatically closed at the end of each request, serving the same purpose. Request-processing and process daemon-izing infrastructure is included. Where possible, use Vely statements and infrastructure instead of pure C in order to get the most of stability enhancing benefits.
From source code to executable
All Vely code is in .vely files, which contain C code with Vely used freely within. To create an executable program,
vv utility will preprocess .vely files into 100% C code, and link it. Linkage uses various libraries (database, internet etc.), and those are dynamically linked only if needed, keeping the executable the smallest possible. When code changes, Vely FCGI manager (
vf) will restart the server (optional).
Vely programs are built in a single command-line step, and ready to use immediately (see
vv). Flexible architecture means the exact same source code can run both as a server application (such as web application back-end) and a command-line program.
Read more about
how_vely_works.
See also
General (
deploying_application how_vely_works quality_control rename_files SELinux vely vely_architecture vely_removal vf vv why_C_and_Vely )
SEE ALL (
documentation)
Vely dispatch request
"void vely_dispatch_request()" is an automatically generated
request-dispatching function in Vely. It uses request name (see
request_URL) to call the appropriate request handler.
For example, if the request name is "myreq", then function with signature "void myreq()" will be called - such function must be implemented in "myreq.vely" source code file.
You can implement two hooks into vely_dispatch_request(): one that executes before each request handling (
before_request_handler) and one that executes afterwards (
after_request_handler).
In terms of debugging, breaking in this function gives you a good starting point to debug the handling of any given request, for instance in gdb:
If no request has been recognized (i.e. request name does not match any request-handling .vely source file), then
At the end of the request, all strings allocated by Vely will be freed.
You cannot change the implementation of vely_dispatch_request(), but you can see it in the build directory (see
vv).
See also
Requests (
after_request_handler before_request_handler building_URL getting_URL global_request_data non_request normalized_URL request request_URL startup_handler vely_dispatch_request )
SEE ALL (
documentation)
Vf
Purpose: Run and manage FastCGI programs.
vf is a FastCGI (FCGI) program manager. An FCGI program is started as a number of concurrent processes serving application requests, typically from reverse-proxy servers such as Apache or Nginx. Use vf to create Vely applications, including both FCGI and command-line.
A number of options are available to setup and manage the execution of a FCGI program as an application server, which can be accessed either via TCP/IP or a Unix domain socket.
vf is a part of
vely package. You can, however, use vf in any case, whether your FCGI programs are created by Vely or otherwise.
<app name> specifies the name of your application. Each application must have a unique name. <app name> may contain alphanumeric characters and an underscore, must start with a character and its maximum length is 30.
vf runs as a light-weight daemon (often requiring only 100-150K of resident RAM), with a separate instance for each application specified by the <app name>. You must supply a valid FCGI program to run. When vf starts your FCGI program, its current directory is set to /var/lib/vv/<app name>. The permissions context is inherited from the caller, so the effective user ID, group ID and any supplemental groups are that of the caller. You can use tools like runuser to specifically set the permissions context.
vf will re-start FCGI processes that exited or died, keeping the number of processes as specified, unless -n option is used. The number of worker FCGI processes can be specified with a fixed (-w) option, or it can dynamically change based on the load (-d option), including none at all. Hence, it is possible to have no worker processes at all, and they will be started when incoming request(s) come in.
Your FCGI program must handle SIGTERM signal and gracefully exit, meaning exit by completing the request that may be executing when SIGTERM was received. See
plain_C_FCGI example.
<options> are:
- -i
Initialize the directory and file structure for application <app name>. If you are building application from source code, this must be executed in the source code directory; vf will create file ".velyapp" which identifies the application so vv can run in the directory. You must run as root when using this option (and must not run as root otherwise). The directory structure is setup in /var/lib/vv/<app name>, and is the same directory structure used by Vely.
- -u <user>
The owner of your application. This is only used when initializing directory structure used by vf (see -i option). Do not use it otherwise. It cannot be root.
- -r <proxy group>
The group of proxy web server (such as Apache or Nginx). This is only used when initializing directory structure used by vf (see -i option). Do not use it otherwise. It restricts the ability to connect to your application only to the members of said group (in addition to the user who owns your server), otherwise anyone can connect.
- -f
Run in the foreground. The process does not return to the command line prompt until it is stopped. Useful for debugging and where foreground processing is required.
- -p
TCP/IP port number your FCGI program will listen on (and accept connections), if you are using TCP/IP. You typically need to specify ProxyPass, "location" or similar FCGI directives in your proxy web server so it can connect to your application. You can either use TCP/IP or Unix domain sockets (-x option). Typically, you would use Unix domain sockets if proxy web server runs on the same computer as your application server. If you specify neither -x nor -p, -x (unix domain socket) is assumed to be used. See SELinux if you are using it, as additional steps may be required.
- -x
Use Unix domain socket to connect from proxy web server to your application server. This socket is automatically created by vf. You typically need to specify ProxyPass, "location" or similar FCGI directives in your proxy web server so it can connect to your application. If you specify neither -x nor -p (TCP/IP socket), then -x (unix domain socket) is assumed to be used.
- -l
The size of socket listening backlog for incoming connections. It must be a number between 10 and SOMAXCONN-1, inclusive. The default is 400. Increase it if your system is very busy to improve performance.
- -d
Dynamically change number of FCGI processes ("worker" processes) to match the request load (adaptive mode). Used with "max-worker" and "min-worker" options. You cannot use -w with this option. The number of processes needed is determined based on any pending connections that are not answered by any running FCGI processes. If no such connections are detected (i.e. existing FCGI processes are capable of handling any incoming requests), the number of processes does not grow and will decrease to the point of minimum necessary number of workers. In that case, given release time (-t option), the number of processes will slowly decline until the incoming requests warrant otherwise. The number of running FCGI processes thus will fluctuate based on the actual load, these options, as well as --min-worker and --max-worker options. If neither -d nor -w is specified, -d is the default.
- --min-worker=<min workers>
Minimum number of FCGI processes that run in adaptive mode (-d option). The default is 0. This option can be used only with -d option.
- --max-worker=<max workers>
Maximum number of FCGI processes that run in adaptive mode (-d option). The default is 20. This option can be used only with -d option.
- -t <release time>
Timeout before the number of FCGI processes is reduced to meet the reduced load. The default is 30 seconds, and it can be a value between 5 seconds and 86400 seconds (i.e. a day).
- -w
Number of parallel FCGI processes ("worker" processes) that will be started. These processes do not exit; they serve incoming requests in parallel, one request per process. The number of processes should be guided by the concurrent user demand of your application. If neither -d nor -w is specified, -d is the default.
- -m
Send a command to vf daemon serving an application. Command can be "start" (to start FCGI processes), "stop" (to stop FCGI processes), "restart" (to restart FCGI processes), "quit" (to stop vf daemon altogether) or "status" (to display status of vf).
- -n
Do not restart FCGI processes if they exit or die. However, in adaptive mode (-d option), this option has no effect.
- -g
Do not restart FCGI processes when their executable changes. By default, they will be automatically restarted, which is useful when in development, or upgrading the server executable.
- -a
Specify any command-line arguments for your application (typically retrieved by argc/argv arguments to main()). The arguments should be double-quoted as a whole, while use of single quotes to quote arguments that contain whitespaces is permitted.
- -s
The basis time period (in milliseconds) that vf will sleep before checking for commands (specified by -m option), or check for dead FCGI processes that need restarting. It can be between 100 and 5000 milliseconds. Smaller value will mean higher responsiveness but also higher CPU usage. The default value usually suffices nicely, and should not be changed without due consideration.
- -e
Display verbose messages.
- -c <program>
Full absolute path to your FCGI program. If omitted, the executable /var/lib/vv/bld/<app name>/<app name>.fcgi is assumed, which is the standard Vely FCGI executable. If present, but without any slashes in it to indicate path (including current directory as ./), then this executable is assumed to be /var/lib/vv/bld/<app name>/<program>.
- -v
Display vf version (which matches Vely version) as well as copyright and license.
- -h,--help
Display help.
vf writes log file at /var/lib/vv/<app name>/vflog/log file. This file is overwritten when vf starts, so it contains the log of the current daemon instance only.
Exit code
When starting, vf exits with 0 if successful and 1 if it is already running. If FastCGI executable cannot run, the exit code is -1. When creating application, vf exits with 0 if successful, and -1 if not.
Process control
When vf is told to stop the application (with "-m stop" arguments), it will send SIGTERM signal to all its children. All children processes must complete the current request before exiting, assuming they are currently processing a request; otherwise they must exit immediately. Vely will always do so; if you are using non-Vely FastCGI application, you should make sure this requirement is met to avoid interrupted requests.
If vf is terminated without "-m stop", (for example with SIGKILL signal), then all its chidlren will immediately terminate with SIGKILL as well, regardless of whether they are currently processing any requests or not.
Platforms and requirements
vf is a part of Vely package. See
vely.
Examples
- To begin using vf for a specific application, you must initialize it first. For example, if your application name is "myapp", the user who will run application is "myuser" and the primary group of your proxy server (Apache, Nginx, or some other) is "www-data" (optional):
sudo vf -i -u myuser -r www-data -- myapp
- The initialization needs to be done only once. Following the above, you can start your FCGI application:
vf -c "/path/to/your/fcgi/program" -x -w 3 -a "--opt1 val1 --opt2 val2" -- myapp
In this example, you would use Unix domain sockets (-x option), start 3 process to server requests in parallel (-w option), and your program accepts input parameters (-a option). If you used -p option instead of -x, you could specify TCP/IP port (in which case proxy web server can be on a different host).
- To stop your FCGI processes:
- To restart them:
- To stop the server entirely (meaning to stop the resident vf daemon serving your particular application):
- To view status of vf daemon for your application:
Running your application server on startup
If you want your application to run on startup (so you don't have to run it manually), you can add it to systemd configuration. Here is an example (replace <app name> with your application name and <app owner> with the name of the Operating System user under which your application is installed):
[Unit]
Description=Vely FastCGI Program Manager for [<app name>] application.
After=network.target
[Service]
Type=forking
ExecStart=/usr/bin/vf <app name>
ExecStop=/usr/bin/vf -m quit <app name>
KillMode=process
Restart=on-failure
User=<app owner>
[Install]
WantedBy=multi-user.target
The above should be saved in the directory given by the output of the following system command:
pkg-config systemd --variable=systemdsystemunitdir
The file should be saved as <app name>.service (or similar). Once saved, you can use standard systemctl commands to start, stop and restart your service.
See also
General (
deploying_application how_vely_works quality_control rename_files SELinux vely vely_architecture vely_removal vf vv why_C_and_Vely )
SEE ALL (
documentation)
Vv
Purpose: Builds Vely applications.
vv is a
vely tool for application building.
Command-line options
- -q Build Vely application from source code in the current directory. vf must run first in this directory with "-i" option to create the application. The following options can be used when building:
- --db="[#]<database vendor>:<db config file> ..."
Specify a list of databases used in your application. Each element of the list is <database vendor> (which is 'mariadb', 'postgres' or 'sqlite'), followed by a colon (:) and then <db config file>, where <db config file> is used to refer to a database in statement_APIs such as run-query.
Each <database vendor>:<db config file> is separated by a space. You can list any number of databases for use in your application. A file with name <db config file> must exist and contain the connection parameters for database access, and is copied once to Vely's database configuration directory (see how_vely_works).
If an optional "#" precedes <database vendor>, then database configuration file is overwritten even if already present. See database_config_file for more details on the content of this file, the defaults used, and changing them to fit your needs.
- --lflag <linker flags>
If you wish to add any additional linker flags (such as any non-Vely libraries), specify them quoted under this option.
- --cflag <C flags>
If you wish to add any additional C compiler (gcc) flags, specify them quoted under this option.
- --trace
If specified, tracing information code will be generated (without it, tracing is not available and trace-run statement is ignored). Tracing only works when debugging mode is enabled, so --debug option must be used as well.
- --path <application path>
This option lets you specify the application path for your request_URLs. It is a leading path of a URL prior to request name and any parameters. If empty, the default is the application name preceded by a forward slash:
- --maxupload <max upload size>
Specify maximum upload size for a file (in bytes). The default is approximately 25MB.
- --debug
Generate debugging information when compiling your application. Debugging information is required to produce a backtrace file with the stack that contains source code line numbers, in order to pinpoint the exact location where report-error statement was used, or where the application crashed. It is also needed to use gdb for debugging purposes.
- --c-lines
Skip generating line information when compiling .vely files. By default line information is included, which allows errors to be reported with line numbers in .vely files. If you want only generated C code line numbers to be used, use this option.
- -i
Display both include and linking flags for an application that uses Client_API to connect to Vely FastCGI server. The flags are for C compiler (gcc). If --include is used in addition, then only include flags are displayed. If --link is used in addition, then only linking flags are displayed. Use this to automate building of client applications with tools like Makefile.
- -v
Display Vely version as well as the Operating System version.
- -s
Trace the execution of vv utility and display all the steps in making your application.
- -e <num of errors>
Show the last <num of errors> from the backtrace file, which receives error message and stack trace when program crashes or report-error is issued. Also display the path to backtrace file which contains the stack details.
- -t <num of trace files>
Show the last <num of trace files> most recent trace files for the application. This is useful when tracing (see trace-run) to quickly find the trace files where Vely writes to. Also display the path to backtrace file which contains the stack details.
- -o
Show documentation directory - web page documentation is located here, and examples are located under "examples" subdirectory.
- -l
Show library directory - Vely's libraries and v1 code processor are located there.
- -a
Display CHANGELOG (i.e. release notes) for the current release.
- -r [ --req="/<request name><url payload>" ] [ --method="<request method>" ]
Display bash code to run a command-line program. You must be in the source code directory. You can use "--req" option to specify the request name and URL payload (see request_URL), for example it may be:
vv -r --req="/encrypt/data/somedata?method=aes256"
where "/encrypt" gives the request name, and "/data/somedata?method=aes256" represents the URL payload. Or it could be for instance:
vv -r --req="/encrypt?data=somedata&method=aes256"
where "/encrypt" gives the request name, and "?data=somedata&method=aes256" represents the URL payload.
Use --method to specify the HTTP request method, for instance:
vv -r --req="/encrypt/data/somedata?method=aes256" --method=POST
If not specified, the default method is "GET". Either way, the output may look like:
export REQUEST_METHOD=POST
export SCRIPT_NAME="/enc"
export PATH_INFO="/encrypt/data/somedata"
export QUERY_STRING="method=aes256"
/var/lib/vv/bld/enc/enc
Running the above executes the command line program which handles the request specified.
- -u
Read stdin (standard input) and substitute any environment variables in the form of ${<var name>} with their values, and output to stdout (stdout). This is useful in processing configuration files that do not have parameter values hardcoded, but rather take them from the environment.
- -m
Add Vely syntax and keyword highlighting rules for files with .vely extension to Vim editor for the currently logged on user.
- -c,--clean
Clean all object and other intermediate files, so that consequent project build is a full recompilation. Use it prior to rebuilding the application.
- -h
Display help.
Examples
- Make application (-q), use three databases (--db) named mdb (MariaDB database), pdb (PostgreSQL) and sdb (SQLite), produce debugging information (--debug), produce tracing information (--trace), specify that your application is using crypto and curl libraries:
vv -q --db="mariadb:mdb postgres:pdb sqlite:sdb" --debug --trace
- make application, use MariaDB database db (--db), specify linker and C compilation flags, specify maximum upload size of about 18M:
vv -q --db="mariadb:db" --lflag "-Wl,-z,defs" --cflag "-DXYZ123" --maxupload 18000000
- Make application that doesn't use any databases:
See also
General (
deploying_application how_vely_works quality_control rename_files SELinux vely vely_architecture vely_removal vf vv why_C_and_Vely )
SEE ALL (
documentation)
Why C and why Vely?
C
Writing programs in C generally results in fastests and smallest programs, which is the reason why it's widely used in system programming and infrastructure software. C is also the
greenest programming language, which means if all programs were written in C, there would be considerably less emissions, water use and pollution, not to mention we would all enjoy faster software that requires less hardware and consequently less use of resources.
On the other hand, the reason why you wouldn't have picked up C in the past may be memory management and low-level constructs. That changes with Vely.
Vely
Vely is a framework for C that lets you rapidly build server-side applications of maximum possible performance with code that's ergonomic, easy to write and read, and considerably safer than C alone, because you do not need to allocate/free memory, or worry about buffer overruns or memory violations. You also get automatic memory allocation/garbage collector and automatic file closing.
Vely
statements are embedded in C code and designed to be declarative, i.e. a single line of code performs a task that is meaningful to a human, and without having to know details of how exactly to do it. Such tasks include building strings, querying databases, outputting data, file manipulation, memory structures like hash and fifo, program execution, encryption, encoding, JSON parsing, web stuff like cookies, input parameters, uploading and downloading files, request handling, daemonizing etc. - in short, lots of high-level stuff you need every day and don't want to spend time reinventing the wheel.
Vely generates C underneath for several reasons. It is the shortest route to maximum performance and the smallest memory footprint. C is simple. It also allows usage of virtually any library in existence. And Vely statements are carefully crafted with the goal of generating fast and safe C code on their own. Vely itself is written in C.
Vely's goal is not to write more C code, quite the opposite. By using Vely anywhere possible, C code can be used as a supporting mechanism for Vely statements, such as declaring variables, conditional statements, program flow and usage of external libraries. This means all the important and difficult parts are done by single-line Vely statements. The purpose of C is then just a "programming glue" that binds together your application and business logic.
Simple and practical
Vely statements look nothing like C, or for that matter like many other popular languages. They are simple and you write them inside C code so there is no need to learn anything new about the layer underneath; C is quite simple and well-known.
Most of Vely statements generate a number of C statements. Still, their scope is typically narrow and the generated code is shallow and direct, similar to what an experienced C programmer would write, incurring virtually no loss of performance. Vely's design omits the classic API look and feel on purpose and focuses on simplicity. The arguments are specified in any order by naming their purpose.
Vely is about practical, actual needs people have; it's about productivity, safety and performance. The idea is to not sacrifice performance at all, and to improve productivity and safety significantly.
What it is, and what it isn't
Vely is not a language. It is a better way to API. As a founding layer, C is a great compromise: rapid and easy development on top of a simple programming base with improved safety and arguably the best performance. This is especially true in the Cloud, where smaller and faster means less CPU seconds, less RAM, less money spent, less energy used and less emissions. And
Moore's law may or may not be failing, but it will take significantly more funding, time and expenditure of all kinds to keep it going, and at some point it may no longer.
In short, the goal of Vely is to lend the superior performance of C to general-purpose application development, and especially web applications. Regardless of what kind of hardware you run, or whatever kind of software you're designing, ultimately, performance matters.
What does Vely look like
Here's an example that lists employees, writes output to file "employees", and then displays the result in a web page or on the command line - all this in less than 30 lines of code:
void list_employees() {
out-header default
@<html><body>
char *header = make_header();
p-out header
write-string define outmsg
run-query ="select name, salary from employees order by name" output name, salary
@Name: <<query-result name>>
@<br/>
@Salary: <<query-result salary>>
@<br/><br/>
end-query
end-write-string
write-file "employees" from outmsg status define st
if (st<0) {
@Error in writing file (<<pf-out "%lld", st>>)
exit-request
} else {
p-out outmsg
}
@</body></html>
}
Highlighted with Vely's
syntax_highlighting using
2html.
See also
General (
deploying_application how_vely_works quality_control rename_files SELinux vely vely_architecture vely_removal vf vv why_C_and_Vely )
SEE ALL (
documentation)
write-fifo
Purpose: Write key/value pair into a FIFO list.
write-fifo <list> key <key> value <value>
write-fifo adds a pair of key/value pointers to the FIFO <list>, specified with string <key> and <value> (in "key" and "value" clauses, collectively called an "element").
It always adds elements to the end of the list. <value> is a pointer to any type, allowing storage of any kind of data.
Memory pointed by <key> and <value> must not go out of scope or be freed while FIFO is used - if necessary, store a copy (see
copy-string for strings). This is because write-fifo does not make copies of <key> and <value>, rather only the pointers to those are stored.
Examples
new-fifo define nf
write-fifo nf key "mykey" value "myvalue"
See also
FIFO (
new-fifo purge-fifo read-fifo rewind-fifo write-fifo )
SEE ALL (
documentation)
write-file
Purpose: Write to a file.
write-file <file> | ( file-id <file id> ) \
from <content> \
[ length <length> ] \
[ ( position <position> ) | ( append [ <append> ] ) ] \
[ status [ define ] <status> ]
Without file-id
This is a simple method of writing a file. File named <file> is opened, data written, and file is closed.
write-file writes <content> to <file>, which is either a full path of the file or a path relative to the application home directory (see
how_vely_works).
If "append" clause is used without optional boolean expression <append>, or if <append> evaluates to true, the <content> is appended to the file; otherwise the file is overwritten with <content>, unless "position" clause is used in which case file is not overwritten and <content> is written at byte <position>. Note that <position> can be beyond the end of file, and null-bytes are written between the current end of file and <position>.
File is created if it does not exist (even if "append" is used), unless "position" clause is used in which case file must exist.
If "length" is not used, then a string is written to a file, and the number of bytes written is the length of that string. If "length" is specified, then exactly <length> bytes is written and <content> can hold a binary value or a string.
If "status" clause is used, then the number of bytes written is stored to <status>, unless error occurred, in which case <status> has the error code. The error code can be VV_ERR_POSITION (if <position> is negative or file does not support it), VV_ERR_WRITE (if there is an error writing file) or VV_ERR_OPEN if file is not open. Note that no partial data will be written; if all of data cannot be written to the file, then none will be written, and in that case an error of VV_ERR_WRITE will be reported in <status>.
With file-id
This method uses a <file id> that was created with
open-file. You can then write (and read) file using this <file id> and the file stays open until
close-file is called.
If "position" clause is used, then data is written starting from byte <position>, otherwise writing starts from the current file position determined by the previous reads/writes or as set by using "set" clause in
file-position. After each read or write, the file position is advanced by the number of bytes read or written. Position can be set passed the last byte of the file, in which case writing will fill the space between the current end of file and the current position with null-bytes.
If "length" is not used, then a string is written to a file, and the number of bytes written is the length of that string. If "length" is specified, then exactly <length> bytes is written and <content> can hold a binary value or a string.
If "append" clause is used without optional boolean expression <append>, or if <append> evaluates to true, then file pointer is set at the end of file and data written.
If "status" clause is used, then the number of bytes written is stored to <status>, unless error occurred, in which case <status> has the error code. The error code can be VV_ERR_POSITION (if <position> is negative or file does not support it), VV_ERR_WRITE (if there is an error writing file) or VV_ERR_OPEN if file is not open. Note that no partial data will be written; if all of data cannot be written to the file, then none will be written, and in that case an error of VV_ERR_WRITE will be reported in <status>.
Examples
To overwrite file "/path/to/file" with "Hello World":
write-file "/path/to/file" from "Hello World"
To append "Hello World" to file:
char *path="/path/to/file";
char *cont="Hello World";
write-file path from cont append
To write only 5 bytes (i.e. "Hello") and get status (if successful, number variable "st" would be "5"):
char *cont="Hello World";
write-file "file" from cont length 5 status define st
To write a string "Hello" at byte position 3 in the existing "file":
char *cont="Hello";
write-file "file" from cont position 3 status define st
See
open-file for an example with "file-id" clause.
See also
Files (
close-file copy-file delete-file file-position file_storage file_uploading lock-file open-file read-file read-line rename-file stat-file temporary_file uniq-file unlock-file write-file )
SEE ALL (
documentation)
write-hash
Purpose: Store key/value pair into a hash table.
write-hash <hash> \
key <key> \
value <value> \
[ status [ define ] <status> ] \
[ old-value [ define ] <old value> ]
[ old-key [ define ] <old key> ]
write-hash will store pointers <key> (in "key" clause) and <value> (specified in "value" clause) into hash table <hash>, which must be created with
new-hash.
<key> is a string and <value> can be a pointer of any type, allowing storage of any kind of data; collectively they are called an "element". Memory pointed by <key> and <value> must not go out of scope or be freed while the hash is used - if necessary, store a copy (see
copy-string for strings); this is because write-hash does not make copies of <key> and <value>, rather only the pointers to those are stored in the hash.
If <key> already exists in the hash table, then the pointer to old value associated with it is returned in the optional <old value> (in "old-value" clause) and the pointer to old key is returned in the optional <old key> (in "old-key" clause) - in this case the optional number <status> (in "status" clause) has a value of VV_ERR_EXIST.
If <key> did not exist, <status> will be VV_OKAY and <old value>/<old key> are NULL. Both <status> and <old value>/<old key> can be created with "define" clause.
Note that while <key> and <old key> will contain matching strings when <status> is VV_ERR_EXIST, the <old key> will contain a pointer to a key used in the previous write-string statement.
Examples
Writing data to hash:
new-hash define h size 1000
write-hash h key "mykey" value "some data"
Writing new value with the same key and obtaining the previous value (which is "some data"):
write-hash h key "mykey" value "new data" status define st old-value define od
if (st == VV_ERR_EXIST) {
@Previous value for this key is <<p-out od>>
}
See
read-hash for more examples.
See also
Hash table (
get-hash new-hash purge-hash read-hash resize-hash write-hash )
SEE ALL (
documentation)
write-string
Purpose: Create complex strings.
write-string [ define ] <string>
<any code>
end-write-string [ bytes-written [ define ] <bytes written> ] [ notrim ]
Output of any Vely code (that normally would go to a client) can be written into <string>, which can be created (if it doesn't exist) with "define". In between write-string and end-write-string you can write <any Vely code>. For instance you can use database queries, conditional statements, call C code etc., just as you would for any other Vely code. write-strings can be nested, meaning you can use write-string to write to a different string while within write-string, and presumably use that string to output it within the parent.
<string> is
allocated memory.
Length of result
To get the length of the string written, use the optional "bytes-written" clause, in which case <bytes written> will have the number of bytes written, minus the trailing zero byte (i.e. it is the length of the string output). If <bytes written> is not defined, you can create it with "define" within the statement.
Shortcut code
Note that instead of write-string you can also use a shortcut "((" (and instead of end-write-string you can use "))" ), for example here a string "fname" holds a full path of a file named "config-install.vely" under the application home directory (see
how_vely_works), and "bw" holds the number of bytes written:
get-app directory to define home_dir
(( fname
@<<p-out home_dir>>/config-install.vely
)) bytes-written define bw
Trimming
Just like with all other Vely code, every line is trimmed both on left and write, so this:
(( define mystr
@Some string
))
is the same as:
(( define mystr
@Some string <whitespaces>
))
write-string (or "((") statement must always be on a line by itself (and so does end-write-string, or "))" statement). The string being built starts with the line following write-string, and ends with the line immediately prior to end-write-string.
All trailing empty lines are removed, for example:
(( define mystr
@My string
@
@
))
the above string would have two trailing empty lines, however they will be removed. If you want to skip trimming the trailing whitespaces, use "notrim" clause in end-write-string.
Referencing result within write-string
The <string> pointer (i.e. the result) is set to an empty string at the beginning of write-string; only after end-write-string completes does it take the computed value. That's because using <string> within write-string is generally inefficient as it's value is copied to the result. If you must use the result recursively (which is not recommended), you can save the pointer to it and use it.
Examples
- Simple
A simple example:
char *my_str="world";
char *my_str1="and have a nice day too!";
write-string define result_str
@Hello <<p-out my_str>> (<<p-out my_str1>>)
end-write-string
p-out result_str
The output is
Hello world (and have a nice day too!)
- Using code inside
Here is using Vely code inside write-string, including database query and conditional statements to produce different strings at run-time:
input-param selector
char *my_str="world";
write-string define result_str
if-string selector=="simple"
@Hello <<p-out my_string>> (and have a nice day too!)
else-if-string selector=="database"
run-query @db="select name from employee"
@Hello <<query-result name>>
@<br/>
end-query
else
@No message
end-if
end-write-string
p-out result_str
If selector variable is "simple", as in URL
https://mysite.com/<app name>/some_service?selector=simple
the result is
Hello world (and have a nice day too!)
If selector variable is "database", as in URL
https://mysite.com/<app name>/some_service?selector=database
the result may be (assuming "Linda" and "John" are the two employees selected):
Hello Linda
<br/>
Hello John
<br/>
If selector variable is anything else, as in URL
https://mysite.com/<app name>/some_service?selector=something_else
the result is
In the above example, "result_str" variable is defined on the spot, but it can also be defined elsewhere without using "define".
- Using function calls inside
The following uses functions inside write-string (note that "<<.func2();>>" is simply calling C code as
inline_code):
void func1 ()
{
char *result_str;
write-string result_str
@<<p-out "Result from func2()">> is <<.func2();>>
end-write-string
p-out result_str
}
void func2()
{
p-out "Hello from func2"
}
The output from func1() is
Result from func2() is Hello from func2
- Nesting
An example to nest write-strings:
write-string define str1
@Hi!
write-string define str2
@Hi Again!
end-write-string
p-out str2
end-write-string
p-out str1
The result is
- Returning result from function
The result of write-string can be returned from a function (because it is heap memory), as in this example:
void func1 ()
{
write-string define result_str
char *func2_result;
@<<p-out "Result from func2()">> is <<p-out func2(&func2_result)>>
end-write-string
p-out result_str
}
char *func2(char **result)
{
write-string *result
@<hr/>
run-query @db="select firstName from employee"
@Hello <<query-result firstName>>
@<br/>
end-query
@<hr/>
end-write-string
return *result;
}
The output from func1() is:
Result from func2() is <hr/>
Hello Linda
<br/>
Hello John
<br/>
<hr/>
See also
Strings (
copy-string count-substring lower-string split-string trim-string upper-string write-string )
SEE ALL (
documentation)