Vely combines excessive efficiency and the low footprint of C with the benefit of use and improved security of languages like PHP. It’s free and open supply software program, licensed underneath GPLv3 and LGPL 3 for libraries, so you possibly can even construct business software program with it.
Using Vely for SaaS
You can use Vely to create a multitenant internet software which you can run on the Internet as Software-as-a-Service (SaaS). Each person has a totally separate knowledge house from every other.
In this instance internet software, a person can join a pocket book service to create notes after which view and delete them. It demonstrates a number of know-how integrations in simply 310 strains of code throughout seven supply recordsdata. The applied sciences embrace:
- MariaDB
- Web browser
- Apache
- Unix sockets
How it really works
Here’s how the applying works from a person’s perspective. A code walk-through follows the photographs.
The app permits a person to create a brand new login by specifying an electronic mail tackle and password. You can fashion these any approach you want, resembling with CSS:
Verify the person’s electronic mail:
Each person logs in with their distinctive username and password:
Once logged in, a person can add a word:
A person can get a listing of notes:
The app asks for affirmation earlier than deleting a word:
After the person confirms, the word is deleted:
Setup stipulations
Follow the set up directions on Vely.dev. It’s a fast course of that makes use of customary packaging instruments, resembling DNF, APT, Pacman, or Zypper.
Because they’re a part of this instance, you will need to set up Apache as an online server and MariaDB as a database.
After putting in Vely, activate syntax highlighting in Vim in case you’re utilizing it:
vv -m
Get the supply code
The supply code for this demonstration SaaS app is a part of the Vely set up. It’s a good suggestion to create a separate supply code listing for every software (and you may identify it no matter you want). In this case, unpacking the supply code does that for you:
$ tar xvf $(vv -o)/examples/multitenant_SaaS.tar.gz
$ cd multitenant_SaaS
By default, the applying is known as multitenant_SaaS
, however you possibly can name it something (in case you do this, change it in all places).
Set up the applying
The very first step is to create an software. It’s easy to do with Vely’s vf
utility:
$ sudo vf -i -u $(whoami) multitenant_SaaS
This command creates a brand new software dwelling (/var/lib/vv/multitenant_SaaS
) and performs the applying setup for you. Mostly, which means creating varied subdirectories within the dwelling folder and assigning privileges. In this case, solely the present person (the results of whoami
) owns the directories, with 0700 privileges, which ensures that nobody else has entry to the recordsdata.
Set up the database
Before doing any coding, you want a spot to retailer the data utilized by the applying. First, create a MariaDB database referred to as db_multitenant_SaaS
, owned by the person vely
with password your_password
. You can change any of those values, however bear in mind to alter them in all places throughout this instance.
Logged in as root within the 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;
Then create database objects (tables and information and so forth) within the database:
USE db_multitenant_SaaS;
SOURCE setup.sql;
exit
Connect Vely to a database
To let Vely know the place your database is and methods to log into it, create a database config file named db_multitenant_SaaS
. (This is the identify utilized by the database statements within the supply code, so in case you change it, be sure you change it in all places.)
Vely makes use of native MariaDB database connectivity, so you possibly can specify any choices {that a} given database permits you to:
$ echo '[client]
person=vely
password=your_password
database=db_multitenant_SaaS
protocol=TCP
host=127.0.0.1
port=3306' > db_multitenant_SaaS
Build the applying
Use the vv
utility to make the applying, utilizing the --db
choice to specify the MariaDB database and the database config file:
$ vv -q --db=mariadb:db_multitenant_SaaS
Start the applying server
To begin the applying server to your internet software, use the vf
FastCGI course of supervisor. The software server makes use of a Unix socket to speak with the online server (making a reverse proxy):
$ vf -w 3 multitenant_SaaS
This begins three daemon processes to serve the incoming requests. You may begin an adaptive server that will increase the variety of processes to serve extra requests and steadily scale back the variety of processes once they’re not wanted:
$ vf multitenant_SaaS
See vf
for extra choices that will help you obtain one of the best efficiency.
When it is advisable cease your software server, use the -m give up
choice:
$ vf -m give up multitenant_SaaS
Set up the online server
This is an online software, so the applying wants an online server. This instance makes use of Apache by the use of a Unix socket listener.
1. Set up Apache
To configure Apache as a reverse proxy and join your software to it, it is advisable allow FastCGI proxy help, which usually means utilizing the proxy
and proxy_fcgi
modules.
For Fedora methods (or others, like Arch) allow the proxy
and proxy_fcgi
modules by including (or uncommenting) the suitable LoadModule directives within the /and so on/httpd/conf/httpd.conf
Apache configuration file.
For Debian, Ubuntu, and related methods, allow the proxy
and proxy_fcgi
modules:
$ sudo a2enmod proxy
$ sudo a2enmod proxy_fcgi
For OpenSUSE, add these strains to the tip of /and so on/apache2/httpd.conf
:
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
2. Configure Apache
Now you will need to add the proxy info to the Apache configuration file:
ProxyPass "/multitenant_SaaS" unix:///var/lib/vv/multitenant_SaaS/sock/sock|fcgi://localhost/multitenant_SaaS
The location of your configuration might range, relying in your Linux distribution:
- Fedora, CentOS, Mageia, and Arch:
/and so on/httpd/conf/httpd.conf
- Debian, Ubuntu, Mint:
/and so on/apache2/apache2.conf
- OpenSUSE:
/and so on/apache2/httpd.conf
3. Restart
Finally, restart Apache. On Fedora and related methods, in addition to Arch Linux:
$ sudo systemctl restart httpd
On Debian and Debian-based methods, in addition to OpenSUSE:
$ sudo systemctl restart apache2
Set up native mail
This instance makes use of electronic mail as part of its operate. If your server can already ship electronic mail, you possibly can skip this. Otherwise, you need to use native mail (myuser@localhost
) simply to check it out. To do this, set up Sendmail.
On Fedora and related:
$ sudo dnf set up sendmail
$ sudo systemctl begin sendmail
On Debian methods (like Ubuntu):
$ sudo apt set up sendmail
$ sudo systemctl begin sendmail
When the applying sends an electronic mail to a neighborhood person, resembling OS_user@localhost
, then you possibly can confirm that the e-mail was despatched by taking a look at /var/mail/
(the “mail spool”).
Access the applying server from the browser
Assuming you are operating the applying domestically, use http://127.0.0.1/multitenant_SaaS?req=notes&action=begin
to entry your software server out of your internet browser. If you are operating this on a dwell server on the Internet, you could want to regulate your firewall settings to permit HTTP visitors.
Source code
This instance software incorporates seven supply recordsdata. You can evaluate the code your self (bear in mind, it is simply 310 strains throughout these recordsdata), however here is an summary of every one.
SQL setup (setup.sql)
The two tables created are:
- customers: Information about every person. Each person within the customers desk has its personal distinctive ID (userId column) together with different info resembling electronic mail tackle and whether or not it is verified. There’s additionally a hashed password. An precise password is rarely saved in plain textual content (or in any other case); a one-way hash is used to verify the password.
- notes: Notes entered by the person. The notes desk incorporates the notes, every together with userId column that states which person owns them. The userId column’s worth matches the namesake column from customers desk. This approach, each word clearly belongs to a single person.
The file contents:
CREATE TABLE IF NOT EXISTS notes (dateOf datetime, noteId BIGINT AUTO_INCREMENT PRIMARY KEY, userId BIGINT, word VARCHAR(1000));
CREATE TABLE IF NOT EXISTS customers (userId BIGINT AUTO_INCREMENT PRIMARY KEY, electronic mail VARCHAR(100), hashed_pwd VARCHAR(100), verified SMALLINT, verify_token VARCHAR(30), SESSION VARCHAR(100));
CREATE UNIQUE INDEX IF NOT EXISTS users1 ON customers (electronic mail);
Run-time knowledge (login.h)
To correctly show the Login, Sign Up, and Logout hyperlinks, you want some flags which are out there anyplace within the software. Also, the applying makes use of cookies to take care of a session, so this must be out there anyplace, for instance, to confirm that the session is legitimate. Every request despatched to the applying is confirmed that approach. Only requests that include verifiable cookies are permitted.
So to that impact, you could have a global_request_data kind reqdata
(request knowledge) and in it there’s sess_userId
(ID of person) and sess_id
(person’s present session ID). You even have reasonably self-explanatory flags that assist render pages:
#ifndef _VV_LOGIN
#outline _VV_LOGINtypedef struct s_reqdata {
bool displayed_logout; // true if Logout hyperlink displayed
bool is_logged_in; // true if session verified logged-in
char *sess_userId; // person ID of present session
char *sess_id; // session ID
} reqdata;void login_or_signup ();
#endif
Session checking and session knowledge (_before.vely)
Vely has a notion of a before_request_handler. The code you write executes earlier than every other code that handles a request. To do that, all you want is to write down this code in a file named _before.vely
, and the remainder is routinely dealt with.
Anything {that a} SaaS software does, resembling dealing with requests despatched to an software, should be validated for safety. This approach, the applying is aware of whether or not the caller has the permissions wanted to carry out an motion.
Checking for permission is finished right here in a before-request handler. That approach, no matter different code you could have dealing with a request, you have already got the session info.
To preserve session knowledge (like session ID and person ID) out there anyplace in your code, you utilize global_request_data. It’s only a generic pointer (void*) to reminiscence that any code that handles requests can entry. This is ideal for dealing with periods, as proven beneath:
#embrace "vely.h"
#embrace "login.h"// _before() is a before-request-handler. It at all times executes earlier than
// every other code that handles a request. It's a superb place for any
// type of request-wide setting or knowledge initialization
void _before() {
// Output HTTP header
out-header default
reqdata *rd; // that is international request knowledge, see login.h
// allocate reminiscence for international request knowledge, might be routinely deallocated
// on the finish of request
new-mem rd measurement sizeof(reqdata)
// initialize flags
rd->displayed_logout = false;
rd->is_logged_in = false;
// set the information we created to be international request knowledge, accessible
// from any code that handles a request
set-req knowledge rd
// verify if session exists (based mostly on cookies from the shopper)
// this executes earlier than every other request-handling code, making it
// simpler to only have session info prepared
_check_session ();
}
Checking if the session is legitimate (_check_session.vely)
One of a very powerful duties in a multitenant SaaS software is to verify (as quickly as potential) if the session is legitimate by checking whether or not a person is logged in. It’s executed by getting the session ID and person ID cookies from the shopper (resembling an online browser) and checking these in opposition to the database the place periods are saved:
#embrace "vely.h"
#embrace "login.h"// Check if session is legitimate
void _check_session () {
// Get international request knowledge
reqdata *rd;
get-req knowledge to rd
// Get cookies from person browser
get-cookie rd->sess_userId="sess_userId"
get-cookie rd->sess_id="sess_id"
if (rd->sess_id[0] != 0) {
// Check if session ID is appropriate for given person ID
char *electronic mail;
run-query @db_multitenant_SaaS = "select email from users where userId='%s' and session='%s'" output electronic mail : rd->sess_userId, rd->sess_id row-count outline rcount
query-result electronic mail to electronic mail
end-query
if (rcount == 1) {
// if appropriate, set logged-in flag
rd->is_logged_in = true;
// if Logout hyperlink not show, then show it
if (rd->displayed_logout == false) {
@Hi <<p-out electronic mail>>! <a href="https://opensource.com/?req=login&action=logout">Logout</a><br/>
rd->displayed_logout = true;
}
} else rd->is_logged_in = false;
}
}
Signing up, Logging in, Logging out (login.vely)
The foundation of any multitenant system is the power for a person to enroll, log in, and log off. Typically, signing up includes verifying the e-mail tackle; most of the time, the identical electronic mail tackle is used as a username. That’s the case right here.
There are a number of subrequests applied right here which are essential to carry out the performance:
- When Signing Up a brand new person, show the HTML type to gather the data. The URL request signature for that is
req=login&motion=newuser
. - As a response to the Sign Up type, create a brand new person. The URL request signature is
req=login&motion=createuser
. The input-param sign obtains an electronic mail and pwd POST type fields. The password worth is a one-way hash, and an electronic mail verification token is created as a random five-digit quantity. These are inserted into the customers desk, creating a brand new person. A verification electronic mail is distributed, and the person is prompted to learn the e-mail and enter the code. - Verify the e-mail by coming into the verification code despatched to that electronic mail. The URL request signature is
req=login&motion=confirm
. - Display a Login type for the person to log in. The URL request signature is
req=login
(for example,motion
is empty.) - Log in by verifying the e-mail tackle (username) and password. The URL request signature is
req=login&motion=login
. - Logout on the person’s request. The URL request signature is
req=login&motion=logout
. - Landing web page for the applying. The URL request signature is
req=login&motion=start
. - If the person is presently logged in, go to the applying’s touchdown web page.
See examples of those beneath:
#embrace "vely.h"
#embrace "login.h"// Handle session upkeep, login, logout, session verification
// for any multitenant Cloud software
void login () {
// Get URL enter parameter "action"
input-param motion// Get international request knowledge, we document session info in it, so it is helpful
reqdata *rd;
get-req knowledge to rd// If session is already established, the one motive why we cannot proceed to
// software dwelling is that if we're logging out
if (rd->is_logged_in) {
if (strcmp(motion, "logout")) {
_show_home();
exit-request
}
}// Application display to get began. Show hyperlinks to login or signup and present
// dwelling display applicable for this
if (!strcmp (motion, "begin")) {
_show_home();
exit-request// Start creating new person. Ask for electronic mail and password, then proceed to create person
// when this type is submitted.
} else if (!strcmp (motion, "newuser")) {
@Create New User<hr/>
@<type motion="https://opensource.com/?req=login" methodology="POST">
@<enter identify="action" kind="hidden" worth="createuser">
@<enter identify="email" kind="text" worth="" measurement="50" maxlength="50" required autofocus placeholder="Email">
@<enter identify="pwd" kind="password" worth="" measurement="50" maxlength="50" required placeholder="Password">
@<enter kind="submit" worth="Sign Up">
@</type>// Verify code despatched to electronic mail by person. The code should match, thus verifying electronic mail tackle
} else if (!strcmp (motion, "verify")) {
input-param code
input-param electronic mail
// Get confirm token based mostly on electronic mail
run-query @db_multitenant_SaaS = "select verify_token from users where email="%s"" output db_verify : electronic mail
query-result db_verify to outline db_verify
// Compare token recorded in database with what person offered
if (!strcmp (code, db_verify)) {
@Your electronic mail has been verifed. Please <a href="https://opensource.com/?req=login">Login</a>.
// If matches, replace person information to point it is verified
run-query @db_multitenant_SaaS no-loop = "update users set verified=1 where email="%s"" : electronic mail
exit-request
}
end-query
@Could not confirm the code. Please attempt <a href="https://opensource.com/?req=login">again</a>.
exit-request// Create person - this runs when person submits type with electronic mail and password to create a person
} else if (!strcmp (motion, "createuser")) {
input-param electronic mail
input-param pwd
// create hashed (one-way) password
hash-string pwd to outline hashed_pwd
// generate random 5 digit string for confirm code
random-string to outline confirm size 5 quantity
// create person: insert electronic mail, hashed password, verification token. Current confirm standing is 0, or not verified
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', '')" : electronic mail, hashed_pwd, confirm affected-rows outline arows error outline err on-error-continue
if (strcmp (err, "0") || arows != 1) {
// if can't add person, it most likely would not exist. Either approach, we won't proceed.
login_or_signup();
@User with this electronic mail already exists.
rollback-transaction @db_multitenant_SaaS
} else {
// Create electronic mail with verification code and electronic mail it to person
write-string outline msg
@From: vely@vely.dev
@To: <<p-out electronic mail>>
@Subject: confirm your account
@
@Your verification code is: <<p-out confirm>>
end-write-string
exec-program "/usr/sbin/sendmail" args "-i", "-t" enter msg standing outline st
if (st != 0) {
@Could not ship electronic mail to <<p-out electronic mail>>, code is <<p-out confirm>>
rollback-transaction @db_multitenant_SaaS
exit-request
}
commit-transaction @db_multitenant_SaaS
// Inform the person to go verify electronic mail and enter verification code
@Please verify your electronic mail and enter verification code right here:
@<type motion="https://opensource.com/?req=login" methodology="POST">
@<enter identify="action" kind="hidden" worth="verify" measurement="50" maxlength="50">
@<enter identify="email" kind="hidden" worth="<<p-out email>>">
@<enter identify="code" kind="text" worth="" measurement="50" maxlength="50" required autofocus placeholder="Verification code">
@<button kind="submit">Verify</button>
@</type>
}// This runs when logged-in person logs out.
} else if (!strcmp (motion, "logout")) {
// Update person desk to wipe out session, that means no such person is logged in
if (rd->is_logged_in) {
run-query @db_multitenant_SaaS = "update users set session='' where userId='%s'" : rd->sess_userId no-loop affected-rows outline arows
if (arows == 1) {
rd->is_logged_in = false; // point out person not logged in
@You have been logged out.<hr/>
}
}
_show_home();// Login: this runs when person enters person identify and password
} else if (!strcmp (motion, "login")) {
input-param pwd
input-param electronic mail
// create one-way hash with the intention of evaluating with person desk - password is NEVER recorded
hash-string pwd to outline hashed_pwd
// create random 30-long string for session ID
random-string to rd->sess_id size 30
// Check if person identify and hashed password match
run-query @db_multitenant_SaaS = "select userId from users where email="%s" and hashed_pwd='%s'" output sess_userId : electronic mail, hashed_pwd
query-result sess_userId to rd->sess_userId
// If match, replace person desk with session ID
run-query @db_multitenant_SaaS no-loop = "update users set session='%s' where userId='%s'" : rd->sess_id, rd->sess_userId affected-rows outline arows
if (arows != 1) {
@Could not create a session. Please attempt once more. <<.login_or_signup();>> <hr/>
exit-request
}
// Set person ID and session ID as cookies. User's browser will return these to us with each request
set-cookie "sess_userId" = rd->sess_userId
set-cookie "sess_id" = rd->sess_id
// Display dwelling, be sure session is appropriate first and set flags
_check_session();
_show_home();
exit-request
end-query
@Email or password are usually not appropriate. <<.login_or_signup();>><hr/>// Login display, asks person to enter person identify and password
} else if (!strcmp (motion, "")) {
login_or_signup();
@Please Login:<hr/>
@<type motion="https://opensource.com/?req=login" methodology="POST">
@<enter identify="action" kind="hidden" worth="login" measurement="50" maxlength="50">
@<enter identify="email" kind="text" worth="" measurement="50" maxlength="50" required autofocus placeholder="Email">
@<enter identify="pwd" kind="password" worth="" measurement="50" maxlength="50" required placeholder="Password">
@<button kind="submit">Go</button>
@</type>
}
}// Display Login or Sign Up hyperlinks
void login_or_signup() {
@<a href="https://opensource.com/?req=login">Login</a> & & <a href="https://opensource.com/?req=login&action=newuser">Sign Up</a><hr/>
}
General-purpose software (_show_home.vely)
With this tutorial, you possibly can create any multitenant SaaS software you need. The multitenant-processing module above (login.vely
) calls the _show_home() operate, which may home any code of yours. This instance code exhibits the Notes software, however it may very well be something. The _show_home() operate calls any code you would like and is a general-purpose multitenant software plug-in:
#embrace "vely.h"void _show_home() {
notes();
exit-request
}
Notes software (notes.vely)
The software is ready to add, record, and delete any given word:
#embrace "vely.h"
#embrace "login.h"// Notes software in a multitenant Cloud
void notes () {
// get international request knowledge
reqdata *rd;
get-req knowledge to rd
// If session invalid, show Login or Signup
if (!rd->is_logged_in) {
login_or_signup();
}
// Greet the person
@<h1>Welcome to Notes!</h1><hr/>
// If not logged in, exit - this ensures safety verification of person's id
if (!rd->is_logged_in) {
exit-request
}
// Get URL parameter that tells Notes what to do
input-param subreq
// Display actions that Notes can do (add or record notes)
@<a href="https://opensource.com/?req=notes&subreq=add">Add Note</a> <a href="https://opensource.com/?req=notes&subreq=list">List Notes</a><hr/>// List all notes for this person
if (!strcmp (subreq, "list")) {
// choose notes for this person ONLY
run-query @db_multitenant_SaaS = "select dateOf, note, noteId from notes where userId='%s' order by dateOf desc" : rd->sess_userId output dateOf, word, noteId
query-result dateOf to outline dateOf
query-result word to outline word
query-result noteId to outline noteId
// change new strains to <br/> with quick cached Regex
match-regex "n" in word replace-with "<br/>n" end result outline with_breaks standing outline st cache
if (st == 0) with_breaks = word; // nothing was discovered/changed, simply use unique
// Display a word
@Date: <<p-out dateOf>> (<a href="https://opensource.com/?req=notes&subreq=delete_note_ask¬e_id=%3C%3Cp-out%20noteId%3E%3E">delete word</a>)<br/>
@Note: <<p-out with_breaks>><br/>
@<hr/>
end-query
}// Ask to delete a word
else if (!strcmp (subreq, "delete_note_ask")) {
input-param note_id
@Are you positive you wish to delete a word? Use Back button to return, or <a href="https://opensource.com/?req=notes&subreq=delete_note¬e_id=%3C%3Cp-out%20note_id%3E%3E">delete word now</a>.
}// Delete a word
else if (!strcmp (subreq, "delete_note")) {
input-param note_id
// Delete word
run-query @db_multitenant_SaaS = "delete from notes where noteId='%s' and userId='%s'" : note_id, rd->sess_userId affected-rows outline arows no-loop error outline errnote
// Inform person of standing
if (arows == 1) {
@Note deleted
} else {
@Could not delete word (<<p-out errnote>>)
}
}// Add a word
else if (!strcmp (subreq, "add_note")) {
// Get URL POST knowledge from word type
input-param word
// Insert word underneath this person's ID
run-query @db_multitenant_SaaS = "insert into notes (dateOf, userId, note) values (now(), '%s', '%s')" : rd->sess_userId, word affected-rows outline arows no-loop error outline errnote
// Inform person of standing
if (arows == 1) {
@Note added
} else {
@Could not add word (<<p-out errnote>>)
}
}// Display an HTML type to gather a word, and ship it again right here (with subreq="add_note" URL param)
else if (!strcmp (subreq, "add")) {
@Add New Note
@<type motion="https://opensource.com/?req=notes" methodology="POST">
@<enter identify="subreq" kind="hidden" worth="add_note">
@<textarea identify="note" rows="5" cols="50" required autofocus placeholder="Enter Note"></textarea>
@<button kind="submit">Create</button>
@</type>
}
}
SaaS with C efficiency
Vely makes it potential to leverage the facility of C in your internet purposes. A multitenant SaaS software is a major instance of a use case that advantages from that. Take a take a look at the code examples, write some code, and provides Vely a attempt.