CETAN Documentation
This documentation covers the CETAN Web Application Server, logging, TLS, authentication, IP filtering, Safe secrets, Safe utilities, development environment setup, and building C++ REST web services using the CETAN REST API.
Contents
1. Quick start
This guide walks you through installing, configuring, and starting the CETAN Server on Linux. All examples use Ubuntu 24.04.2, but the steps apply to most modern Linux distributions.
1.1 Prerequisites
Before you begin, ensure your system includes:
- Linux server with GLIBC++ version 4.3 or later.
- A
g++ stdc++23compiler for building C++ web applications. - Permissions to install and run services.
1.2 Download the server
Download the CETAN Server package: Cetan Web Application Server.
1.3 Unpack the software
tar -xvf cetan-server_1.0.tar.gz
This extracts the server into a directory named CETAN_SERVER.
1.4 Edit the configuration file
The main configuration file is located at:
CETAN_SERVER/config/cetan_config.xml.
A full reference file (cetan_config_full_sample.xml) is also available in the same directory.
Update the <host> directive:
<host>example.com</host>
Replace example.com with your server’s hostname.
1.5 Set permissions and ownership
To improve security and simplify access control, configure group‑based permissions for the server directory.
Create the cetan group:
sudo addgroup cetan
Assign ownership to root:cetan:
sudo chown -R root:cetan CETAN_SERVER
Apply secure permissions:
sudo chmod -R 2670 CETAN_SERVER
Meaning of 2670:
- 2 — SetGID: new files inherit the
cetangroup - 6 — Owner (root): read + write
- 6 — Group (cetan): read + write + execute
- 0 — Others: no access
Add users to the cetan group:
sudo usermod -aG cetan username
Replace username with the actual Linux user.
1.6 Start the CETAN server
First‑time startup:
bin/cetan start
- Confirm first‑time startup (
yorY) - Create a server password
- A default Safe store is created at
security/cetan.safe
Subsequent startups:
bin/cetan start
Enter the server password created during the first startup.
1.7 Verify the server
Open your server URL in a browser. The CETAN welcome page should appear, confirming the server is running.
1.8 About Safe
Safe is CETAN’s secure storage system for sensitive data, including the server password. A detailed Safe reference document is available in the next section.
1.9 Troubleshooting
Missing GLIBCXX version:
/lib64/libstdc++.so.6: version 'GLIBCXX_X.Y.Z' not found
Install or upgrade GLIBC++ to the required version and retry.
Other errors:
- Continue to the logging configuration section
- Check
logs/cetan.log - Email error details to
contact@cetan.io
1.10 Running the server in the background
To move a running foreground job to the background:
Ctrl+Z
bg
disown
exit
Congratulations — your CETAN Server is now installed, configured, and running.
2. Logging (SLog)
CETAN uses SLog as its primary logging utility. This section explains how to configure logging for the CETAN Web Application Server. For full details about SLog’s API and advanced features, refer to the official SLog documentation.
Configuration overview
- Define loggers, log levels, and appenders.
- Configure log file paths and rotation policies.
- Set the default log level for CETAN components.
2.1 Logging configuration files
The main logging configuration file is located at:
CETAN_SERVER/config/slog_config.xml
A complete reference configuration is also provided:
CETAN_SERVER/config/slog_config_full_sample.xml
CETAN reads this file at startup. Any changes require restarting the server.
2.2 CETAN’s built‑in logger
CETAN includes a private, reserved logger named CETAN. This logger is used
internally by the server and must not be redefined in your SLog configuration.
The default logger entry appears as:
<target name="cetan_file">
<type>file</type>
<file_name>cetan.log</file_name>
<max_number_file>10</max_number_file>
</target>
<logger name="CETAN">
<target>cetan_file</target>
<accept_level>INFO</accept_level>
</logger>
This logger accepts messages at INFO level or lower and outputs them to the console.
Because it is a private logger, do not create another logger with the same name.
2.3 Log levels
SLog supports the following log levels, from highest to lowest severity:
- OUT — always write to target.
- FATAL — fatal messages.
- ERROR — error messages.
- WARN — warnings and lower levels.
- AUDIT — request and client information.
- INFO — informational messages.
- TRACE — trace‑level diagnostic messages.
- DEBUG — full debug output (use only for troubleshooting).
For normal operation, set accept_level to ERROR or
WARN to reduce overhead.
Use AUDIT for basic request logging.
Use DEBUG only when diagnosing issues, as it significantly impacts performance.
2.4 Writing logs to a file
To write log output to a file, configure a file target and logger as follow:
<target name="restapi_file">
<type>file</type>
<file_name>restapi.log</file_name>
<max_number_file>10</max_number_file>
</target>
<logger name="RestAPI">
<target>restapi_file</target>
<accept_level>INFO</accept_level>
</logger>
Additional options such as file_size_max may be used to control rotation size.
2.5 Applying changes
After modifying slog_config.xml, restart the CETAN server for changes to take effect.
SLog is a flexible logging system. Refer to your SLog configuration file for full element definitions and advanced options.
3. SSL/TLS configuration
CETAN can serve HTTPS traffic using TLS certificates configured in cetan_config.xml.
You can use unencrypted or encrypted private keys, and store passphrases in Safe.
3.1 Certificate and private key requirements
- Certificates must be in PEM format.
- Both RSA and ECC certificates are supported.
-
The certificate bundle must include:
- Server certificate
- Intermediate CA certificates
- Root CA certificate
- Private keys may be encrypted (recommended) or unencrypted. Encrypted keys require a passphrase stored in Safe.
For production environments, always obtain certificates from a trusted Certificate Authority. Self‑signed certificates should be used only for testing.
3.2 Generating a self‑signed certificate (testing only)
From CETAN_SERVER, run:
bin/cetan gen-cert
Follow the prompts to enter certificate subject information. The command generates two files:
cert.pem— server certificate bundlekey.pem— private key (encrypted if you chose a passphrase)
Record the private key passphrase — it will be needed when configuring Safe.
3.3 Placing certificate and key files
CETAN only searches for certificate and key files inside:
CETAN_SERVER/security
Place your cert.pem (or CA‑issued bundle) and key.pem in this directory.
3.4 Enabling HTTPS in cetan_config.xml
Set the protocol to HTTPS:
<protocol>https</protocol>
Specify the certificate bundle:
<ssl_cert_bundle_pem>cert.pem</ssl_cert_bundle_pem>
Only specify the file name — do not include the security/ directory path.
3.5 Configuring the private key
CETAN supports both encrypted and unencrypted private keys. Choose the method that matches your key type.
3.5.1 Method 1 — Encrypted private key (recommended)
Specify the encrypted key file:
<ssl_enc_key_pem>key.pem</ssl_enc_key_pem>
Reference the Safe entry containing the passphrase:
<safe_ssl_key_passphase>cetan_ssl_key_passphase</safe_ssl_key_passphase>
Create the Safe entry:
bin/safe add -n security/cetan.safe -e cetan_ssl_key_passphase
Enter:
- The Safe password (same as server password)
- The private key passphrase
- Confirmation of the passphrase
You should see: Entry added
3.5.2 Method 2 — Unencrypted private key
Specify the Safe entry containing the raw private key:
<safe_ssl_cert_key>cetan_ssl_key</safe_ssl_cert_key>
Create the Safe entry:
bin/safe add -n security/cetan.safe -e cetan_ssl_key -infile security/key.pem
Enter the Safe password when prompted. You should see Entry added confirming the key was stored.
List Safe entries:
bin/safe list -n security/cetan.safe
3.6 Restarting the server
After updating cetan_config.xml, restart the CETAN server for TLS changes to take
effect.
3.7 Verifying HTTPS
Once restarted, access your server using https://. If the certificate and key are
configured correctly, CETAN will serve traffic securely over TLS.
4. Authentication
CETAN supports multiple authentication mechanisms for securing web resources. Authentication consists of three components:
- An optional Identity Provider (IDP)
- An authentication scheme
- A protect_resources declaration
These components work together to validate user credentials, API keys, or JWT tokens before granting access to protected URLs.
4.1 How authentication is structured
The example below shows all three declarations working together:
<cetan_server>
...
<idps>
<idp name="cetan_basic_idp">
<type>BasicFile</type>
<lockout_time>100</lockout_time>
<safe_idp_key>idp_key</safe_idp_key>
<display_name>basic_idp</display_name>
<max_failed_attempt>3</max_failed_attempt>
<file_name>cetan_basic_account.txt</file_name>
</idp>
</idps>
<auths>
<authn name="cetan_basic_file_authn">
<type>Basic</type>
<idp>cetan_basic_idp</idp>
</authn>
</auths>
<protect_resources>
<uris>/</uris>
<authn>cetan_basic_file_authn</authn>
</protect_resources>
...
</cetan_server>
protect_resources references an authn entry, which in turn
references an IDP. Removing protect_resources disables web
authentication entirely.
Multiple IDPs and authentication schemes may be declared, but only one IDP and one authn entry are used per protected resource.
4.2 Identity Providers (IDPs)
CETAN supports the following IDP types:
- LDAP
- BasicFile
- KeyFile
4.2.1 LDAP IDP
<idp name="cetan_ldap">
<type>LDAP</type>
<lockout_time>100</lockout_time>
<url>ldaps://ldap.cetan.io</url>
<bind_attribute>uid</bind_attribute>
<max_failed_attempt>3</max_failed_attempt>
<base_dn>ou=People,dc=cetan,dc=io</base_dn>
<admin_dn>cn=admin,dc=cetan,dc=io</admin_dn>
<safe_admin_key>ldap_admin_key</safe_admin_key>
</idp>
- name — IDP name (required)
- type — must be
LDAP - url — LDAP server URL
- bind_attribute + base_dn form the Bind DN
- admin_dn — required if anonymous bind is not allowed
- safe_admin_key — Safe entry containing the admin password
Create the Safe entry:
bin/safe add -n security/cetan.safe -e ldap_admin_key
4.2.2 BasicFile IDP
BasicFile IDP stores user accounts in a text file using the format:
uid;password_hmac;salt
<idp name="cetan_basic_idp">
<type>BasicFile</type>
<salt_size>24</salt_size>
<lockout_time>100</lockout_time>
<display_name>basic_idp</display_name>
<hash_algorithm>SHA3-256</hash_algorithm>
<max_failed_attempt>3</max_failed_attempt>
<file_name>cetan_basic_account.txt</file_name>
<safe_idp_key>basic_idp_key</safe_idp_key>
</idp>
- file_name — user account file
- hash_algorithm — default is SHA3‑256
- safe_idp_key — Safe entry containing the IDP key
Create the IDP key:
bin/safe gen-password -length 32
bin/safe add -n security/cetan.safe -e basic_idp_key
4.2.3 Adding users to BasicFile IDP
Using CETAN CLI:
bin/cetan add-user -idp-name cetan_basic_idp -uid user_uid
Manual method:
- Generate salt:
bin/safe gen-salt -length 24 - Compute HMAC:
bin/safe compute-hmac - Retrieve IDP key:
bin/safe get -n security/cetan.safe -e basic_idp_key - Add record to file:
uid;base64_hmac;base64_salt
4.2.4 KeyFile IDP
<idp name="cetan-api-key">
<type>KeyFile</type>
<key_size>64</key_size>
<display_name>APIKey</display_name>
<file_name>cetan_api_key.txt</file_name>
<safe_idp_key>idp_api_key</safe_idp_key>
</idp>
Create the IDP key:
bin/safe gen-password -length 64
bin/safe add -n security/cetan.safe -e idp_api_key
Add API keys:
bin/cetan add-api-key
4.3 Authentication schemes
CETAN supports two authentication types:
- Basic — username/password
- Bearer — API keys or JWT tokens
4.3.1 Basic Authentication
<authn name="cetan_basic_file_authn">
<type>Basic</type>
<idp>cetan_basic_idp</idp>
</authn>
4.3.2 Bearer Authentication
Bearer tokens may be API keys or JWT tokens.
API Key example:
<authn name="cetan_key_bearer">
<type>Bearer</type>
<token>apikey</token>
<idp>cetan-api-key</idp>
</authn>
JWT (HMAC) example:
<authn name="cetan_jwt_HS256_bearer">
<type>Bearer</type>
<token>jwt</token>
<verify>HMAC</verify>
<safe_key>HS256_secret</safe_key>
</authn>
JWT (RSA/EC/PS/EdDSA) examples:
<authn name="cetan_jwt_RS256_bearer">
<type>Bearer</type>
<token>jwt</token>
<verify>signature</verify>
<cert_file>jwt_rs256_public_key.pem</cert_file>
</authn>
4.4 Protecting resources
Use protect_resources to secure specific URL paths:
<protect_resources>
<uris>/</uris>
<authn>cetan_basic_ldap_authn</authn>
</protect_resources>
- uris — comma‑separated list of protected paths
- authn — authentication scheme name
4.5 Save and restart
After updating cetan_config.xml, restart the CETAN server for authentication changes
to take effect.
5. IPFilter
The CETAN Web Application Server includes a built‑in IP filtering system that allows you to
control access based on client IP addresses or ranges. IPFilter operates in either blacklist or
whitelist mode and is configured using a dedicated file referenced from
cetan_config.xml.
5.1 Enabling IPFilter
To enable IP filtering, add the <ip_filter> directive to your server
configuration:
<ip_filter>blacklist.conf</ip_filter>
You may choose any filename. The file must be placed in the
CETAN_SERVER/security directory.
5.2 Configuration file structure
The IPFilter configuration file defines the filter mode and the list of IP addresses or ranges to allow or block.
Example configuration file:
mode = 1; # blacklist mode (required)
1.2.3.4 = 1; # drop a single IP
4.5.6.7 - 4.5.6.254 = 1; # drop a range of IPs
If mode is not defined, CETAN defaults to blacklist mode.
5.3 Modes
-
mode = 1 — Blacklist
Clients matching listed IPs or ranges are blocked. All others are allowed. -
mode = 2 — Whitelist
Only listed IPs or ranges are allowed. All others are blocked.
5.4 IP entries and action codes
Each entry in the configuration file maps an IP address or range to an action code:
1.2.3.4 = 1;
- 1 = drop — deny access for the specified IP or range
- 2 = allow — permit access for the specified IP or range
Supported formats:
- Single IP address:
203.0.113.5 = 1; - IP range:
10.0.0.1 - 10.0.0.254 = 1;
5.5 Example configurations
5.5.1 Blacklist mode
mode = 1;
203.0.113.5 = 1;
198.51.100.0 - 198.51.100.255 = 1;
5.5.2 Whitelist mode
mode = 2;
192.168.1.10 = 2;
10.0.0.0 - 10.0.0.255 = 2;
5.6 Restart required
After modifying the IPFilter configuration file or updating the
<ip_filter> directive, restart the CETAN server for changes to take effect.
IPFilter is evaluated early in the request pipeline. Clients blocked by IPFilter cannot reach authentication or application handlers.
6. Safe (secrets)
Safe is a robust C++‑based system for securely managing sensitive data—commonly referred to as secrets. It provides both a command‑line interface (CLI) for direct user interaction and a comprehensive C++ API for programmatic integration. Safe enables secure storage, retrieval, and lifecycle management of secrets within applications and systems, eliminating hardcoded credentials and insecure storage practices.
6.1 Core concepts
- Secret — a sensitive value such as a password, API key, token, or private key.
- Safe file — an encrypted
.safevault storing secrets at rest. - Entry — a name/value pair stored inside a Safe file.
6.2 Typical usage
- Create a Safe file for your environment.
- Add entries for TLS passphrases, LDAP admin passwords, API keys, and other secrets.
- Reference Safe entries from
cetan_config.xmlto avoid storing secrets in plain text. - Use the Safe API or CLI to retrieve or manage secrets securely.
6.3 Safe C++ API (SDK)
The Safe C++ API provides programmatic access to encrypted Safe files. Applications can create
safes, add entries, retrieve values, and manage passwords without exposing secrets in plain text.
All functionality is provided through the ctn::Safe class.
6.3.1 Class overview
namespace ctn {
class Safe {
public:
Safe();
Safe(const string& name, const string& key);
int create_safe(string& error);
bool key_verified(string& error);
int get_entry(Entry& e, string& error);
int add_entry(const Entry& e, string& error);
int remove_entry(const string& entry_name, string& error);
int list_entries(vector<string>& names, string& error);
int add_entry_from_file(const string& name,
const string& file,
string& error);
int change_key(const string& name,
const string& current_key,
const string& new_key,
string& error);
};
}
All methods returning int follow a standard convention:
0 = success, -1 = error.
Error details are returned through the error reference parameter.
6.3.2 Constructors
Default constructor
Safe safe;
Creates an empty Safe instance.
This form is used exclusively for change_key() operations.
Constructor with file + key
Safe safe("cetan", "password");
- name — Safe filename (adds
.safeif missing). - key — password protecting the Safe file.
6.3.3 Core API methods
Create Safe
int create_safe(string& error);
Add entry
int add_entry(const Entry& e, string& error);
Add entry from file
int add_entry_from_file(const string& name,
const string& file,
string& error);
List entries
int list_entries(vector<string>& names, string& error);
Get entry
int get_entry(Entry& e, string& error);
Remove entry
int remove_entry(const string& entry_name, string& error);
Verify key
bool key_verified(string& error);
Change Safe password
int change_key(const string& name,
const string& current_key,
const string& new_key,
string& error);
6.3.4 Examples
Create a Safe
#include "Safe.h"
#include <string>
#include <iostream>
using namespace ctn;
using namespace std;
int main() {
string error;
Safe safe("cetan", "password");
if (safe.create_safe(error) == -1)
cout << "Could not create safe: " << error << "\n";
else
cout << "A new safe created\n";
}
Add entry
#include "Safe.h"
#include "Entry.h"
Entry e("entry_name", "entry value");
safe.add_entry(e, error);
List entries
vector<string> names;
safe.list_entries(names, error);
Retrieve entry
Entry e("entry_name");
safe.get_entry(e, error);
cout << e.value();
Remove entry
safe.remove_entry("entry_name", error);
Change Safe password
Safe safe;
safe.change_key("cetan.safe", "oldpass", "newpass", error);
6.4 Safe Command Line Interface (CLI)
6.4.1 Introduction
Safe Utilities is a standalone command‑line tool for secure management of secrets. It supports both interactive and non‑interactive modes, making it suitable for scripting, automation, and secure deployment workflows.
6.4.2 Installation
- Download
safe_utilities.zip. - Unzip the archive to any directory.
- Run the
safeexecutable from that location.
6.4.3 Core concepts
- Secrets — API keys, passwords, private keys, and other sensitive values.
- Vault — the encrypted
.safefile storing secrets at rest.
6.4.4 General usage
./safe COMMAND [OPTIONS] <ARG>...
6.4.5 Commands
Create Safe
./safe create --name safe-name --pass safe-password
Add entry
./safe add --name safe-name --pass safe-password --entry entry-name --value entry-value
Add entry from file
./safe add --name safe-name --pass safe-password --entry entry-name --infile filename
List entries
./safe list --name safe-name --pass safe-password
Get entry
./safe get --name safe-name --pass safe-password --entry entry-name
./safe get ... --out hex|HEX|base64
./safe get ... --outfile filename
Delete entry
./safe delete --name safe-name --pass safe-password --entry entry-name
Change Safe password
./safe change-password --name safe-name --pass safe-password --newpass new-password
Verify Safe password
./safe verify-password --name safe-name --pass safe-password
Generate salt
./safe gen-salt --length salt-length --out hex|HEX|base64
Generate password
./safe gen-password --length password-length
Compute HMAC
./safe compute-hmac --msg message --b64-salt salt --hash-algo algo --key password --out outform
./safe get --name safe-name --pass safe-password --entry entry-name
./safe get ... --out hex|HEX|base64
./safe get ... --outfile filename./safe delete --name safe-name --pass safe-password --entry entry-name./safe change-password --name safe-name --pass safe-password --newpass new-password./safe verify-password --name safe-name --pass safe-password./safe gen-salt --length salt-length --out hex|HEX|base64./safe gen-password --length password-length./safe compute-hmac --msg message --b64-salt salt --hash-algo algo --key password --out outform
Supported hash algorithms include SHA-256, SHA-384,
SHA-512, SHA3-256, SHA3-384, and
SHA3-512.
7. SLog Logging Framework
SLog is a lightweight, high‑performance logging framework for modern C++ applications. It provides configurable log targets, structured formatting, log‑level filtering, and efficient variadic logging APIs. The framework is designed to be simple to integrate, fast under load, and flexible enough for both small utilities and large server systems.
7.1 Introduction
SLog offers a clean C++ interface for writing log messages at different severity levels.
Developers bind an SLog instance to a named logger defined in
slog_config.xml, then write log entries using typed logging methods such as
info(), warn(), or debug().
The framework includes timestamp utilities, log‑level filtering, file rotation,
and a minimal API designed for clarity and performance.
7.2 SLog API
7.2.1 Constants
static constexpr const char* DEFAULT_LOGGER = "CONSOLE";
static constexpr const char* HTTP_TIME_FORMAT = "%a, %d %b %Y %T %Z";
static constexpr const char* SLOG_TIME_FORMAT = "%m-%d-%Y %H:%M:%S %Z";
- DEFAULT_LOGGER — used when no logger name is provided.
- HTTP_TIME_FORMAT — RFC‑style timestamp for HTTP headers.
- SLOG_TIME_FORMAT — default timestamp format for log entries.
7.2.2 Log Levels
enum Level { OUT, FATAL, ERROR, WARN, AUDIT, INFO, TRACE, DEBUG };
- OUT — always written, bypasses filtering.
- FATAL — unrecoverable errors.
- ERROR — operational failures.
- WARN — warnings and degraded behavior.
- AUDIT — request metadata and client information.
- INFO — normal operational messages.
- TRACE — fine‑grained execution flow.
- DEBUG — full diagnostic detail.
7.2.3 Constructors and Object Semantics
SLog();
SLog(const string& logger_name, long key = 0);
SLog(SLog&& orig);
SLog& operator=(SLog&& orig);
SLog(const SLog& orig) = delete;
SLog& operator=(const SLog& orig) = delete;
SLog is move‑only to prevent accidental duplication of logger state.
The optional key parameter allows creation of private loggers
that are accessible only to components that know the key.
7.2.4 Time Utilities
// Default formats
static string time_gmt();
static string time_local();
// HTTP-style formats
static string http_gmt_time();
static string http_time_local();
// Explicit default formats
static string time_gmt_format();
static string time_local_format();
// Custom formats
static string time_gmt_format(const string& format);
static string time_local_format(const string& format);
These functions generate timestamps in GMT or local time using either default or custom formats.
7.2.5 Log-Level Checking
bool logable(Level L) const;
This function returns true if the logger accepts the specified level.
It is useful for avoiding expensive string construction when a message would be filtered out.
7.2.6 Generic Logging Function
template<typename... Params>
void log(Level L, const Params&... params) const;
All parameters are concatenated into a single unformatted message.
7.2.7 Typed Logging Methods
template<typename... Params> void out(const string& tag, const Params&... params) const;
template<typename... Params> void fatal(const string& tag, const Params&... params) const;
template<typename... Params> void error(const string& tag, const Params&... params) const;
template<typename... Params> void warn(const string& tag, const Params&... params) const;
template<typename... Params> void audit(const string& tag, const Params&... params) const;
template<typename... Params> void info(const string& tag, const Params&... params) const;
template<typename... Params> void trace(const string& tag, const Params&... params) const;
template<typename... Params> void debug(const string& tag, const Params&... params) const;
Each method prepends a timestamp, level, and tag, then writes the formatted entry to the configured target.
Example log format:
02-11-2025 04:27:58.263312 GMT D HTTP 333952 receive header for request: 20
7.2.8 Example
#include "SLog.h"
SLog slog("RestAPI");
slog.info("startup", "Service initialized");
slog.warn("validation", "Missing field: ", field_name);
slog.error("db", "Database connection failed: ", err_msg);
if (slog.logable(SLog::DEBUG)) {
slog.debug("payload", "Request body: ", json_body);
}
7.3 SLog Usage
This section explains how to configure SLog, define loggers and targets, and write log messages in C++ applications.
7.3.1 Configuration File
SLog loads its configuration from config/slog_config.xml
or from the application's working directory.
<slog_config>
<log_dir>logs</log_dir>
<time_zone>local</time_zone>
<locale>C</locale>
<file_size_max>50M</file_size_max>
<target name="app_file">
<type>file</type>
<file_name>application.log</file_name>
<max_number_file>10</max_number_file>
</target>
<logger name="AppLogger">
<target>app_file</target>
<accept_level>INFO</accept_level>
</logger>
</slog_config>
7.3.2 Global Settings
- log_dir — directory for log files.
- time_zone —
GMTorLOCAL. - locale — affects date/time formatting.
- file_size_max — maximum size before rotation.
7.3.3 Targets
Targets define where log messages are written. SLog supports console and file targets.
<target name="restapi_file">
<type>file</type>
<file_name>restapi.log</file_name>
<accept_level>INFO</accept_level>
<max_number_file>10</max_number_file>
</target>
7.3.4 Loggers
A logger binds a name to a target and defines the minimum log level it accepts.
<logger name="RestAPI">
<target>restapi_file</target>
<accept_level>INFO</accept_level>
</logger>
7.3.5 Writing Logs in C++
#include "SLog.h"
SLog log("RestAPI");
log.out("startup", "Service initialized");
log.info("startup", "Listening on port ", port);
log.warn("validation", "Missing field: ", field_name);
log.error("db", "Database connection failed: ", err_msg);
log.debug("trace", "Processing request ID=", id);
7.3.6 Checking Log Level
if (log.logable(SLog::DEBUG)) {
log.debug("payload", "Request body: ", expensive_to_string(obj));
}
7.3.7 Time Formatting Utilities
string now = SLog::time_local();
string http_time = SLog::http_gmt_time();
string custom = SLog::time_local_format("%Y-%m-%d %H:%M:%S");
7.3.8 Recommended Practices
- Use AUDIT for request metadata.
- Use INFO for normal operations.
- Use DEBUG sparingly in production.
- Use ERROR and FATAL for critical failures.
- Keep logger names short and descriptive.
- Rotate logs regularly in production environments.
8. Develop Web Applications Using the CETAN REST API
The CETAN REST API enables developers to build high‑performance C++ web applications that run natively on the CETAN Web Application Server. It provides an object‑oriented framework for defining services, routing HTTP requests, generating responses, and integrating with CETAN’s logging, security, and configuration systems.
This section explains how to set up a complete development environment, then walks through
building a fully functional REST application named rest-api-101 that exposes a
simple “Hello, World!” endpoint.
8.1 Development Environment Setup
CETAN REST development typically uses a Linux server as the build host and a Windows workstation
for editing source code. The example environment below uses Ubuntu 24.04.2 LTS with
g++ (C++23) and Apache NetBeans on Windows with remote development.
8.1.1 Requirements
- Linux server with GLIBC++ ≥ 3.4 and a C++23‑capable
g++compiler. - Windows workstation for development.
- Apache NetBeans with the C/C++ plugin installed.
- CETAN REST SDK installed on the Linux server.
- Samba or NFS for shared project files.
8.1.2 Directory Layout
A typical project structure:
project/
include/
src/
build/
CMakeLists.txt
cetan-rest-sdk/
7.1.3 Configure Samba Share on Linux
- Create a shared directory such as
/opt/dev. - Set appropriate ownership and permissions.
- Add a
[dev]share to/etc/samba/smb.conf. - Restart Samba and map the share as a drive (e.g.,
V:) on Windows.
8.1.4 Install and Configure Apache NetBeans
- Extract a JDK (e.g.,
D:\dev\jdk-25.0.1). - Extract Apache NetBeans (e.g.,
D:\dev\netbeans). - Set
netbeans_jdkhomeinnetbeans.conf. - Add the NetBeans distribution update center.
- Install the C/C++ plugin.
If NetBeans reports an unpack200 error, copy the unpack200 binary
from an older JDK into the new JDK’s bin directory.
8.1.5 Configure Remote Build Host
- Add your Linux server as a C/C++ build host via SSH.
- Use Samba/NFS for shared project files.
- Map Windows
V:to Linux/opt/dev.
8.1.6 Validate the Environment
- Create a new C/C++ project on
V:\. - Select the Linux build host and GNU toolchain.
- Build and run a simple “Hello, world!” program.
8.1.7 Linking Against CETAN REST
Include the CETAN REST headers:
#include "Rest.h"
#include "WebService.h"
#include "Resource.h"
Ensure your runtime search path includes:
$ORIGIN/../lib
8.2 Develop the rest-api-101 Web Application
This tutorial walks through creating a minimal REST application that exposes a single endpoint:
/greeting/hello-world. The application returns a simple HTML “Hello, World!” page.
8.2.1 Create the Greeting WebService
// Greeting.h
#ifndef GREETING_H
#define GREETING_H
#include "Response.h"
#include "WebService.h"
#include <vector>
using namespace std;
using namespace ctn;
class Greeting : public WebService {
public:
Greeting();
Response hello_world();
virtual ~Greeting();
private:
vector<Resource*> register_resources() const override final;
};
#endif /* GREETING_H */
// Greeting.cpp
#include "Greeting.h"
Greeting::Greeting() : WebService("/greeting") {}
Response Greeting::hello_world() {
Response resp(200);
resp.body("<html><head><title>Hello World from CETAN REST</title></head>"
"<body><center><font size=\\"25\\">Hello, World!</font>"
"</center></body></html>");
return resp;
}
vector<Resource*> Greeting::register_resources() const {
vector<Resource*> resources;
resources.emplace_back(new Resource("GET", "/hello-world", &Greeting::hello_world));
return resources;
}
Greeting::~Greeting() {}
The full path for this endpoint becomes:
/greeting/hello-world under the application’s root URL.
8.2.2 Create the RestAPI101 Application Class
// RestAPI101.h
#ifndef RESTAPI101_H
#define RESTAPI101_H
#include "REST.h"
#include "WebService.h"
#include <set>
using namespace std;
using namespace ctn;
class RestAPI101 : public REST {
public:
RestAPI101();
virtual ~RestAPI101();
private:
REST* create_cetan_app() const override final;
set<WebService*> register_services() const override final;
};
#endif /* RESTAPI101_H */
// RestAPI101.cpp
#include "REST.h"
#include "Greeting.h"
#include "RestAPI101.h"
RestAPI101::RestAPI101() {}
REST* RestAPI101::create_cetan_app() const {
return new RestAPI101();
}
set<WebService*> RestAPI101::register_services() const {
set<WebService*> services;
services.emplace(new Greeting());
return services;
}
RestAPI101::~RestAPI101() {}
8.2.3 Build Configuration
- Compile with
-std=c++23. - Output file:
cetan-rest-101.ctn. - Add
libto library directories. - Set runtime search path to
$ORIGIN/../lib. - Link against
libctn-rest.so.
8.2.4 Deploy to CETAN
- Copy
cetan-rest-101.ctninto the server’slibdirectory. - Add an application entry to
cetan_config.xml:
<applications>
<app name="cetan-rest-101">
<root_url>/rest-101</root_url>
<app_file>cetan-rest-101.ctn</app_file>
</app>
</applications>
After restarting the CETAN Web Application Server, the endpoint becomes:
https://your-server.com/rest-101/greeting/hello-world
You should now see the “Hello, World!” HTML page in your browser. This completes the
rest-api-101 tutorial.
9. REST C++ API (SDK)
The CETAN REST SDK provides a set of C++ classes for building RESTful services, handling requests, generating responses, managing uploads, and defining routes. This section documents each class in the REST framework.
9.1 REST Application Base Class
The REST class is the foundation of every CETAN REST application. It manages
application initialization, service registration, and endpoint discovery. Developers derive
from this class to define their application and register WebService components.
Constructors & Lifecycle
REST();
virtual ~REST();
The constructor initializes the REST application. The destructor cleans up registered services and internal state.
Members
const string tag = "REST";
A default tag used for logging or identification.
Endpoint Discovery
const string& json_endpoints() const;
const vector<string>& app_endpoints() const;
- json_endpoints() — returns all registered endpoints in JSON format.
- app_endpoints() — returns a list of endpoint paths for the application.
Initialization Hook
protected:
virtual void init();
Override init() in your derived class to configure services, routes, and
application‑specific initialization logic.
Service Registration (Required)
private:
virtual set<WebService*> register_services() const = 0;
Every REST application must implement register_services() to return the set of
WebService instances that belong to the application. Each service defines its own
routes and handlers.
9.2 WebService Base Class
WebService represents a REST resource bound to a specific base path. Developers
derive from this class to implement REST endpoints using Resource objects. Each
service is responsible for registering its own routes.
Constructors & Semantics
WebService(const string& path);
WebService(WebService&& orig);
WebService& operator=(WebService&& orig);
WebService(const WebService& orig) = delete;
WebService& operator=(const WebService& orig) = delete;
virtual ~WebService();
A WebService is move‑only. The path parameter defines the base URL
under which all service endpoints are registered.
Accessors
Request get_request() const;
const string& app_name() const;
const string& app_root_url() const;
const string& document_root() const;
const REST* get_app() const;
const string& get_path() const;
const vector<string>& get_endpoints() const;
- get_request() — returns the current request object.
- app_name() — name of the owning REST application.
- app_root_url() — root URL of the application.
- document_root() — static file root directory.
- get_app() — pointer to the owning
RESTinstance. - get_path() — base path for this service.
- get_endpoints() — list of registered endpoint paths.
Resource Registration (Required)
private:
virtual vector<Resource*> register_resources() const = 0;
Derived classes must implement register_resources() to return the list of
Resource objects that define the service’s HTTP endpoints.
12.3 Response Class
Response represents an HTTP response, including status code, headers,
cookies, and body content.
Header Enumeration
enum Header {
Cache_Control, Connection, Date, Pragma, Trailer, Transfer_Encoding, Upgrade, Via, Warning,
Accept, Accept_Charset, Accept_Encoding, Accept_Language, Authorization, Expect, From, Host,
If_Match, If_Modified_Since, If_None_Match, If_Range, If_Unmodified_Since, Max_Forwards,
Proxy_Authorization, Range, Referer, TE, User_Agent, Accept_Ranges, Age, ETag, Location,
Proxy_Authenticate, Retry_After, Server, Vary, WWW_Authenticate, Allow, Content_Encoding,
Content_Language, Content_Length, Content_Location, Content_MD5, Content_Range, Content_Type,
Expires, Last_Modified, Cookie, Set_Cookie
};
Constructors
Response();
Response(int code);
Response(Response&& orig);
Response& operator=(Response&& orig);
Response(const Response& orig) = delete;
Response& operator=(const Response& orig) = delete;
virtual ~Response();
Status & Body
bool has_content() const;
Response& status(int code);
int status() const;
void body(const string_view& body);
void body(const char* buf, unsigned size);
const string& body() const;
Headers & Cookies
string_view header() const;
Response& header(const char* buf, unsigned size);
Response& header(const map<string,string>& header);
Response& header(Header header, const string& value);
Response& header(const string& name, const string& value);
Response& header(const string_view& n, const string_view& v);
Response& header(const map<string_view,string_view>& headers);
Response& set_cookie(const string& name, const string& value);
9.4 ResourceBuilder Class
ResourceBuilder registers HTTP routes and binds them to member function handlers.
Route Registration
template<typename MemPtr>
auto PUT(const string& path, MemPtr mptr);
template<typename MemPtr>
auto POST(const string& path, MemPtr mptr);
template<typename MemPtr>
auto DEL(const string& path, MemPtr mptr);
template<typename MemPtr>
auto GET(const string& path, MemPtr mptr);
template<typename MemPtr>
auto add(const string& method, const string& path, MemPtr mptr);
Each method returns a RouteBuilder for additional configuration.
9.5 Request Class
Request represents an incoming HTTP request, including headers, method,
URI, cookies, parameters, and uploaded files.
Constructors
Request(void*);
Request(Request&& orig);
Request& operator=(Request&& orig);
Request(const Request& orig);
Request& operator=(const Request& orig);
virtual ~Request();
General Properties
Header header() const;
bool keepalive() const;
bool has_content() const;
bool authenticated() const;
string_view method() const;
string_view get_uri() const;
string_view get_path() const;
string_view request_line() const;
Content & Types
string_view mime_type() const;
string_view accept_type() const;
string_view content_type() const;
unsigned long content_length() const;
Query & Parameters
string_view get_query_string() const;
unordered_map<string_view,string_view> get_params() const;
string_view get_param(const string& name) const;
Headers & Cookies
string_view get_header(const string& name) const;
list<string_view> get_cookies() const;
string_view get_cookie(const string& name) const;
Uploads
list<FileUpload> get_upload_files() const;
9.6 Param Class Template
Param represents a named parameter with an optional value.
Constructors
Param();
Param(string&& name);
Param(const char* name);
Param(const string& name);
Param(const string_view& name);
Param(Param&& orig);
Param(const Param& orig);
Param& operator=(Param&& orig);
Param& operator=(const Param& orig);
Name & Value
void name(string&& s);
void name(const char* s);
void name(const string& s);
void name(const string_view& s);
string param() const;
bool has_value() const;
const string& name();
char value() const;
9.7 Header Class
Header provides access to HTTP header fields and cookies.
Constructors
Header();
Header(Header&& orig);
Header(const Header& orig);
Header& operator=(Header&& orig);
Header& operator=(const Header& orig);
virtual ~Header();
Accessors
string_view view() const;
const header_t& get() const;
list<string_view> get_cookies() const;
string_view get(const string& name) const;
string_view get_cookie(const string& name) const;
9.8 FileUpload Class
FileUpload represents a single uploaded file in a multipart request.
Constructors
FileUpload();
FileUpload(const string& filename);
FileUpload(HTTPFile* file);
FileUpload(FileUpload&& orig);
FileUpload(const FileUpload& orig);
FileUpload& operator=(FileUpload&& orig);
FileUpload& operator=(const FileUpload& orig);
virtual ~FileUpload();
Properties & Operations
bool has_content() const;
ssize_t file_size() const;
string_view filename() const;
string_view content_type() const;
const string& name_id();
int move_file(const WebService*, const string& new_dir) const;
int save_as(const WebService*, const string& dir, const string& name);
9.9 Stream Class
Stream represents an in‑memory content buffer.
Constructors
Stream();
Stream(Stream&& orig);
Stream(const Stream& orig);
Stream& operator=(Stream&& orig);
Stream& operator=(const Stream& orig);
virtual ~Stream();
Accessors
unsigned int length() const;
const string_view& view() const;
const string_view& content_type() const;
ssize_t save_as(const string& filename) const;
9.10 Uri Class
Uri stores a parsed URI string for a request or resource.
Constructors
Uri();
Uri(Uri&& orig);
Uri(const Uri& orig);
Uri& operator=(Uri&& orig);
Uri& operator=(const Uri& orig);
virtual ~Uri();
Accessor
const string_view& get() const;
9.11 RouteBuilder Class
The RouteBuilder class is a lightweight helper used internally by
ResourceBuilder to construct Resource objects. It binds an HTTP
method, a path, and a member function pointer, and then produces a fully constructed
Resource instance when build() is invoked.
Constructor
RouteBuilder(string method, string path, MemPtr ptr)
: method(method), path(path), memptr(ptr) {}
Creates a route builder for the given HTTP method, URL path,
and handler member function pointer memptr.
Build Functions
template<typename... Args>
Resource* build(Args&&... args) {
return new Resource(method, path, memptr, forward<Args>(args)...);
}
template<typename... Args>
Resource* build(const Args&... args) {
return new Resource(method, path, memptr, args...);
}
-
build(Args&&...) — forwards constructor arguments perfectly to the
Resourceconstructor. - build(const Args&...) — passes arguments by const reference.
Both overloads return a dynamically allocated Resource object configured with
the method, path, and handler pointer captured by the RouteBuilder.