Introduction
The tcp_server plugin is a complete callback-based TCP package: an
Open call opens a loopback TCP server (incoming connections are
dispatched to a callback), and the SAME plugin can also act as a client
— it can send a request to its own (or another) listener via
RequestText/RequestBlob (synchronous) or
BeginTextRequest/BeginBlobRequest (asynchronous) calls. The plugin
supports the full async_meta vocabulary (Capabilities = 4095 =
ProfileRouteService), and every job-level query has two names: the
native Request* and the async_meta-compatible Job* synonym.
Server + client in one
This plugin is both a TCP server AND a TCP client. Open(callback, timeoutMs) opens a loopback TCP port and dispatches the RAW bytes of
every incoming connection to the callback on a background thread. The
callback signature is callback i32 NAME(ref blob $Request) (see the
extended forms below for the peer's address and certificate); the
callback may modify the request blob in place, and the modified bytes
become the TCP response. Meanwhile, RequestText(listenerId, text, timeoutMs) synchronously and BeginTextRequest(listenerId, text, timeoutMs) asynchronously open a TCP connection to the listener's port,
send the text/blob, and return the response (or an asynchronous
requestId).
Peer metadata: the extended callback forms
The handler may declare extra parameters, and the host fills them in with the peer's identity. Three forms are accepted; pick the shortest one that covers what you need:
callback i32 NAME(ref blob $Request)— payload only.callback i32 NAME(ref blob $Request, string $PeerHost, i32 $PeerPort)— plus the client's address and port.callback i32 NAME(ref blob $Request, string $PeerHost, i32 $PeerPort, string $PeerCertSubject)— plus the client certificate's subject when the connection is mTLS-verified. It is an empty string when there is no TLS, or when an optional-mode client presented no certificate.
The same forms work on the TCP, HTTP and UDP callback families, so a handler moves between transports unchanged.
What is the client side for?
The RequestText / Begin*Request families exist primarily for
TESTING: you call your own server with them to confirm the callback
handles requests correctly. The loopback TCP channel has minimal
overhead and works like a locally simulated incoming connection. They
can also reach other loopback servers (e.g. another
tcp_server listener, or another local TCP service).
Relationship to async_meta
The plugin is fully built on the async_meta constants.
Capabilities() returns exactly the async_meta.ProfileRouteService()
bitmask (=4095) — the entire vocabulary supported. RouteState returns
async_meta.RouteStateOpen (=1) / RouteStateClosed (=0);
RouteMeta(listenerId, key) knows the async_meta.RouteMeta* keys;
JobMeta(requestId, key) knows the async_meta.JobMeta* keys.
Request* / Job* synonym pairs
Job-level (i.e., asynchronous request) query functions all exist
under two names. Request* is the native, descriptive name (paints a
“request”, HTTP-like image); Job* is the async_meta-vocabulary name.
The two names map to the EXACT same handler — pick stylistically.
Examples: RequestPhase ↔︎ Phase, RequestStatus ↔︎ Status,
RequestError ↔︎ ErrorText, RequestStatusText ↔︎ StatusText,
RequestInfo ↔︎ JobInfo, RequestRoute ↔︎ JobRoute, RequestTimeout
↔︎ JobTimeout, RequestKnown ↔︎ JobKnown, RequestMeta ↔︎ JobMeta,
RequestHasBlobResult ↔︎ JobHasBlobResult, RequestBlobResultSize ↔︎
JobBlobResultSize.
Optional TLS layer
EnableTls(listenerId, certFile, keyFile) activates TLS on the
listener: every subsequent incoming connection is wrapped in TLS by the
tls plugin's provider. The listener itself does NOT link OpenSSL — that
is resolved at runtime via the tls plugin. On a platform that doesn't
support TLS (e.g. Windows pre-bundle), EnableTls returns a clean error
and the listener keeps serving plaintext. TLS activation is IDEMPOTENT:
calling it again with the same files just returns 0.
Sync vs async request
RequestText(listenerId, text, timeoutMs) and
RequestBlob(listenerId, blob, timeoutMs) are SYNCHRONOUS: the call
BLOCKS until the TCP connection is established, the request is sent, and
the response arrives. BeginTextRequest/BeginBlobRequest are
ASYNCHRONOUS: they return immediately with a requestId, and a background
thread handles the connect/send/wait. Async jobs are managed with
WaitComplete(requestId, timeoutMs), TakeTextResponse(requestId), and
TakeBlob(requestId).
Loading the plugin
plugin "builtin:print"; plugin "../plugins/async_meta/AsyncMetaPlugin"; plugin "../plugins/tcp_server/TcpServerPlugin";
A typical flow — synchronous echo on loopback
callback i32 Echo(ref blob $Request) { // the callback may modify the request bytes in place; // these become the TCP response bytes return(1); } i32 main() { i64 $lid; string $resp[64]; $lid = tcp_server.Open("Echo", 5000); $resp = tcp_server.RequestText($lid, "PING", 5000); // ... process the response ... tcp_server.Close($lid); return(0); }
A typical flow — asynchronous request with WaitComplete + TakeTextResponse
i64 $lid; i64 $jobId; string $resp[64]; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); // ... the script does other things ... tcp_server.WaitComplete($jobId, 5000); if (tcp_server.Status($jobId) == 0) { $resp = tcp_server.TakeTextResponse($jobId); // ... processing ... } tcp_server.Close($lid);
What to know about every function (the basics)
Capabilities is always ProfileRouteService = 4095 (every async_meta bit).
Open binds to loopback (127.0.0.1) on an OS-picked port (query with Port).
The
Request*andJob*families are SYNONYMS — exactly the same handler. Stylistic choice for code style.RequestText/RequestBlob are synchronous; BeginTextRequest/BeginBlobRequest are asynchronous.
WaitComplete takes a requestId (NOT listenerId), and does NOT destroy the request.
TakeTextResponse and TakeBlob destroy the request; the requestId is invalid afterward.
EnableTls is idempotent — calling it again with the same files just returns 0.
RouteClose and Close map to the same handler: close the listener, return the canceled queued requests count.
RouteCloseAccepted is a LATCH — once it's 1 it never resets.
An error (stopping the script) is caused by a wrong argument count or type, an out-of-range timeoutMs, an invalid listenerId/requestId, a closed plugin, TCP connect failure, and embedded NUL byte in a text response.
How to read the signatures
callbackName is the script-level callback name (callback i32 NAME(ref blob $X)). timeoutMs is the callback run-time (Open) or
request I/O (Request*, Begin*) max in ms; -1 = infinite. listenerId is
the positive id given by Open. requestId is the positive id given by
Begin*. metaKey is one of the async_meta.RouteMeta* or JobMeta* values.
The type after the -> arrow is the return type.
Lifecycle and TLS
Opening the listener on a loopback TCP port, optional TLS activation, and plugin shutdown.
tcp_server.Capabilities
tcp_server.Capabilities() -> intReturns the plugin's capability bitmask matching the async_meta constants.
This function takes no arguments.
async_meta.ProfileRouteService (=4095): every async_meta bit (the entire vocabulary supported).
printf("%d\n", tcp_server.Capabilities()); printf("%d\n", (tcp_server.Capabilities() == async_meta.ProfileRouteService()));
4095 1
For backend capability checking with the Supports* helpers. tcp_server supports the FULL vocabulary (like bus) — the Supports* helpers return 1 for everything.
tcp_server.Open
tcp_server.Open(callbackName, timeoutMs) -> intOpens a TCP listener on loopback IP and dispatches every incoming connection to the callback via a background thread.
| Parameter | Type | Description |
|---|---|---|
| callbackName | string | The script-level callback name to call. Signature: callback i32 NAME(ref blob $X). |
| timeoutMs | int | The callback run-time limit in ms. -1 = infinite; otherwise 0..2147483647. |
The listenerId (positive i64). A bad argument, bind failure, or thread startup failure raises a runtime error.
callback i32 Echo(ref blob $X) { return(1); } i64 $lid; $lid = tcp_server.Open("Echo", 5000); printf("%d\n", ($lid > 0));
1
For starting a local TCP service (debug server, local RPC, tested protocol). The port is OS-picked; query it with Port($lid). The callback receives RAW TCP bytes — any text or binary protocol is freely implementable.
tcp_server.SetClientCa
tcp_server.SetClientCa(listenerId, caFile, mode) -> 0
Turns on mTLS for this listener: requires (mode 2) or accepts (mode 1) a client certificate that chains to caFile on every TLS connection it accepts. The listener's mTLS shape is therefore Open + SetClientCa + EnableTls. With mTLS on, the 4-parameter callback form receives the verified client certificate's subject.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id returned by Open. |
| caFile | string | Path to the CA bundle (PEM) that client certificates are verified against. |
| mode | int | 1 = optional (accept a certificate if offered), 2 = required (the handshake fails without a valid one). |
Returns 0 on success; raises a runtime error on a bad argument, an unknown listener, a missing mTLS-capable TLS provider, or when TLS is already enabled.
Call it BEFORE EnableTls / OpenTls. Calling it afterwards is a runtime error by design — otherwise connections accepted in between would run unverified, leaving an unchecked window.
tcp_server.EnableTls
tcp_server.EnableTls(listenerId, certFile, keyFile) -> intActivates TLS on the listener: every subsequent connection is wrapped in TLS by the tls plugin's provider.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
| certFile | string | The server certificate path (PEM). |
| keyFile | string | The private key path (PEM). |
0 (success). Idempotent: calling it again with the same files just returns 0. If TLS is not supported on the platform, a clean runtime error.
i64 $lid; $lid = tcp_server.Open("Echo", 5000); // tcp_server.EnableTls($lid, "server.crt", "server.key"); print.Line($"ok");
ok
For a secure local service with a TLS layer. The listener itself does not link OpenSSL — it relies on the tls plugin's provider at runtime. The probe didn't activate TLS (we only document the verb's presence).
tcp_server.OpenCount
tcp_server.OpenCount() -> intReturns the count of currently open listeners (plugin-wide, global view).
This function takes no arguments.
The number of open listeners (0 if none).
printf("%d\n", tcp_server.OpenCount()); i64 $lid; $lid = tcp_server.Open("Echo", 5000); printf("%d\n", tcp_server.OpenCount());
0 1
For resource control and diagnostics. If it does not return to 0 after Close calls, that signals a resource leak.
tcp_server.Close
tcp_server.Close(listenerId) -> intCloses the given listener: stops the accept thread, shuts down the socket, releases resources.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id from Open. |
The count of queued requests canceled by the close. Typically 0 (clean close with nothing pending).
i64 $lid; $lid = tcp_server.Open("Echo", 5000); printf("%d\n", tcp_server.Close($lid));
0
For clean listener shutdown. Close waits for in-progress callbacks to finish. If you called RouteClose first, Close returns 0 (listener no longer active).
Listener-level state
Current state of the listener: bound port, “open/closed” state, callback name, timeout, full diagnostic text. Route* names are equivalent to the State*/Info names.
tcp_server.Port
tcp_server.Port(listenerId) -> intReturns the listener's actual, bound TCP port.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
The port (1..65535).
i64 $lid; $lid = tcp_server.Open("Echo", 5000); printf("%d\n", (tcp_server.Port($lid) > 0));
1
For querying the OS-picked port. RequestText/Blob and BeginTextRequest/BeginBlobRequest use this port automatically via the listenerId.
tcp_server.State
tcp_server.State(listenerId) -> intReturns the listener's state using the async_meta route-state constants.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
async_meta.RouteStateOpen (=1) or RouteStateClosed (=0).
i64 $lid; $lid = tcp_server.Open("Echo", 5000); printf("%d\n", tcp_server.State($lid));
1
For programmatic state checking. Shared vocabulary with the callback_* family.
tcp_server.RouteState
tcp_server.RouteState(listenerId) -> intSynonym of State — under the async_meta vocabulary.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
async_meta.RouteStateOpen (=1) or RouteStateClosed (=0).
i64 $lid; $lid = tcp_server.Open("Echo", 5000); printf("%d\n", tcp_server.RouteState($lid));
1
For async_meta-compatible code.
tcp_server.StateText
tcp_server.StateText(listenerId) -> string
The state as text.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
“open” or “closed”.
i64 $lid; $lid = tcp_server.Open("Echo", 5000); print.Line(tcp_server.StateText($lid));
open
For logging or UI display.
tcp_server.RouteStateText
tcp_server.RouteStateText(listenerId) -> string
Synonym of StateText.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
“open” or “closed”.
i64 $lid; $lid = tcp_server.Open("Echo", 5000); print.Line(tcp_server.RouteStateText($lid));
open
For logging, under the async_meta vocabulary.
tcp_server.RouteCallback
tcp_server.RouteCallback(listenerId) -> string
Returns the callback name passed at Open.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
The callback name as text.
i64 $lid; $lid = tcp_server.Open("Echo", 5000); print.Line(tcp_server.RouteCallback($lid));
Echo
For reflective queries and diagnostics.
tcp_server.RouteTimeout
tcp_server.RouteTimeout(listenerId) -> intReturns the timeout value passed at Open, in ms.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
The timeoutMs value; -1 for infinite.
i64 $lid; $lid = tcp_server.Open("Echo", 5000); printf("%d\n", tcp_server.RouteTimeout($lid));
5000
Reflective query of listener configuration.
tcp_server.RouteKnown
tcp_server.RouteKnown(listenerId) -> intTells whether the listenerId is known to the plugin.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
1 if known (open); 0 if not.
i64 $lid; $lid = tcp_server.Open("Echo", 5000); printf("%d\n", tcp_server.RouteKnown($lid));
1
Defensive check.
tcp_server.RouteMeta
tcp_server.RouteMeta(listenerId, metaKey) -> intReturns a specific meta field of the listener by async_meta.RouteMeta* keys.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
| metaKey | int | An async_meta.RouteMeta* value (State, Timeout, Pending, Queued, Running, Drained, Known, CloseAccepted, ClosedQueued). |
The value for the field.
i64 $lid; $lid = tcp_server.Open("Echo", 5000); printf("%d\n", tcp_server.RouteMeta($lid, async_meta.RouteMetaState())); printf("%d\n", tcp_server.RouteMeta($lid, async_meta.RouteMetaDrained()));
1 1
Uniform async_meta query — for portable multi-backend code.
tcp_server.Info
tcp_server.Info(listenerId) -> string
Returns the listener's aggregated key=value diagnostic state as a string.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
A space-separated key=value list.
i64 $lid; $lid = tcp_server.Open("Echo", 5000); printf("%d\n", (str.Len(tcp_server.Info($lid)) > 0));
1
Detailed diagnostics in a single call.
tcp_server.RouteInfo
tcp_server.RouteInfo(listenerId) -> string
Synonym of Info — key=value diagnostic state.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
A space-separated key=value list.
i64 $lid; $lid = tcp_server.Open("Echo", 5000); printf("%d\n", (str.Len(tcp_server.RouteInfo($lid)) > 0));
1
For logging, under the async_meta vocabulary.
Listener-level traffic
Listener traffic metrics: active, pending, queued, running connections; drain state and blocking wait. Route* names are synonyms here too.
tcp_server.Pending
tcp_server.Pending(listenerId) -> intReturns the number of pending (not yet started) requests.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
The number of pending requests.
i64 $lid; $lid = tcp_server.Open("Echo", 5000); printf("%d\n", tcp_server.Pending($lid));
0
Early signal of congestion.
tcp_server.RoutePending
tcp_server.RoutePending(listenerId) -> intSynonym of Pending.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
The number of pending requests.
i64 $lid; $lid = tcp_server.Open("Echo", 5000); printf("%d\n", tcp_server.RoutePending($lid));
0
Under the async_meta vocabulary.
tcp_server.Queued
tcp_server.Queued(listenerId) -> intReturns the number of queued requests.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
The number of queued requests.
i64 $lid; $lid = tcp_server.Open("Echo", 5000); printf("%d\n", tcp_server.Queued($lid));
0
For monitoring the internal queue length.
tcp_server.RouteQueued
tcp_server.RouteQueued(listenerId) -> intSynonym of Queued.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
The number of queued requests.
i64 $lid; $lid = tcp_server.Open("Echo", 5000); printf("%d\n", tcp_server.RouteQueued($lid));
0
Under the async_meta vocabulary.
tcp_server.Running
tcp_server.Running(listenerId) -> intReturns the number of currently running requests.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
The number of running requests.
i64 $lid; $lid = tcp_server.Open("Echo", 5000); printf("%d\n", tcp_server.Running($lid));
0
For monitoring parallelism.
tcp_server.RouteRunning
tcp_server.RouteRunning(listenerId) -> intSynonym of Running.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
The number of running requests.
i64 $lid; $lid = tcp_server.Open("Echo", 5000); printf("%d\n", tcp_server.RouteRunning($lid));
0
Under the async_meta vocabulary.
tcp_server.Active
tcp_server.Active(listenerId) -> intReturns the number of currently active connections.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
The number of active connections.
i64 $lid; $lid = tcp_server.Open("Echo", 5000); printf("%d\n", tcp_server.Active($lid));
0
For monitoring current server load.
tcp_server.Drained
tcp_server.Drained(listenerId) -> intTells whether the listener is drained (pending = queued = running = active = 0).
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
1 if drained; 0 if something is in progress.
i64 $lid; $lid = tcp_server.Open("Echo", 5000); printf("%d\n", tcp_server.Drained($lid));
1
Before clean shutdown: ensure Drained=1 before calling Close.
tcp_server.RouteDrained
tcp_server.RouteDrained(listenerId) -> intSynonym of Drained.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
1 if drained; 0 if not.
i64 $lid; $lid = tcp_server.Open("Echo", 5000); printf("%d\n", tcp_server.RouteDrained($lid));
1
Under the async_meta vocabulary.
tcp_server.WaitDrained
tcp_server.WaitDrained(listenerId, timeoutMs) -> intBlocks until the listener is drained, or timeoutMs expires.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
| timeoutMs | int | Max wait in ms; -1 = infinite. |
1 if drain occurred before timeoutMs expired; 0 if it expired.
i64 $lid; $lid = tcp_server.Open("Echo", 5000); printf("%d\n", tcp_server.WaitDrained($lid, 50));
1
In the two-step shutdown pattern: AFTER RouteClose, wait for drain.
tcp_server.WaitRouteDrained
tcp_server.WaitRouteDrained(listenerId, timeoutMs) -> intSynonym of WaitDrained.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
| timeoutMs | int | Max wait in ms. |
1 if drain occurred; 0 if expired.
i64 $lid; $lid = tcp_server.Open("Echo", 5000); printf("%d\n", tcp_server.WaitRouteDrained($lid, 50));
1
Under the async_meta vocabulary.
Request sending (client side)
Sending a TCP request to a listener's port (typically to test your own listener). Request* is synchronous, Begin* is asynchronous.
tcp_server.RequestText
tcp_server.RequestText(listenerId, text, timeoutMs) -> string
Sends a synchronous TCP request to the listener's port and returns the response text.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The target listener id. |
| text | string | The text to send (UTF-8 or ASCII; the response MUST NOT contain NUL bytes). |
| timeoutMs | int | Max time for the entire connection (connect + send + receive) in ms. |
The response text. If the response contains a NUL byte, a runtime error.
i64 $lid; string $resp[64]; $lid = tcp_server.Open("Echo", 5000); $resp = tcp_server.RequestText($lid, "PING", 5000); printf("%d\n", str.Len($resp));
0
For testing your own callback: send a specific request and check the response. A simple, synchronous pattern — if you need async, use BeginTextRequest.
tcp_server.RequestBlob
tcp_server.RequestBlob(listenerId, request, timeoutMs) -> blob
Sends a synchronous TCP request with a blob to the listener's port and returns the response blob.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The target listener id. |
| request | blob | The binary data to send. |
| timeoutMs | int | Max time for the entire connection in ms. |
The response blob (the callback-modified bytes). May be empty if the callback didn't write anything.
i64 $lid; blob $req[3]; blob $resp[16]; $req[0] = 65; $req[1] = 66; $req[2] = 67; $lid = tcp_server.Open("Echo", 5000); $resp = tcp_server.RequestBlob($lid, $req, 5000); printf("%d\n", $resp.Length);
0
For binary protocols. NUL bytes are allowed (only RequestText restricts them).
tcp_server.BeginTextRequest
tcp_server.BeginTextRequest(listenerId, text, timeoutMs) ->
intStarts an asynchronous TCP request: the background thread connects, sends, and waits — the call returns immediately with a requestId.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The target listener id. |
| text | string | The text to send. |
| timeoutMs | int | Max I/O time in ms. |
The requestId (positive i64).
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); printf("%d\n", ($jobId > 0));
1
For an async testing pattern: start a request, do other things, then WaitComplete + TakeTextResponse.
tcp_server.BeginBlobRequest
tcp_server.BeginBlobRequest(listenerId, request, timeoutMs) ->
intStarts an asynchronous TCP request with a blob payload.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The target listener id. |
| request | blob | The binary data to send. |
| timeoutMs | int | Max I/O time in ms. |
The requestId (positive i64).
i64 $lid; i64 $jobId; blob $req[3]; $req[0] = 65; $req[1] = 66; $req[2] = 67; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginBlobRequest($lid, $req, 5000); printf("%d\n", ($jobId > 0));
1
For async binary requests. Take the response with TakeBlob after WaitComplete.
Wait and response take
Waiting for an async request to complete and taking the response (text or blob).
tcp_server.WaitComplete
tcp_server.WaitComplete(requestId, timeoutMs) -> intBlocks until the request completes, but does NOT destroy it — status and result are queryable afterward.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId from Begin*. |
| timeoutMs | int | Max wait in ms; -1 = infinite. |
1 if the request completed; 0 if the timeout expired.
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); printf("%d\n", tcp_server.WaitComplete($jobId, 5000));
1
First step of the async two-step pattern: wait for completion, verify success via Status / RequestStatus, then TakeTextResponse / TakeBlob the response.
tcp_server.TakeTextResponse
tcp_server.TakeTextResponse(requestId) -> string
Returns the text response of a completed request, and destroys the request.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
The response text. If the request has not completed, a runtime error. If it contains a NUL byte, a runtime error.
i64 $lid; i64 $jobId; string $resp[64]; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); tcp_server.WaitComplete($jobId, 5000); $resp = tcp_server.TakeTextResponse($jobId); printf("%d\n", str.Len($resp));
0
For a text response. If the callback didn't write anything, the response is an empty string. For binary, use TakeBlob.
tcp_server.TakeBlob
tcp_server.TakeBlob(requestId) -> blob
Returns the blob response of a completed request, and destroys the request.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
The response blob.
i64 $lid; i64 $jobId; blob $req[3]; blob $resp[16]; $req[0] = 65; $req[1] = 66; $req[2] = 67; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginBlobRequest($lid, $req, 5000); tcp_server.WaitComplete($jobId, 5000); $resp = tcp_server.TakeBlob($jobId); printf("%d\n", $resp.Length);
0
For a binary response (may contain NUL bytes). The callback may modify the request bytes in place — these become the response.
Request-level state and metadata
Current state (phase, status, error) and metadata of an async request. Request* and Job* names are SYNONYMS — exactly the same handler, just stylistically interchangeable.
tcp_server.RequestPhase
tcp_server.RequestPhase(requestId) -> intReturns the request phase from the async_meta.Phase* constants.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
async_meta.PhaseQueued (=0), PhaseRunning (=1), or PhaseCompleted (=2).
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); tcp_server.WaitComplete($jobId, 5000); printf("%d\n", tcp_server.RequestPhase($jobId));
2
For programmatic phase checks. Synonym of Phase.
tcp_server.Phase
tcp_server.Phase(requestId) -> intSynonym of RequestPhase — under the async_meta vocabulary.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
async_meta.PhaseQueued / PhaseRunning / PhaseCompleted.
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); tcp_server.WaitComplete($jobId, 5000); printf("%d\n", tcp_server.Phase($jobId));
2
For async_meta-compatible code.
tcp_server.RequestPhaseText
tcp_server.RequestPhaseText(requestId) -> string
The phase as text.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
“queued”, “running”, or “completed”.
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); tcp_server.WaitComplete($jobId, 5000); print.Line(tcp_server.RequestPhaseText($jobId));
completed
For logging or UI display.
tcp_server.PhaseText
tcp_server.PhaseText(requestId) -> string
Synonym of RequestPhaseText.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
“queued”, “running”, or “completed”.
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); tcp_server.WaitComplete($jobId, 5000); print.Line(tcp_server.PhaseText($jobId));
completed
Under the async_meta vocabulary.
tcp_server.RequestStatus
tcp_server.RequestStatus(requestId) -> intReturns the request status: 0 = success, non-zero = error.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
0 if successful; non-zero is the error code.
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); tcp_server.WaitComplete($jobId, 5000); printf("%d\n", tcp_server.RequestStatus($jobId));
0
Worth checking first after WaitComplete.
tcp_server.Status
tcp_server.Status(requestId) -> intSynonym of RequestStatus.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
0 or error code.
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); tcp_server.WaitComplete($jobId, 5000); printf("%d\n", tcp_server.Status($jobId));
0
Under the async_meta vocabulary.
tcp_server.RequestError
tcp_server.RequestError(requestId) -> string
Returns the request error message (empty on success).
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
The error message; empty string on success.
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); tcp_server.WaitComplete($jobId, 5000); printf("%d\n", str.Len(tcp_server.RequestError($jobId)));
0
If Status is non-0, check this for the cause.
tcp_server.ErrorText
tcp_server.ErrorText(requestId) -> string
Synonym of RequestError.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
The error message.
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); tcp_server.WaitComplete($jobId, 5000); printf("%d\n", str.Len(tcp_server.ErrorText($jobId)));
0
Under the async_meta vocabulary.
tcp_server.RequestStatusText
tcp_server.RequestStatusText(requestId) -> string
The textual counterpart of the status.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
“success” on a successful request, other text depending on the error type.
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); tcp_server.WaitComplete($jobId, 5000); print.Line(tcp_server.RequestStatusText($jobId));
success
For logging or user feedback.
tcp_server.StatusText
tcp_server.StatusText(requestId) -> string
Synonym of RequestStatusText.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
“success” or error text.
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); tcp_server.WaitComplete($jobId, 5000); print.Line(tcp_server.StatusText($jobId));
success
Under the async_meta vocabulary.
tcp_server.RequestInfo
tcp_server.RequestInfo(requestId) -> string
Returns the request state as a key=value diagnostic string.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
A space-separated key=value list.
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); tcp_server.WaitComplete($jobId, 5000); printf("%d\n", (str.Len(tcp_server.RequestInfo($jobId)) > 0));
1
Detailed request diagnostics in a single call.
tcp_server.JobInfo
tcp_server.JobInfo(requestId) -> string
Synonym of RequestInfo.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
A key=value diagnostic string.
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); tcp_server.WaitComplete($jobId, 5000); printf("%d\n", (str.Len(tcp_server.JobInfo($jobId)) > 0));
1
Under the async_meta vocabulary.
tcp_server.RequestRoute
tcp_server.RequestRoute(requestId) -> intReturns the id of the listener the request belongs to.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
The listenerId.
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); printf("%d\n", (tcp_server.RequestRoute($jobId) == $lid));
1
Reflective query of the request-owner relation.
tcp_server.JobRoute
tcp_server.JobRoute(requestId) -> intSynonym of RequestRoute.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
The listenerId.
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); printf("%d\n", (tcp_server.JobRoute($jobId) == $lid));
1
Under the async_meta vocabulary.
tcp_server.RequestTimeout
tcp_server.RequestTimeout(requestId) -> intReturns the request's configured timeout in ms (from Begin*).
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
The timeoutMs value.
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); printf("%d\n", tcp_server.RequestTimeout($jobId));
5000
For logging or configuration checks.
tcp_server.JobTimeout
tcp_server.JobTimeout(requestId) -> intSynonym of RequestTimeout.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
The timeoutMs value.
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); printf("%d\n", tcp_server.JobTimeout($jobId));
5000
Under the async_meta vocabulary.
tcp_server.RequestKnown
tcp_server.RequestKnown(requestId) -> intTells whether the requestId is known to the plugin.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
1 if known; 0 if not (e.g. already destroyed by Take).
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); printf("%d\n", tcp_server.RequestKnown($jobId));
1
Defensive check.
tcp_server.JobKnown
tcp_server.JobKnown(requestId) -> intSynonym of RequestKnown.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
1 if known; 0 if not.
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); printf("%d\n", tcp_server.JobKnown($jobId));
1
Under the async_meta vocabulary.
tcp_server.RequestMeta
tcp_server.RequestMeta(requestId, metaKey) -> intReturns a meta field of the request by async_meta.JobMeta* keys.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
| metaKey | int | An async_meta.JobMeta* value (Phase, Status, Route, Timeout, Known, HasBlobResult, BlobResultSize). |
The value for the field.
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); tcp_server.WaitComplete($jobId, 5000); printf("%d\n", tcp_server.RequestMeta($jobId, async_meta.JobMetaPhase()));
2
Uniform async_meta query on the request state.
tcp_server.JobMeta
tcp_server.JobMeta(requestId, metaKey) -> intSynonym of RequestMeta.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
| metaKey | int | An async_meta.JobMeta* value. |
The value for the field.
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); tcp_server.WaitComplete($jobId, 5000); printf("%d\n", tcp_server.JobMeta($jobId, async_meta.JobMetaPhase()));
2
Under the async_meta vocabulary — for portable multi-backend code.
tcp_server.RequestHasBlobResult
tcp_server.RequestHasBlobResult(requestId) -> intTells whether the request has a blob result.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
1 if yes; 0 if no.
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); tcp_server.WaitComplete($jobId, 5000); printf("%d\n", tcp_server.RequestHasBlobResult($jobId));
1
Take precondition: if 0, TakeBlob raises a runtime error.
tcp_server.JobHasBlobResult
tcp_server.JobHasBlobResult(requestId) -> intSynonym of RequestHasBlobResult.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
1 if yes; 0 if no.
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); tcp_server.WaitComplete($jobId, 5000); printf("%d\n", tcp_server.JobHasBlobResult($jobId));
1
Under the async_meta vocabulary.
tcp_server.RequestBlobResultSize
tcp_server.RequestBlobResultSize(requestId) -> intReturns the response blob size in bytes.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
The blob size in bytes.
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); tcp_server.WaitComplete($jobId, 5000); printf("%d\n", tcp_server.RequestBlobResultSize($jobId));
0
For memory pre-allocation: you know how big a blob to allocate for TakeBlob.
tcp_server.JobBlobResultSize
tcp_server.JobBlobResultSize(requestId) -> intSynonym of RequestBlobResultSize.
| Parameter | Type | Description |
|---|---|---|
| requestId | int | The requestId. |
The blob size in bytes.
i64 $lid; i64 $jobId; $lid = tcp_server.Open("Echo", 5000); $jobId = tcp_server.BeginTextRequest($lid, "HELLO", 5000); tcp_server.WaitComplete($jobId, 5000); printf("%d\n", tcp_server.JobBlobResultSize($jobId));
0
Under the async_meta vocabulary.
Listener shutdown
Diagnostics of the listener (route) shutdown process and explicit shutdown. RouteClose and Close do the same thing under different names.
tcp_server.RouteCloseAccepted
tcp_server.RouteCloseAccepted(listenerId) -> intTells whether a close request has been accepted on the listener.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
1 if yes; 0 if no RouteClose / Close has happened yet.
i64 $lid; $lid = tcp_server.Open("Echo", 5000); tcp_server.RouteClose($lid); printf("%d\n", tcp_server.RouteCloseAccepted($lid));
1
LATCH: once 1, it never resets. Useful for diagnostics.
tcp_server.RouteClosedQueued
tcp_server.RouteClosedQueued(listenerId) -> intReturns the count of queued requests canceled by the most recent RouteClose / Close.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
The count of canceled queued requests.
i64 $lid; $lid = tcp_server.Open("Echo", 5000); tcp_server.RouteClose($lid); printf("%d\n", tcp_server.RouteClosedQueued($lid));
0
For diagnostics: how many requests were lost during shutdown.
tcp_server.RouteClose
tcp_server.RouteClose(listenerId) -> intSynonym of Close — exact same handler.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
The count of canceled queued requests (0 = clean close).
i64 $lid; $lid = tcp_server.Open("Echo", 5000); printf("%d\n", tcp_server.RouteClose($lid));
0
Under the async_meta vocabulary. Equivalent to Close — stylistic choice.
tcp_server.RouteCloseInfo
tcp_server.RouteCloseInfo(listenerId) -> string
Returns the listener shutdown process as a key=value diagnostic string.
| Parameter | Type | Description |
|---|---|---|
| listenerId | int | The listener id. |
A space-separated key=value list.
i64 $lid; $lid = tcp_server.Open("Echo", 5000); printf("%d\n", (str.Len(tcp_server.RouteCloseInfo($lid)) > 0));
1
Detailed shutdown diagnostics in a single call.
Practical notes
What the tcp_server plugin is good for
Starting a local TCP service on a loopback port (debug server, local RPC, tested protocol).
Event-driven processing: every incoming TCP connection is a callback invocation; the callback gets RAW bytes and can implement any protocol.
Built-in client side for testing your own server:
RequestText/RequestBlobare synchronous,BeginTextRequest/BeginBlobRequestare asynchronous — both open a loopback TCP connection.Optional TLS layer with EnableTls: incoming connections are TLS-wrapped, the callback receives the decoded bytes unchanged.
async_meta compatibility with the full vocabulary: Phase, Status, JobMeta, RouteMeta, WaitDrained, RouteClose — every bit supported (Capabilities = 4095).
Server + client in one — the testing pattern
A key feature of the plugin is that a complete TCP exchange is reachable from one script: Open opens a server, and RequestText immediately calls the server itself. This is ideal for testing a callback implementation: you can invoke your own logic through a real TCP channel, ensuring the endpoints communicate properly. A minimal test: open a listener → send RequestText → check the response → close.
Request* vs Job* — synonym pairs
Every job-level (= queryable per async request) function has two
names: the native Request* (paints the HTTP/RPC request image) and the
async_meta-vocabulary Job*. The two forms are EXACTLY the same handler
— pick stylistically. If your code works with other async_meta-compatible
backends, Job* is more consistent; if only tcp_server, Request* is
more expressive.
Sync vs async request
RequestText/RequestBlob are SYNCHRONOUS: the call blocks until
the TCP connection + send + receive finishes. Simple, but the script
does nothing else meanwhile. BeginTextRequest/BeginBlobRequest are
ASYNCHRONOUS: they return immediately with a requestId, the background
thread handles I/O. Then WaitComplete + TakeTextResponse / TakeBlob
finish and collect. For high-throughput or parallel testing, the async
form is recommended.
TLS activation
EnableTls(listenerId, certFile, keyFile) is IDEMPOTENT: calling it
again with the same files just returns 0. The TLS layer runs through the
tls plugin's provider — resolved at runtime, so the tcp_server plugin
itself does not link OpenSSL. If the platform doesn't support TLS (e.g.
a Windows pre-bundle build), EnableTls returns a clean error and the
listener keeps serving plaintext.
Callback signature
The callback signature is callback i32 NAME(ref blob $Request). The
incoming $Request blob contains the RAW bytes received over the TCP
connection — the plugin does not interpret HTTP, does not look for
protocol framing. The callback may modify the blob bytes in place, and
the modified content becomes the TCP response. If the callback writes
nothing, the response is empty (as in the probe — the Echo callback
returns 1 without modifying the blob).
Listener shutdown
RouteClose(listenerId) and Close(listenerId) are the SAME handler
— two names, identical behavior. The return value is NOT a 1/0 success
flag, but the COUNT of queued requests canceled by the close. Typically
0 (clean close with no pending requests). The two-step shutdown pattern:
RouteClose → WaitDrained → forced cleanup (if needed). A single Close
performs all steps.
Error handling
An invalid listenerId, requestId, wrong argument count or type,
out-of-range timeoutMs, touching an already-closed listener or plugin
are reported by the runtime as a runtime error, and the script stops. A
TCP connect failure (e.g. connecting to a closed listener's port) gives
an error code in the Status field; RequestError returns the error
text. TakeTextResponse raises a runtime error if the response contains
an embedded NUL byte — use TakeBlob instead.