Host API in Depth
The quickstart covers a host's first day. This page is the rest of
jai::engine's host API. The rules that run through all of it:
- A call fails one way. A call that returns a
checked_resultreports every failure in it; a call that returns a plain value throws. The engine is usable after either. - A value belongs to its engine and its engine's thread. Values cross engines by copy and die with their engine.
- A host
T¶meter writes back into the script lvalue it was given;const T&and by-value parameters only read. - Write-through belongs to a registered name, never to a copy stored somewhere else.
- Declare before loading. Registrations the checker reads (functions, constant arguments,
nodiscard) come before the scripts that use them.
Registering C++ functions
// Typed: the signature drives conversion and overload resolution
eng->add_function("square", [](jai::script_int n) { return n * n; });
std::cout << eng->execute("square(12)").as_int() << "\n";
// Variadic: the engine and every argument of the call
eng->add_variadic_function("sum_all", [](jai::engine&, std::span<const jai::script_value> args) {
jai::script_int total = 0;
for (const auto& a : args) total += a.as_int();
return total;
});
std::cout << eng->execute("sum_all(1, 2, 3, 4)").as_int() << "\n";144
10Registering a name again with another signature adds an overload, resolved by argument types at the call. A variadic
function returns what a typed one returns, a script_value, or a checked_result<script_value>
whose error the script receives. A script_value it constructs takes the engine it is given
(jai::script_value(total, &eng)) or comes from eng.make_value(v), make_null(),
make_array() or make_map(). A callable of the argument vector alone,
const std::vector<jai::script_value>&, returning a checked_result<script_value>,
registers as a variadic function too, and captures the engine it needs.
The raw-arity and overload registrations, custom and container conversions, class and backend queries, text_of, the app folders and set_output_stream: registration and engine queries.
A direct entry for a hot variadic function
A typed add_function binding has a fast call path unless a parameter is a non-const reference to a value
(a T& that writes back). const std::string&, a bound object's T&,
const std::vector<jai::script_value>& and const jai::script_value& parameters
receive the script's own values, never copies. An engine-first variadic function has one as well: bound alone under its
name, with no requirement, it runs on the arguments where they are for a VM call of 1 to 8 arguments, none of them a
reference, with no argument vector and no std::function call. Every other call reaches the same callable
through the function.
eng->add_variadic_function("label", [](jai::engine&, std::span<const jai::script_value> args) -> std::string {
if (args.size() != 1) return "?";
return args[0].is_int() ? "int" : args[0].is_float() ? "float" : "other";
});
std::cout << eng->execute("label(2.5)").as_string() << "\n";floatA variadic function of the argument vector pays a std::function call and an argument vector per call.
add_host_direct(name, arity, invoke, target) gives it, or any function a name binds, an entry over the
arguments where they are:
// one body for both: the function and its direct entry
static jai::checked_result<jai::script_value> label_of(const void* target, const jai::script_value* args, size_t count) {
auto* eng = static_cast<jai::engine*>(const_cast<void*>(target));
if (count != 1) return jai::script_value(std::string("?"), eng);
return jai::script_value(std::string(args[0].is_int() ? "int" : args[0].is_float() ? "float" : "other"), eng);
}
eng->add_variadic_function("label", [e = eng.get()](const std::vector<jai::script_value>& args) {
return label_of(e, args.data(), args.size());
});
eng->add_host_direct("label", 1, &label_of, eng.get()); // after the function: it keys on the function "label" binds now
std::cout << eng->execute("label(2.5)").as_string() << "\n";floatThe VM calls invoke(target, args, arity) for a call of exactly arity arguments, none of them a
reference; jai::engine::host_direct_any_arity takes any call of 1 to 8 arguments. Every other call (another
count, a reference argument, a name re-registered or rebound) calls the function, so invoke must return what
the function returns. Sharing one body keeps them equal.
Globals: copies and live bindings
eng->add_global("PI", jai::script_value(3.14159, eng.get())); // copied in
int hp = 100;
eng->add_global_ref("hp", hp); // a live binding: no copy
eng->execute("hp = hp - 25;");
std::cout << "hp after script damage: " << hp << "\n";
hp = 1; // and the script sees C++ writes
std::cout << "script sees: " << eng->execute("hp").as_int() << "\n";hp after script damage: 75
script sees: 1add_global_ref on a primitive (int8 to int64, the unsigned widths,
float, double, bool, char, std::string) binds the C++
variable itself. For any other type it holds a non-owning pointer. Either way the variable must outlive the engine.
| Script does | The C++ variable |
|---|---|
| reads the name | is read live, every time |
hp = 5, hp += 7, ++hp, hp--, string += | is written, at every width. boundInt += 2.5 truncates and still writes through. A bound char is a one-byte int and reads as its code. |
auto& r = hp;, then writes r; passes hp to a script int& parameter; auto& h = player.hp; on a bound object | is written: a reference aliases it |
stores it in an array element, a map slot or an object member ([hp], {"k": hp}, push, assignment) | is snapshotted: the stored copy is detached |
(host) clone() of the bound script_value | is snapshotted. A plain copy of the script_value stays bound. |
Namespace members
A qualified name in any registration call (add_global("game::tick", v),
add_global_ref, add_function("game::spawn", f)) registers a member of that namespace.
int64_t tick = 41;
eng->add_global_ref("game::tick", tick);
eng->add_function("game::spawn", [](int64_t n) { return n * 2; });
eng->execute(R"(
namespace game { int next() { return tick + spawn(1); } } // the namespace's code reads them bare
print(`${game::tick} ${game::next()}`);
)");41 43- It is read-only from script:
game::tick = 1raisesCannot assign to 'tick': it is read-only. The host changes it through the live binding or by registering the name again. - A unit loaded before the registration declares it:
extern game::tick;(extern). - The host names it qualified:
get_variable("game::tick"),has_function("game::spawn").
References into C++: as<T&> and T& parameters
A non-const as<T&> on a bound value is a live reference into the C++ variable, but only when
the bound type is exactly the script-side type: int64_t for as<jai::script_int&>,
double for as<jai::script_float&>, bool for
as<jai::script_bool&>, std::string for as<std::string&>. Any
other width throws jai::runtime_error:
jai::script_int gold = 10;
eng->add_global_ref("gold", gold);
auto v = eng->execute("gold"); // a bound alias, not a snapshot
v.as<jai::script_int&>() += 5;
std::cout << "gold after as<script_int&> += 5: " << gold << "\n";
int32_t narrow = 5;
eng->add_global_ref("narrow", narrow);
try {
eng->execute("narrow").as<jai::script_int&>(); // an int32_t is not a script_int
} catch (const jai::runtime_error& e) {
std::cout << "int32 as<script_int&>: " << e.what() << "\n";
}
eng->execute("narrow += 7;"); // by name every width works
std::cout << "narrow after script += 7: " << narrow << "\n";gold after as<script_int&> += 5: 15
int32 as<script_int&>: cannot bind a non-const script reference to a C++-bound variable of a different width
narrow after script += 7: 12A non-const T& parameter of a bound function works on any script lvalue:
eng->add_function("heal", [](int64_t& hp, int64_t amount) { hp = std::min<int64_t>(hp + amount, 100); });
eng->add_function("tag", [](std::string& name) { name += " (healed)"; });int hp = 40;
heal(hp, 25); // hp is 65
var party = [10, 20];
heal(party[1], 90); // party[1] is 100
tag(player.name); // a field, a map value, a C++ property (through its setter), v.x of a vector
heal(hp + 1, 5); // a temporary: the callee works on a copy, nothing is stored back- The callee gets a copy of the argument's value. When it returns or throws, its final value is stored back with the
store a script
=makes, so the variable's declared type converts or rejects it. A bound argument of exactly matching width is aliased instead. - Every lvalue writes back (a variable, a field, an element, a map value, a static, a reference, a C++ property, a
math component), and every value kind: numbers,
bool,std::string,std::vector,std::map,jai::script_value,std::shared_ptr<T>, enums, math-adapted types. A registered class taken asT&is the live object and needs no store. - A binding with no non-const
T¶meter has no write-back step.
Running scripts
eng->add_include_path("scripts"); // where import/include look after the file's own folder
eng->execute_file("scripts/game.jai"); // parse (or load scripts/.cache/game.jai.jaibite), check, run- What a script declares (functions, classes, globals) stays in the engine for later calls.
execute_fileattributes every node to the file's canonical path: check listings, stack traces and debugger breakpoints name it. Code fromexecute(source)is<script>.execute_filekeeps a parsed copy at.cache/game.jai.jaibitebeside the source. Add.cache/to.gitignore, move it witheng->jaibite_cache_directory(dir, root)(jaibite), or turn it off witheng->jaibite_cache(false).execute(source)keeps the last 64 distinct sources parsed and compiled, so repeating a string skips both. For hot scripts hold a jaibite.
Per-execution variables
execute(source, vars) and execute_file(path, vars) take a
jai::instance_variables (a std::unordered_map<std::string, jai::script_value>). A non-empty
map is a scope over the globals for that one execution:
eng->execute("var counter = 0;"); // no overlay: a global
jai::instance_variables locals{{"increment", eng->make_value(int64_t{5})}};
eng->execute("counter = counter + increment;", locals); // reads the overlay, writes the global
eng->execute("var temp = 1;", locals); // temp is declared in the overlay
eng->execute("counter").as_int(); // 5
eng->execute("increment"); // raises: the overlay is gone
eng->execute("temp"); // raises: temp died with the overlay- A supplied name shadows a global of the same name for that execution; the checker treats it as declared.
- Top-level declarations land in the overlay and vanish with it. With an empty map they are globals.
- It is not dynamic scope: a function defined by an earlier execute does not see a later execute's overlay.
- A script-text receiver on a
jai::signalruns this way, the emit's arguments as the overlay (Connecting from script).
Calling script from C++
A script function is a value: look it up and call it with a vector of arguments.
int damage_for(int level) { return level * 3; }// examples/embed_quickstart makes these calls
jai::script_value fn = eng->get_variable("damage_for");
jai::checked_result<jai::script_value> r = fn.as_function()({eng->make_value(int64_t{4})});
if (!r) { throw std::runtime_error(eng->format_error(r)); } // "Division by zero", ...
int64_t dmg = r.value().as_int(); // 12- The result carries every failure, so a result check is all a frame loop needs.
eng->format_error(r)is the text andeng->format_stack_trace()the script stack. - A typed wrapper throws instead.
jai::convert_script_function<int64_t(int64_t)>(fn)gives astd::functionwhose failures leave asjai::runtime_error. A null value converts to an emptystd::function(test it withif (cb)); any other non-function makes the conversion throw. - A call made while no script runs is a host entry and gets a fresh execution budget, like
execute()(Safety Rails). - A call made while a script runs (from a host function the script called, or a signal it emitted)
needs
jai::engine::external_call_guard guard(eng.get());around it, so reference parameters do not bind against the script call in flight. The guard is harmless elsewhere.
Reading script state back
int score = 7;
array<string> log = ["spawned", "hit"];
map<string, int> kills = {"orc": 3, "troll": 1};
array<var> loot = [12, "rope", 2.5];// examples/embed_quickstart makes these calls
int64_t score = eng->get_variable("score").as_int();
auto log = eng->get_variable("log").as<std::vector<std::string>>(); // a typed array copies out
auto kills = eng->get_variable("kills").as<std::map<std::string, int64_t>>();
jai::script_value loot = eng->get_variable("loot"); // hold the value: as_array() views into it
const std::vector<jai::script_value>& items = loot.as_array(); // array<var> only
jai::script_value log_value = eng->get_variable("log");
log_value.unchecked_array_node()->for_each(eng.get(), [](const jai::script_value& line) {
std::cout << line.as_string() << "\n"; // any array kind, no copy of the buffer
});- Typed arrays are raw buffers and copy out as a
std::vector; onlyarray<var>has ascript_valuevector foras_array()to view. On a typed arrayas_array()raises an error naming the extraction that works.unchecked_array_node()requiresis_array(). - A bound C++ object comes back with
as<std::shared_ptr<Monster>>(), sharing the live object. - A host function taking
bound_array<int64_t>&mutates a typed array in place (core/bound_array.hpp). - Write back with
add_global(name, value)or anexecutethat assigns.
How long a value lives
| Value | Valid |
|---|---|
a borrow: a script_value C++ receives as an argument, reads out of a container, or copies from a held value | for the call, like a C++ reference parameter |
a jai::held_value: what execute, execute_file, get_variable and eng->hold(v) return | until its engine is destroyed. It never keeps the engine alive. |
| a held value after its engine is gone | valid() is false, get_engine() is null, every read throws jai::runtime_error Value outlived its engine.; copying, assigning and destroying it are safe |
- Teardown. A destroyed engine first releases every held payload while it can still run script, so a held instance's destructor runs then.
- Threads. Another thread may copy, read, assign or drop a value only while the engine is idle, with the hand-off synchronized as for any C++ object. Debug builds assert when an object dies on another thread while its engine runs.
- Other engines. A value of another engine passed to
add_global,make_value, instance variables or a script function's arguments enters by copy: numbers, strings and math values copy, arrays and maps convert element by element. An object, a function or a reference throwsCannot pass an object from another engine: values cross engines by copy (numbers, strings, math values and arrays and maps of them).(ora function,a reference).
Error surfaces
Which way a call fails depends on the call, never on what went wrong. The failures are the same for all of them: a
parse error, a check error, a run-time error, a script throw nothing caught, and an import
whose file is missing or fails its check.
| Call | A failure |
|---|---|
execute(source), execute(source, vars), execute(jaibite&), execute_file(path), execute_file_source, jaibite(source) | throws |
a std::function from convert_script_function<Sig>(fn), or the std::function parameter of a bound host function | throws jai::runtime_error |
fn.as_function()(args) on a script function, a lambda, a static method or a reflect::bind value | in the checked_result |
coroutine_handle::next(engine*, n) | in the checked_result |
extend_checked(...) / extend(...) | in the result / throws |
| a script receiver of a C++ signal | throws out of the emit; the receivers after it do not run |
execute never signals failure through its return value. It returns the value of the
script's last top-level expression, and a null value when the script ends in anything else (a declaration, a loop), so
result.is_null() after a normal return says only that the script produced no value. A script that does not
parse or does not pass the checker throws before any statement runs.
execute<T>(...) is execute(...).as<T>(). It takes what
execute takes (a string, a script_source, a string with instance variables) and returns the
result as a T: int v = eng->execute<int>("2 + 2");. A result that is not a
T throws what as<T>() throws. A script int is 64 bits and
int is int32_t, so execute<int> of a value past its range throws
Integer value out of range for int; ask for int64_t to take any script int.
T is a value (a number, a std::string, a copied object); a reference, a pointer or a
std::string_view does not compile, because the result does not outlive the call.
| Exception | Raised for | what() |
|---|---|---|
jai::parse_error | syntax | parse failed: and every syntax error with its location |
jai::static_check_error | load-time type and name errors | static check failed: and every error in the source, compiler style |
jai::script_exception | anything raised while running, an uncaught script throw included | the error's text, the text a script catch sees |
All derive from std::exception, so catch (const std::exception&) is a valid backstop.
script_exception derives from jai::runtime_error, which with parse_error derives
from jai::exception and carries a location(); static_check_error derives from
std::runtime_error, and its listing holds the locations.
try {
eng->execute("int a = missing_one; int b = missing_two;");
} catch (const jai::static_check_error& e) {
std::cout << e.what(); // every error in the source, compiler style
}static check failed:
<script>:1:9: error: use of undeclared identifier 'missing_one'
int a = missing_one; int b = missing_two;
^
<script>:1:30: error: use of undeclared identifier 'missing_two'
int a = missing_one; int b = missing_two;
^A name the host injects later is declared with extern; using it undefined is then a run-time error,
raised only if control reaches it: eng->execute("extern function spawn_wave; spawn_wave(3);") throws
jai::script_exception 'spawn_wave' is declared extern but never defined.
eng->format_stack_trace() and eng->last_stack_trace() give the script stack of the most
recent failure:
try {
eng->execute("function inner() { throw \"boom\"; }\n"
"function outer() { inner(); }\n"
"outer();");
} catch (const jai::script_exception& e) {
std::cout << e.what() << "\n" << eng->format_stack_trace();
}boom
at inner (<script>:1)
at outer (<script>:2)
at <script> (<script>:3)- Nested calls. A call made from inside running script (a host function calling back into script, a signal emit) is part of that script's run: its failure travels on to the script, which can catch it.
- Errors nothing can catch (a destructor failing while the engine tears down its globals) go to the
sink
eng->set_script_error_handler(...)installs; the default writes tostd::cerr.
Constant arguments
Some arguments must never come from data: SQL text, a shell command. engine::constant_argument(callable, index,
refusal) declares that argument index of a host function ("run_sql") or bound method
("Database.query") takes only the program's own constants: a string literal, a const variable
initialized with a constant, a ternary whose arms are constants, a + of constants, or an array literal of
them. A template string with a splice is not one, nor is anything read, computed or passed in.
jai::engine* raw = eng.get();
eng->add_function("run_sql", [raw](const std::string& sql, const jai::script_value& value) -> std::string {
if (!raw->constant_argument_admits("run_sql", 0, sql)) {
throw std::runtime_error("SQL text must be a constant; pass values as parameters (?)");
}
return execute_with_parameter(sql, value); // the text is the program's, the value only binds
});
eng->constant_argument("run_sql", 0, "SQL text must be a constant; pass values as parameters (?)"); // before any script loadsconst string FIND = "SELECT * FROM posts WHERE id = ?";
run_sql(FIND, id); // a const
run_sql(newest ? "SELECT * FROM posts ORDER BY id DESC" : "SELECT * FROM posts", 0);
run_sql("SELECT * FROM posts WHERE title = '" + title + "'", 0); // a load error- At load the checker refuses any other argument with the host's refusal:
argument 1 of 'run_sql' is not a constant: SQL text must be a constant; pass values as parameters (?). For a method it does so wherever it knows the receiver's class. - At run time nothing rests on the checker. As each unit loads (a
.jaibiteand a[[warn_mode]]region included) the engine gathers the constant texts its calls pass to the argument, andconstant_argument_admits(callable, index, text)answers whether a text is one of them. The host function refuses the rest, however they arrived (avar, a function value,reflect::invoke). - Declare before any script loads: a unit loaded earlier was never gathered, so its texts are refused.
A result the script must use
A binding cannot read C++'s [[nodiscard]] off a function, so the host declares it in the registration's
trailing option, before any script loads:
eng->add_function("try_connect", [](const std::string& host) -> bool { return open_socket(host); }, jai::nodiscard);
jai::dynamic_binder<Inventory>(*eng, "Inventory")
.method("take", &Inventory::take, jai::nodiscard);A script statement that drops the result then fails the load, under the language's
[[nodiscard]] rule: the result of 'try_connect' is discarded,
but 'try_connect' is [[nodiscard]]: use it, or keep it in the placeholder (auto _ = try_connect(...);). For a
method this holds wherever the checker knows the receiver's class. add_variadic_function and .static_method
take the option too, and it combines with the others: jai::nodiscard | jai::serial | jai::filesystem.
is_nodiscard_function(callable) asks, by "name" or "Class.method". A
namespace entry described with a host_entry_signature sets its nodiscard field;
in_place_twin adds that the entry copies what the same-named method changes in place, and the error then
names that method.
The time source
Every time:: call a script makes (Time) reads an engine-owned clock
the host can replace. It lives on eng->time(), in <jaiscript/core/engine_time.hpp>.
| Provider | Behind | Empty means |
|---|---|---|
| monotonic | time::now(), time::engine_elapsed(), time::execute_elapsed(), every stopwatch | steady_clock |
| wall (nanoseconds since the Unix epoch, UTC) | time::unix_time(), every date | system_clock |
| sleep | a sleep that blocks | this_thread::sleep_for |
A virtual clock is the whole integration for tests, replays and deterministic simulation:
std::chrono::nanoseconds sim_now{0};
const auto epoch = std::chrono::nanoseconds(1700000000000000000LL); // 2023-11-14T22:13:20Z
eng->time().source(
[&] { return sim_now; }, // monotonic
[&] { return epoch + sim_now; }, // wall
[&](std::chrono::nanoseconds d) { sim_now += d; }); // sleep advances the simulation
sim_now += std::chrono::milliseconds(1500);
eng->execute("print(time::now(), ' ', time::utc_now().to_string());");1.5 2023-11-14T22:13:21.500Z- Reads are countable, so a provider that replays recorded readings sees the same sequence on every
run of a program. Each
time::call reads the provider it needs once; constructing a stopwatch or a date reads nothing. Beyond those, the engine reads the monotonic provider once per host entry (anexecute, a jaibite run, or a function or coroutine the host calls while no script runs), and only after a loaded program namestime::execute_elapsed. Nested executes, includes and imports belong to their entry. - The budget ignores the source. The execution budget and the import check window run on the real steady clock, so a virtual clock neither stalls nor trips them.
- Zero is the host's.
time::engine_elapsed()counts from engine construction on the system clock; on a host monotonic provider it is the provider's reading. - The host reads the same clock through
eng->time().monotonic_now(),wall_now(),sleep_for(duration),eng->engine_elapsed()andexecute_elapsed().eng->time().source({}, {}, {})restores the system clocks. - Providers run on the engine's thread only: a parallel body cannot call
time::.
Suspendable runs
execute() runs a program to its end. A program that waits (a cutscene, an AI routine, a level's waves)
runs as a run: eng->start(...) runs it to its first
sleep and returns a jai::script_run
(<jaiscript/core/script_run.hpp>), which the host resumes when the time has passed.
std::chrono::nanoseconds now{0};
eng->time().source([&] { return now; }, {}, {});
jai::script_run run = eng->start(R"(
for (int wave = 1; wave <= 3; ++wave) {
print("wave {} at {}", wave, time::now());
sleep(2.0);
}
"cleared";
)");
while (!run.done()) {
now = run.wake_at(); // a game moves its clock each frame and resumes; this jumps to the wake time
run.resume();
}
std::cout << run.result().value().to_string() << "\n";wave 1 at 0.0
wave 2 at 2.0
wave 3 at 4.0
clearedA run also has state() and cancel(), and its destructor cancels a run still suspended. Every call: script_run calls.
- Failure. A run from source, a file or a jaibite fails as
execute()does: thestartorresume()that ends it throws, and the run is then failed with the text inresult(). A callable's run reports throughresult()alone. - Runs are independent. Several can wait at once and resume in any order, between the host's own executes and calls; each keeps its frames, budget and debugger step. A host function a run calls may start a run of its own.
- Each resume is a host entry with a fresh execution budget, memory allowance and step budget; time
asleep never counts.
time::execute_elapsed()counts from the start of the run. - A run suspends only where script alone stands between the host and the
sleep. Asleepreached through native code (a host function's callback, a destructor, a signal receiver, an include or import's file,hot_reload_migrate, a builtin's callback) blocks on the sleep provider, and the run reports one warning throughset_script_warning_handler. Underexecute()and a host's call of a function value,sleepalways blocks. - Native stacks. Each run has a native stack of its own, reserved as deep as the starting thread's, so deep recursion stops at the same depth. Once warm,
startallocates nothing. - Teardown. Destroying the engine cancels every suspended run; a
script_runthat outlives its engine stays done. - The debugger. A breakpoint stops a run like any script. A suspended run is not a stop, and a step
over a
sleepends at the run's next statement once it resumes.
Resuming on another thread, from inside the run, or through a moved-from script_run throws jai::runtime_error: script_run misuse.
Parallel worker count
A parallel region (Parallel Loops) runs on at most
eng->parallel_thread_count() threads, the calling thread among them.
eng->parallel_thread_count(n) sets an exact count and 0 restores the default. Results never depend on it.
The default is jai::thread_pool::performance_core_count(), capped at
std::thread::hardware_concurrency() - 1 and at least 1: a region waits for its slowest chunk, so it stays
off the slow cores.
performance_core_count() counts the physical cores of the fastest class the platform reports, once: the highest efficiency class on Windows, hw.perflevel0.physicalcpu on Apple platforms, the CPUs an Intel hybrid chip lists in /sys/devices/cpu_core/cpus on Linux (every online CPU on other Linux machines), and std::thread::hardware_concurrency() anywhere else or when the query fails.
The engine builds its pool with jai::thread_pool pool{count, jai::thread_pool::fast_threads}: each worker
asks the system never to throttle it to save power (EcoQoS on Windows, user-initiated QoS on Apple platforms).
The HTTP transport
The spider:: client (spider::catch,
sling, get, post, spider::Response) is opt-in and sends nothing by
itself: the host supplies the HTTP stack. Three calls make a working client:
#include <jaiscript/stdlib/spider.hpp>
#include <jaiscript/net/http_socket_transport.hpp> // or your own jai::http_transport
jai::stdlib::register_spider(*eng); // the functions, spider::Response, the "network" permission
eng->http_transport(std::make_shared<jai::net::http_socket_transport>());
eng->enable("network"); // registered disabled: the host grants itWithout a transport a call raises spider::catch: no HTTP transport installed: the host provides one with
engine::http_transport(); without the permission, Insufficient privilege: network.
A transport implements one function of jai::http_transport
(<jaiscript/core/http_transport.hpp>): http_reply exchange(const http_request&).
- The request carries the method, an absolute http or https URL, the headers, the body and the seconds left for the call.
- The reply is the status, every header line as it arrived and the decoded body, or an
errortext."timed out"is reported astimed out after N s. An exception out ofexchangeis reported like an error text. - A transport follows no redirect. The library applies options, redirect policy and error texts above it, so every transport behaves alike.
exchangeruns on the script's thread and blocks it until the answer is complete. One transport may serve many engines on many threads when itsexchangetakes concurrent calls, as the two below do.
Two transports ship as opt-in headers that nothing in the library includes:
<jaiscript/net/http_socket_transport.hpp> provides jai::net::http_socket_transport, plain HTTP/1.1 over sockets unless its constructor is given a jai::net::tls_layer. <jaiscript/net/openssl_tls.hpp> provides jai::net::openssl_tls, that layer on OpenSSL. Versions, trust setup and the plain-HTTP error: HTTP transport headers.
The runner and jai_spider make one transport per process: WinHTTP on Windows (the system's TLS and certificate
store), sockets with openssl_tls on Linux and macOS when the build found OpenSSL, and plain sockets
otherwise, where an https URL raises the plain-HTTP error. JAISCRIPT_HTTP_TRANSPORT=socket in the
environment makes them use the socket transport on Windows too (plain HTTP only there).