The rampart-url module¶
Preface¶
What is in this section?¶
This section of the documentation is intended to document feature (supported or not) that do not neatly fit into other sections.
rampart-webserver.js Module¶
The rampart-webserver.js module is a JavaScript module that aims to simplify the
functionality of the rampart-server
module. It is part of the complete rampart distribution and lives in the process.modulesPath directory.
Loading the module.¶
The module can be loaded in the normal manner.
var wserv = require("rampart-webserver");
Standard Server Layout¶
Using the rampart-webserver module assumes a standard layout for static and dynamic
web content. Refer to the example webserver for a view of the
expected location of content and scripts.
Usage from a configuration file¶
The easiest method of starting the rampart webserver is to copy the example webserver https://github.com/aflin/rampart/tree/main/web_server
directory and edit the settings found in the web_server_conf.js script.
After editing, the server can be used as follows:
rampart@machine:~>$ rampart /path/to/web_server/web_server_conf.js start
Server has been started.
rampart@machine:~>$ rampart /path/to/web_server/web_server_conf.js help
usage:
rampart web_server_conf.js [start|stop|restart|letssetup|status|dump|help]
start -- start the http(s) server
stop -- stop the http(s) server
restart -- stop and restart the http(s) server
letssetup -- start http only to allow letsencrypt verification
status -- show status of server processes
dump -- dump the config object used for server.start()
help -- show this message
The usable settings in web_server_conf.js include all the possible settings applicable to the
server.start() function as well as extras to simplify the process and
add extra functionality.
Notable extras:
bindAll- Boolean, if true, bind the server to0.0.0.0and[::]ip addressesport- Number, use this value to setipPortandipv6Port.redirPort- Number, when launching a securehttpsserver, also launch ahttpserver using this port to redirect requests to the https server (assuming default port443).redir- Boolean, if true, setredirPortto80.rotateLogs- Boolean, if true, launch a monitor process to rotate the access and error log files at a given time. Default isfalse.rotateStart- String, the time to start rotating the logs. Default is00:00(for localtime, midnight).rotateInterval- Number, how often to rotate the logs. Default is86400(for every 24 hours). It may also be given as the Strings"hourly","daily"or"weekly".letsencrypt- String, for secure serving, the directory where the letsencrypt certificates can be found. Set to"example.comwould therefore look for certificates in the/etc/letsencrypt/live/example.com/directory. See Use with Letsencrypt below.serverRoot- String, the root directory (e.g."/path/to/my/web_server". Default is the current working directory.map- Object, replace the map and only use this Object to pass to the server.start() function.appendMap- Object, append default mappings passed to the server.start() function.monitor- Boolean, if true, launch a monitor process to continuously check that the server is running (every 10 seconds) and that the root index.html file can be reached (every 60 seconds). Default is false.stop- Boolean, if true, stop the server, along with the redirect server and monitor processes if either was launched.
Building a command line utility¶
To aid in starting a server from the command line without having to configure it from a JavaScript script, a small script such as the following can be used:
var wserv = require("rampart-webserver");
webserv.cmdLine(2);
The webserv.cmdLine(2); function will process options from command line
arguments, starting with the second one (skipping argv[0] (rampart) and argv[1]
(script_name.js). It
will then launch a server using the processed options.
The same functionality is also available from the main rampart executable and can be used as such:
rampart@machine:~>$ rampart --server ~/web_server/
Server has been started.
rampart@machine:~>$ rampart --server --stop ~/web_server/
server stopped
rampart@machine:~>$ rampart --server --help
rampart built-in server help:
Usage: rampart --[quick]server [options] [root_dir]
--server - run as a full server
--quickserver - run as a test server
--help, -h - this help message
--lsopts - print details on all options
--showdefaults - print the list of default settings for --server or --quickserver
--OPTION [val] - where OPTION is one of options listed from '--lsopts'
If root_dir is not specified, the current directory will be used
rampart@machine:~>$ rampart --server --lsopts
--ipAddr String. The ipv4 address to bind
--ipv6Addr String. The ipv6 address to bind
--bindAll Bool. Set ipAddr and ipv6Addr to '0.0.0.0' and '[::]' respectively
--ipPort Number. Set ipv4 port
--ipv6Port Number. Set ipv6 port
--port Number. Set both ipv4 and ipv6 port
--redirPort Number. Launch http->https redirect server and set port
--redir Bool. Launch http->https redirect server and set to port 80
--htmlRoot String. Root directory from which to serve files
--appsRoot String. Root directory from which to serve apps
--wsappsRoot String. Root directory from which to serve wsapps
--dataRoot String. Setting for user scripts
--logRoot String. Log directory
--accessLog String. Log file name. "" for stdout
--errorLog String. error log file name. "" for stderr
--log Bool. Whether to log requests and errors
--rotateLogs Bool. Whether to rotate the logs
--rotateInterval Number. Interval between log rotations in seconds
--rotateStart String. Time to start log rotations
--user String. If started as root, switch to this user
--threads Number. Limit the number of threads used by the server.
Default (-1) is the number of cores on the system
--sslKeyFile String. If https, the ssl/tls key file location
--sslCertFile String. If https, the ssl/tls cert file location
--secure Bool. Whether to use https. If true sslKeyFile and sslCertFile must be set
--developerMode Bool. Whether script errors result in 500 and return a stack trace. Otherwise 404
--letsencrypt String. If using letsencrypt, the 'domain.tld' name for automatic setup of https
(assumes --secure true and looks for '/etc/letsencrypt/live/domain.tld/' directory)
(if redir is set, also map ./letsencrypt-wd/.well-known/ --> http://mydom.com/.well-known/)
(if set to "setup", don\'t start https server, but do map ".well-known/" for http)
(sets port:443 unless set otherwise)
--rootScripts Bool. Whether to treat *.js files in htmlRoot as apps (not secure)
--directoryFunc Bool. Whether to provide a directory listing if no index.html is found
--daemon Bool. whether to detach from terminal
--monitor fork and run a monitor as a daemon which restarts server w/in 10 seconds if it dies
--scriptTimeout Number Max time to wait for a script module to return a reply in seconds (default 20)
--connectTimeout Number Max time to wait for client send request in seconds (default 20)
-d alias for '--daemon true'
--detach alias for '--daemon true'
--stop stop the server. Also stop the monitor and log rotation, if started
The default settings, whether used from the command line or with a script such as the included
web_server_conf.js
script are visible with the following commands:
rampart@machine:~>$ rampart --server --showdefaults ~/web_server
Defaults for --server:
{
"ipAddr": "127.0.0.1",
"ipv6Addr": "[::1]",
"bindAll": false,
"ipPort": 8088,
"ipv6Port": 8088,
"port": -1,
"redirPort": -1,
"redir": false,
"htmlRoot": "/home/rampart/web_server/html",
"appsRoot": "/home/rampart/web_server/apps",
"wsappsRoot": "/home/rampart/web_server/wsapps",
"dataRoot": "/home/rampart/web_server/data",
"logRoot": "/home/rampart/web_server/logs",
"accessLog": "/home/rampart/web_server/logs/access.log",
"errorLog": "/home/rampart/web_server/logs/error.log",
"log": true,
"rotateLogs": false,
"rotateInterval": 86400,
"rotateStart": "00:00",
"user": "nobody",
"threads": -1,
"sslKeyFile": "",
"sslCertFile": "",
"secure": false,
"developerMode": true,
"letsencrypt": "",
"rootScripts": false,
"directoryFunc": false,
"monitor": false,
"daemon": true,
"scriptTimeout": 20,
"connectTimeout": 20,
"quickserver": false,
"appendProcTitle": false,
"serverRoot": "/home/rampart/web_server",
"fullServer": 1
}
rampart@machine:~/dir_with_files>$ rampart --quickserver --showdefaults
Defaults for --quickserver:
{
"ipAddr": "127.0.0.1",
"ipv6Addr": "[::1]",
"bindAll": false,
"ipPort": 8088,
"ipv6Port": 8088,
"port": -1,
"redirPort": -1,
"htmlRoot": "/home/rampart/dir_with_files/",
"appsRoot": "",
"wsappsRoot": "",
"dataRoot": "",
"logRoot": "/home/rampart/dir_with_files/logs",
"accessLog": "",
"errorLog": "",
"log": false,
"rotateLogs": false,
"rotateInterval": 86400,
"rotateStart": "00:00",
"user": "nobody",
"threads": 1,
"sslKeyFile": "",
"sslCertFile": "",
"secure": false,
"developerMode": true,
"letsencrypt": "",
"rootScripts": false,
"directoryFunc": true,
"monitor": false,
"daemon": false,
"scriptTimeout": 20,
"connectTimeout": 20,
"quickserver": true,
"appendProcTitle": false,
"serverRoot": "/home/rampart/dir_with_files",
"fullServer": 0
}
Use with Letsencrypt¶
A shortcut for setting up a secure server with letsencrypt is available via the letsencrypt keys in web_server_conf or using
rampart --server --letsencrypt example.com. Setting the value to the appropriate domain name (e.g.
example.com) will
set the following keys automatically:
{
"secure": true,
"sslKeyFile": "/etc/letsencrypt/live/example.com/privkey.pem",
"sslCertFile": "/etc/letsencrypt/live/example.com/fullchain.pem",
}
Obtaining a key via the letsencrypt certbot utility requires access to http://example.com:80/.well-known/ and the corresponding mapped directory
on the filesystem. If the redir or redirPort setting is set along with the letsencrypt:"example.com", the
directory /path/to/my/web_server/letsencrypt_wd/.well-known will automatically be
created and mapped to http://example.com:80/.well-known/.
In addition, the letsencrypt key may be set to "setup" (or by doing
rampart ./web_server_conf.js letssetup) to prevent
starting the secure server when the certificates have not yet been issued by letsencrypt.
A full example of obtaining a certificate using certbot, substituting the
desired domain name with example.com:
# work must be performed as root
~>$ sudo bash
# install the certbot using appropriate package manager
[email protected]:~# apt install certbot
# change to the location of your web_server.
[email protected]:~# cd /path/to/my/web_server
# edit web_server_conf.js file and set ``"letsencrypt": "example.com"``
[email protected]:/path/to/my/web_server# vi ./web_server_conf.js
# start the http webserver in letsencrypt setup mode (don't start https)
[email protected]:/path/to/my/web_server# rampart ./web_server_conf.js letssetup
# verify redirect server has mapped .well-known/
[email protected]:/path/to/my/web_server# ls -a letsencrypt_wd/
. .. .well-known
# request a certificate (this machine must be reachable at [www.]example.com)
[email protected]:/path/to/my/web_server# certbot certonly --webroot \
--webroot-path /path/to/my/web_server/letsencrypt_wd \
-d example.com,www.example.com
# if certs were issued without error, the server can now be restarted
# with https enabled
[email protected]:/path/to/my/web_server# rampart ./web_server_conf.js restart
A root crontab entry will keep the certificate up to date:
0 5 * * * /usr/bin/certbot renew --quiet --renew-hook "rampart /path/to/my/web_server/web_server_conf.js restart" 2>/dev/null
websocket_client¶
NOTE: superseded by WebSocket Client Functions.
A command line websocket client and rampart module can be found in the unsupported_extras/websocket_client directory of the rampart distribution.
It can be used from the command line as such:
rampart@machine:~>$ /rampart/unsupported_extras/websocket_client>$ rampart wsclient.js
wsclient.js [ -h header] [-s] url:
url where scheme is ws:// or wss://
-H header is a header to be added ('headername=headerval'). May be used more than once.
-s show raw http request to server on connect
Once connected, text entered will be sent to the server while sent messages will appear in the terminal. There are also commands that can be run from the prompt:
.save filename- save the last binary message sent by the server. to a file..send filename- send a file as a binary message to the server..close- close the connection and quit.
More information is in the file itself.
forkpty-term¶
The unsupported_extras/forkpty-term
directory of the rampart distribution contains a sample web terminal emulator that uses the
rampart-server module,
websockets and the rampart.utils.forkpty() on the
server side, and xterm.js on the
client to create a fully functioning xterm in the browser.
The relevant files may be copied directly into the example webserver directory.
rampart-converter¶
NOTE: superseded by rampart-totext module.
The included rampart-converter module uses command line utilities to convert various file formats into plain text suitable for indexing with the sql module.
The following programs/modules should be installed and available before usage:
- pandoc - for docx, odt, markdown, rtf, latex, epub and docbook
- catdoc (linux/freebsd) or textutil (macos) - for doc
- pdftotext from the xpdf utils - for pdfs
- man - for man files (if not available, pandoc will be used)
- file - to identify file types
- head - for linux optimization identifying files
- gunzip - to decompress any gzipped document
- the rampart-html module for html and as a helper for pandoc conversions
Minimally, pandoc and file must be available for this module to load.
The following file formats are supported (if appropriate program above is available):
docx, doc, odt, markdown, rtf, latex, epub, docbook, pdf & man Also files identified as txt (text/plain) will be returned as is.
Usage:
var converter = require("rampart-converter.js");
var convert = new converter(defaultOptions);
Where defaultOptions
is Undefined or an Object of command line
flags for each of the converting programs. Example to only include the first two pages for a pdf
(pdftotext) and to convert a docx (pandoc) to markdown instead of to text:
var convert = new converter({
pdftotext: {f:1, l:2},
pandoc : {t: 'markdown'}
});
To convert a document:
var converter = require("rampart-converter.js");
var convert = new converter();
var txt = convert.convertFile('/path/to/my/file.ext', options);
or
var txt = convert.convert(myFileBufferOrString, options);
where options
overrides the defaultOptions above and is either of:
- same format as defaultOptions above:
{pdftotext: {f:1, l:2}}; or- options for the utility to be used:
{f:1, l:2}
Full example:
var converter=require('rampart-converter.js');
// specify options optionally as defaults
//var c = new convert({
// pandoc : { 't': 'markdown' },
// pdftotext: {f:1, l:2}
//});
var convert = new converter();
//options per invocation
var ptxt = convert.convertFile('convtest/test.pdf', {pdftotext: {f:1, l:2}});
var dtxt = convert.convertFile('convtest/test.docx', { pandoc : { 't': 'markdown' }});
// OR - alternative format for options:
var ptxt = convert.convertFile('convtest/test.pdf', {f:1, l:2});
var dtxt = convert.convertFile('convtest/test.docx', { 't': 'markdown' });
rampart.utils.printf("%s\n\n%s\n", ptxt, dtxt);
Command Line usage:
> rampart /path/to/rampart-converter.js /path/to/document.ext
rampart-llm¶
The rampart-llm.js
module provides a streaming interface to LLM servers that expose an OpenAI-compatible API. It
supports both llama.cpp’s llama-server and Ollama, and handles SSE parsing,
thinking/reasoning model output, and cancellation.
The module lives in the process.modulesPath directory.
Loading the module¶
var llm = require("rampart-llm.js");
Creating a connection¶
The module exports two constructors, one for each supported backend. Both accept an Object of options and verify that the server is reachable on construction.
// llama-server (llama.cpp)
var client = new llm.llamaCpp({
server: "127.0.0.1", // default
port: 8080 // default
});
// Ollama
var client = new llm.ollama({
server: "127.0.0.1", // default
port: 11434 // default
});
An error is thrown if the server is not running at the given address.
Note: llama-server
loads a single model at startup, so the model property described below is required by the API format but its value
is ignored. With Ollama, the model name selects which model to use.
Instance properties¶
After construction, set properties on the instance before calling query().
model- String. The model name. Required for Ollama (e.g."qwen2.5-coder:7b"). For llamaCpp, any non-empty string will do since llama-server always uses its loaded model.params- Object. Sampling and generation parameters that are merged directly into the/v1/chat/completionsPOST body. Common parameters include:
temperature- Number. Controls randomness (0.0 – 2.0).max_tokens- Number. Maximum tokens to generate.top_p- Number. Nucleus sampling threshold (0.0 – 1.0).top_k- Number. Top-k sampling.repeat_penalty- Number. Repetition penalty (1.0 = off; 1.1 – 1.3 typical).frequency_penalty- Number. Penalize tokens by frequency (0.0 – 1.0).presence_penalty- Number. Penalize tokens by presence (0.0 – 1.0).cancel- Boolean. Set totrueto abort an in-flight query. The stream will stop on the next chunk and the final callback will fire.
Example:
client.model = "qwen2.5-3b-instruct-q4_k_m.gguf";
client.params = {temperature: 0.2, max_tokens: 4096};
query()¶
The query() method
sends a prompt to the server and streams the response back through callbacks.
client.query(prompt, perTokenCallback, finalCallback);
Where:
prompt- Array or String. If an Array, it is sent asmessagesto the/v1/chat/completionsendpoint (chat mode). Each element is an Object withrole("system","user", or"assistant") andcontentproperties. If a String, it is sent aspromptto the/v1/completionsendpoint (completion mode).perTokenCallback- Function. Called for each streamed token and on error or completion. May benullif onlyfinalCallbackis needed. The callback receives a single Object argument with the following properties:
token- String. The token text.thinking- Boolean.trueif this token is reasoning/thinking content (from<think>tags or the--reasoning-formatflag in llama-server).error- Set if the server returned an error.done- Boolean.truewhen the stream has ended.serverResponse- The raw HTTP response object.finalCallback- Function. Called once after the stream ends. Receives a single Object with:
fullText- String. The complete response text (excluding thinking content).thinkingText- String. The complete thinking/reasoning text (only present if the model produced thinking output).answer- String. The answer portion after thinking (only present ifthinkingTextis present).serverResponse- The raw HTTP response object.error- Set if the query failed.
At least one of perTokenCallback or finalCallback must be provided.
Chat example¶
A basic chat completion with streaming output:
var llm = require("rampart-llm.js");
var client = new llm.llamaCpp({server: "127.0.0.1", port: 8080});
client.params = {temperature: 0.7, max_tokens: 2048};
var prompt = [
{role: "system", content: "You are a helpful assistant."},
{role: "user", content: "What is the capital of France?"}
];
client.query(
prompt,
// per-token callback — print each token as it arrives
function(res) {
if (res.error) {
fprintf(stderr, "Error: %J\n", res.error);
return;
}
if (res.done) return;
if (res.thinking)
fprintf(stderr, "(thinking) %s", res.token);
else
printf("%s", res.token);
},
// final callback — runs once when the stream ends
function(res) {
if (res.error) {
fprintf(stderr, "Query failed: %J\n", res.error);
return;
}
printf("\n\n--- Complete response ---\n%s\n", res.fullText);
}
);
Multi-turn conversation¶
To maintain a conversation, accumulate messages and send the full array on each turn. The server is stateless — it needs the entire history every time.
var conversation = [
{role: "system", content: "You are a helpful assistant."}
];
function ask(question, callback) {
conversation.push({role: "user", content: question});
client.query(conversation, null, function(res) {
if (!res.error) {
conversation.push({role: "assistant", content: res.fullText});
printf("%s\n", res.fullText);
}
if (callback) callback();
});
}
ask("What is the capital of France?", function() {
ask("What is its population?");
});
Cancelling a query¶
Setting cancel to
true on the instance
will abort the current stream on the next chunk.
// start a query
client.query(prompt, function(res) {
if (res.done) {
printf("(cancelled or complete)\n");
return;
}
printf("%s", res.token);
});
// cancel it after 2 seconds
setTimeout(function() {
client.cancel = true;
}, 2000);
Overriding params per query¶
The params property
can be saved and temporarily replaced for a side-query (e.g. a short classification task)
without affecting the main conversation settings.
var savedParams = client.params;
client.params = {temperature: 0.1, max_tokens: 2048};
client.query(classifyPrompt, null, function(res) {
client.params = savedParams; // restore original params
printf("Classification: %s\n", res.fullText);
});
Thinking/reasoning models¶
Some models produce internal reasoning wrapped in <think>...</think>
tags or via a separate reasoning_content field (when llama-server is started with --reasoning-format deepseek). The module handles both formats transparently:
- In the per-token callback,
res.thinkingistruefor reasoning tokens andfalsefor answer tokens. - In the final callback,
res.thinkingTextcontains the full reasoning andres.answercontains only the answer.res.fullTextcontains the full non-thinking output.
Note that thinking tokens consume the max_tokens budget but do not appear in fullText. When using thinking
models, set max_tokens high enough to accommodate both reasoning and answer (4096 or
more is recommended, as reasoning can easily consume several thousand tokens before the visible
answer begins).
Context window management¶
When a conversation grows long enough to exceed the server’s context window, the server will return an error. There are two approaches to handling this:
llama-server (llama.cpp): Start the server with the --context-shift flag to allow
automatic context shifting — older tokens are discarded from the KV cache to make room for new
ones. This is a server-start flag and cannot be set per-request. The context size is set with
-c (e.g.
-c 32768). When using --parallel for multiple slots, the context is divided evenly among slots.
llama-server also exposes several useful REST endpoints beyond the OpenAI-compatible
/v1/ API:
-
/props— returns server properties includingdefault_generation_settings.n_ctx(the configured context size). -
/slots— returns per-slot information includingn_ctx(the actual per-slot context size, which accounts for--parallel). -
/health— returns server health status.
These can be queried with rampart-curl to discover the context size at runtime, for example to
display a token usage indicator to the user.
Ollama: Context shifting is handled automatically. The context window size
defaults to 2048 tokens but can be increased by passing num_ctx in the params (e.g.
client.params = {num_ctx: 32768}).
Ollama will silently shift context when the window fills up.
Using with the Rampart web server¶
The module is designed to work well in websocket scripts. The
req object in a
websocket handler persists across messages, so an llm instance created during the
handshake (req.count
== 0) can be reused for all subsequent
messages in the connection. See the llmchat.js example in the Rampart LLM demo for a complete working
implementation.
var llm = require("rampart-llm.js");
function handler(req) {
if (req.count == 0) {
// handshake — create connection, store on req
req.llm = new llm.llamaCpp({server: "127.0.0.1", port: 8080});
req.llm.params = {temperature: 0.5};
return;
}
// subsequent messages
var userText = sprintf("%s", req.body); // Buffer to string
var prompt = [
{role: "system", content: "You are a helpful assistant."},
{role: "user", content: userText}
];
req.llm.query(prompt,
function(res) {
if (res.error || res.done) return;
req.wsSend(res.token);
},
function(res) {
req.wsSend({end: true});
}
);
}
module.exports = handler;
The c_module_template_maker utility¶
Included in the rampart unsupported extras is a utility script to help with the creation of rampart modules written in C. It can be found in the unsupported_extras/c_module_template_maker directory of the rampart distribution.
Creating a C Module template¶
The first step is to copy the make_cmod_template.js into a new directory for the project. After copying,
it can be run as such:
rampart@machine:~>$ mkdir my_module
rampart@machine:~>$ cd my_module
rampart@machine:~/my_module>$ cp /usr/local/rampart/unsupported_extras/c_module_template_maker/make_cmod_template.js ./
rampart@machine:~/my_module>$ rampart make_cmod_template.js -- --help
usage:
make_cmod_template.js -h
or
make_cmod_template.js c_file_name [-f function_args] [-m make_file_name] [-t test_file_name]
where:
c_file_name - The c template file to write.
make_file_name - The name of the makefile to write (default "Makefile")
test_file_name - The name of the JavaScript test file (default c_file_name-test.js)
function_args - Create c functions that will be exported to JavaScript.
- May be specified more than once.
- Format: cfunc_name:jsfunc_name[:nargs[:input_types]]
function_args format (each argument separated by a ':'):
cfunc_name: The name of the c function.
jsfunc_name: The name of the javascript function to export.
nargs: The number of arguments the javascript function can take (-1 for variadic)
input_types: Require a variable type for javascript options:
A character for each argument. [n|i|u|s|b|B|o|a|f].
Corresponding to require
[ number|number(as int)|number(as int>-1)|string|
boolean|buffer|object|array|function ]
A ready to compile, testable module will be produced if both "nargs" and "input_types" are provided.
Example to create a module that exports two functions which each take a String and Number:
rampart make_cmod_template example.c -f my_func:myFunc:2:sn -f my_func2:myFunc2:2:sn
Example usage to create a module¶
The following is an example of how to make a simple C module that capitalizes a string.
First, run the script with appropriate options to create the template files. In this case it is
used to create a module named "myutil.so" which will export an Object with the
function "capitalize". Calling this function from JavaScript will run a C function
named my_capitalization_func.
rampart@machine:~/my_module>$ rampart make_cmod_template.js myutil -f my_capitalize_func:capitalize:1:s
rampart@machine:~/my_module>$ ls
make_cmod_template.js Makefile myutil.c myutil-test.js
rampart@machine:~/my_module>$ make
cc -Wall -g -O2 -std=c99 -I/usr/local/rampart/include -fPIC -shared -Wl,-soname,myutil.so -o myutil.so myutil.c
myutil.c: In function ‘my_capitalize_func’:
myutil.c:5:18: warning: unused variable ‘js_arg1’ [-Wunused-variable]
5 | const char * js_arg1 = REQUIRE_STRING(ctx, 0, "capitalize: argument 1 must be a string");
| ^~~~~~~
At this stage, the function does not actually do anything (hence the warning above). But it is
a fully functioning module which can now be edited to add actual functionality. The
myutil.c file will
contain the following:
#include "/usr/local/rampart/include/rampart.h"
static duk_ret_t my_capitalize_func(duk_context *ctx)
{
const char * js_arg1 = REQUIRE_STRING(ctx, 0, "capitalize: argument 1 must be a string");
/* YOUR CODE GOES HERE */
return 1;
}
/* **************************************************
Initialize module
************************************************** */
duk_ret_t duk_open_module(duk_context *ctx)
{
/* the return object when var mod=require("myutil") is called. */
duk_push_object(ctx);
/* js function is mod.capitalize and it calls my_capitalize_func */
duk_push_c_function(ctx, my_capitalize_func, 1);
duk_put_prop_string(ctx, -2, "capitalize");
return 1;
}
We can add the needed #includes and replace the /* YOUR
CODE GOES HERE
*/ with the following:
//for linux and strdup and -std=c99
#define _DEFAULT_SOURCE
#include <ctype.h>
#include <string.h>
#include "/usr/local/rampart/include/rampart.h"
static duk_ret_t my_capitalize_func(duk_context *ctx)
{
const char * js_arg1 = REQUIRE_STRING(ctx, 0, "capitalize: argument 1 must be a string");
char *capped = strdup(js_arg1), *s=capped;
while(*s) *(s++)=toupper(*s);
duk_push_string(ctx, capped);
free(capped);
return 1;
}
Then recompile:
rampart@machine:~/my_module>$ make
Also created is a script to test the new module named mymod-test.js.
rampart.globalize(rampart.utils);
var myutil = require("myutil");
function testFeature(name,test,error)
{
if (typeof test =='function'){
try {
test=test();
} catch(e) {
error=e;
test=false;
}
}
printf("testing %-50s - ", name);
if(test)
printf("passed\n")
else
{
printf(">>>>> FAILED <<<<<\n");
if(error) printf('%J\n',error);
process.exit(1);
}
if(error) console.log(error);
}
testFeature("myutil.capitalize basic functionality", function(){
var lastarg = "String";
return lastarg == myutil.capitalize(lastarg);
});
Next step is to modify the testFeature() call in mymod-test.js to verify the new
function works as expected:
testFeature("myutil.capitalize basic functionality", function(){
var mystring = "String";
var expected = "STRING";
return expected == myutil.capitalize(mystring);
});
Running the test script results in the following output:
rampart@machine:~/my_module>$ rampart myutil-test.js
testing myutil.capitalize basic functionality - passed
- See:
- Macros below for some useful macros.
- See also:
- Duktape Api
rampart-cmodule¶
With the rampart-cmodule, it is possible to embed c code that will be automatically compiled for use as a javascript function.
Usage:
var cmodule = require('rampart-cmodule.js');
var myfunc = cmodule(funcName, funcCode, supportFuncs, flags, libs, extraSearchPath);
/* or */
var myfunc = cmodule({
name:funcName,
exportFunction:funcCode,
supportCode: supportFuncs,
compileFlags: flags,
libraries: libs,
rpHeaderLoc: extraSearchPath
});
- Where:
-
-
funcNameis a String - the name of your C function and module. -
exportFunctionis a String - code that contains#includelines and a single function block without the function name and signature. -
supportFuncsis a String - support functions which will be placed above theexportFunctionand below the#includelines, so they can be called from the exportFunction without forward declarations. -
flagsis a String - any desired flags like-g -O2and the like. -
libsis a String - libraries to be included when compiling, such as-lm. -
rpHeaderLocis a String - a path to first search forrampart.h. If omitted the search will includeprocess.installPath + "/include/rampart.h"and other standard locations.
-
Example:
var cmodule = require('rampart-cmodule.js');
var name = "squareRoot";
// include lines and function block only. Any extra, including
// comments not inside the function will throw an error.
var func = `
#include <math.h>
{
double d = REQUIRE_NUMBER(ctx, 0, "squareRoot: argument 1 must be a Number");
duk_push_number(ctx, _square_root(d) );
return 1;
}`;
// support functions are written as normal C and placed above the main function
var supportFuncs = `
static double _square_root (double a) {
return sqrt(a);
}`;
var extraFlags="-g -O3";
var libs = "-lm"
//build squareRoot.so, or throw error
var sqRt = cmodule(name, func, supportFuncs, extraFlags, libs);
console.log(sqRt(64));
// second go, don't need program if it is already built
// effectively the same as var myfunc2 = require('squareRoot.so');
var sqRt2 = cmodule(name);
console.log(sqRt2(111));
/* expected output:
Files:
Two files named squareRoot.c and squareRoot.so
Stdout:
8
10.535653752852738
*/
- See:
- Duktape Api
Rampart Macros for C Modules¶
Require macros require that the particular JavaScript variable be of the specified type, or throws the given error, which is a variadic printf type format.
Macro Return Type Notes REQUIRE_STRING(ctx,idx,...)const char * REQUIRE_LSTRING(ctx,idx,sz,...)const char * sz is a duk_size_t *REQUIRE_INT(ctx,idx,...)int (int) doubleREQUIRE_UINT(ctx,idx,...)unsigned int (unsigned int) double
throws error if double < 0REQUIRE_POSINT(ctx,idx,...)int throws error if int < 0 REQUIRE_INT64(ctx,idx,...)int64_t (int64_t) doubleREQUIRE_UINT64(ctx,idx,...)uint64_t (uint64_t) double
throws error if double < 0REQUIRE_BOOL(ctx,idx,...)int 0|1REQUIRE_NUMBER(ctx,idx,...)double REQUIRE_FUNCTION(ctx,idx,...)none no return REQUIRE_OBJECT(ctx,idx,...)none no return REQUIRE_PLAIN_OBJECT(ctx,idx,...)none no return
throw if array or functionREQUIRE_ARRAY(ctx,idx,...)none no return REQUIRE_BUFFER_DATA(ctx,idx,sz,...)void * sz is a duk_size_t *
any buffer typeREQUIRE_STR_TO_BUF(ctx,idx,sz,...)void * sz is a duk_size_t *
If string, converts to buffer firstREQUIRE_STR_OR_BUF(ctx,idx,sz,...)const char * sz is a duk_size_t *
casts to(const char *)if bufferExample:
For a function that must be called as
myfunc(bufData, myPosInt)wherebufDatamust be a Buffer andmyPosIntmust be a Number equal to or greater than0.duk_size_t mybufsz; void *mybuf = REQUIRE_BUFFER_DATA(ctx, 0, &mybufsz, "myfunc - First argument must be a Buffer"); duk_idx_t idx = 1; int myposInt = REQUIRE_POSINT(ctx, idx, "myfunc - Argument #%d must be a positive integer", (int)(idx+1));
- Throw Macro:
- At any point in any function, a JavaScript error can be thrown and flow of the program can be returned to Javascript by using RP_THROW (which uses duk_push_error_object() and duk_throw()).
if(something_bad)
RP_THROW(ctx, "Something bad happened at line %d", __LINE__);
- Getting duk_context:
-
The
exportFunctionwill already haveduk_context *ctxpassed to it. In other functions, you can continue to pass thectxpointer, or, if necessary, it can be retrieved as such:
RPTHR *thr = get_current_thread();
duk_context *ctx = thr->ctx;
- Note:
-
* ctxis not a global variable, and may change depending on the current thread. Nothing special needs to be done to retrieve the validctxother than the above. - Debugging stack:
-
Keeping track of variables on the duktape value stack can be aided with a few macros that print out the stack contents.
-
printstack(ctx)- A simple printout of the value stack contents. -
prettyprintstack(ctx)- A JSON-like printout of the stack. -
safeprintstack(ctx)- Prints stack, taking care to not infinitely regress if there are self referencing or cyclic Objects present. -
safeprettyprintstack(ctx)- combines the two above. -
printat(ctx, idx)- prints the variable at stack indexidx -
printenum(ctx, idx)- enumerate Object at idx, printing out key/value pairs and including non-enumerable, symbols and hidden symbols.
-
- Returning a value to JavaScript:
-
Pushing a value to the top of the stack and returning
1will set the return value in Javascript. Returning0sets the return value toundefined. See theduk_push_*functions in the Duktape Api. If the value to be returned is not on the top of the stack, duk_dup() or duk_pull() functions may be used to place the variable on top of the stack before returning. - Simple Type Check:
-
A simple type check of a value can be performed using
rp_gettype().int type = rp_gettype(ctx, idx); /* type is one of: RP_TYPE_STRING RP_TYPE_ARRAY RP_TYPE_NAN RP_TYPE_NUMBER RP_TYPE_FUNCTION RP_TYPE_BOOLEAN RP_TYPE_BUFFER RP_TYPE_NULL RP_TYPE_UNDEFINED RP_TYPE_SYMBOL RP_TYPE_DATE RP_TYPE_OBJECT RP_TYPE_FILEHANDLE RP_TYPE_UNKNOWN */
This allows you to, e.g., check for a Date Object without getting
"object"back as you would withtypeofand obviates the need to use ofinstanceOf DateorArray.isArray()where is not cleanly codable in C. It is also the basis of the rampart.utils.getType() JavaScript call.