On this page
  1. tecs.io.http
  2. Module contents
    1. Constructors
    2. Types
    3. Functions
  3. Constructors
    1. newClient
  4. Types
    1. Client
    2. ClientOptions
    3. plugin
    4. Request
    5. Response
  5. Functions
    1. getOpenClientCount

tecs.io.http

Cooperative HTTP and HTTPS.

local client <const> = tecs.io.http.newClient({userAgent = "mygame/1.0"})
local response <const> = client:send({
    url = tecs.io.URI.new("https://example.com/manifest.json"),
})
print(response.status)
print(assert(response.body:readAll(1024 * 1024)))

send returns when status and headers exist. Inside a system it suspends only until that boundary; outside an update it blocks its caller. The body is a one-shot streaming Reader backed by bounded native chunks, so a slow consumer applies transport backpressure instead of accumulating the full response. Arbitrary streaming request bodies run in client-owned cooperative work, so their Reader may itself wait on a socket, process, transform, or HTTP response without blocking the SDL pump. On the SDL storage backend, a file stream is opened and read directly by Tokio instead. It stays bounded without retaining the complete file or crossing Lua for every chunk.

An HTTP error status still returns a response. DNS, connection, TLS, timeout, or streaming failures raise at the suspended call.

Raw tecs.io sockets use the process-wide mio readiness reactor. HTTP deliberately uses Reqwest and Tokio for its connection pool, TLS, redirects, and protocol work. Both implementations resume the same direct Lua call, and neither parks one worker thread per waiting socket.

A request holds one of its client's maxConnections slots until its response body ends, so close or discard a body you do not intend to read.

Call Client:close when you no longer need its connection pool. Losing the last Lua reference does not cancel its requests. Application shutdown closes any remaining clients and drains their internal upload tasks to settlement, within a bounded number of scheduler steps that it logs whenever it has to abandon work. An Application owns progress; a direct headless call drives the client while it blocks.

Module contents

Constructors

Constructor Description
newClient Builds a client.

Types

Type Kind Description
Client interface A Client owns one connection pool and its active requests.
ClientOptions record Settings copied when an HTTP client is built.
plugin record Read-only. plugin exposes requests and responses as ECS components.
Request record Request describes one request and requires only url.
Response record Response describes status, headers, and a progressive response body.

Functions

Function Kind Description
getOpenClientCount Static Returns the open-client count.

Constructors

tecs.io.http.newClient Static

Builds a client.

function tecs.io.http.newClient(options: ClientOptions): Client

Arguments

Name Type Description
options ClientOptions The caller supplies client defaults or omits this record to use every default. Each insecureHosts entry logs a warning.

Returns

Type Description
Client Returns an open client that remains active until close.

Examples

local http <const> = tecs.io.http
local endpoint, reason = tecs.io.URI.new("https://example.com/status")
if endpoint == nil then
    error(reason)
end
local client <const> = http.newClient({
    userAgent = "mygame/1.0",
    timeoutMs = 10000,
    maxBytes = 1024 * 1024,
})
local response <const> = client:send({url = endpoint})
print(response.status, response.url:host())
response.body:close()
client:close()

Types

tecs.io.http.Client interface

A Client owns one connection pool and its active requests.

Closing the client cancels every pending request, releases the pool, and remains safe to repeat. The Application drives active requests.

interface tecs.io.http.Client is Closeable
    pending: function(self): integer
    send: function(self, request: Request): Response
end

Interfaces

Interface
Closeable

tecs.io.http.Client:pending Instance

Returns the number of unsettled requests.

function tecs.io.http.Client.pending(self): integer
Arguments
Name Type Description
self Client The client to inspect.
Returns
Type Description
integer Returns zero after every request settles or is canceled.

tecs.io.http.Client:send Instance

Sends one HTTP request and returns its response.

A transport failure raises. An HTTP error status returns normally with that status in the response. Inside a system, the call suspends the world update only while the transfer is pending.

function tecs.io.http.Client.send(self, request: Request): Response
Arguments
Name Type Description
self Client The open client that owns the request.
request Request The caller supplies the URL and optional method, headers, upload body, timeouts, and byte limit.
Returns
Type Description
Response Returns status, headers, and a one-shot streaming body.

tecs.io.http.ClientOptions record

Settings copied when an HTTP client is built.

userAgent and headers set request defaults. timeoutMs defaults to 30000, connectTimeoutMs to 10000, and stallTimeoutMs to zero, which disables the no-progress timeout. maxRedirects defaults to five, maxConnections to 16, and maxConnectionsPerHost to six; a request holds one slot of each until its response body ends. maxBytes defaults to zero for no response limit. compressed defaults to true.

insecureHosts disables certificate verification for named hosts and logs a warning for each. proxy accepts the textual spelling of an absolute URI, follows environment variables when nil, and forces a direct connection when empty. noProxy uses Reqwest's exclusion syntax, and proxyCredentials uses user:password.

record tecs.io.http.ClientOptions
    userAgent: string
    headers: {string: string}
    timeoutMs: number
    connectTimeoutMs: number
    stallTimeoutMs: number
    maxRedirects: integer
    maxConnections: integer
    maxConnectionsPerHost: integer
    maxBytes: integer
    compressed: boolean
    insecureHosts: {string}
    proxy: string
    noProxy: string
    proxyCredentials: string
end

tecs.io.http.ClientOptions.userAgent field

Caller-writable. Sets User-Agent on every request.

tecs.io.http.ClientOptions.userAgent: string

tecs.io.http.ClientOptions.headers field

Caller-writable. Sets headers sent on every request. Per-request headers override these without regard to case.

tecs.io.http.ClientOptions.headers: {string: string}

tecs.io.http.ClientOptions.timeoutMs field

Caller-writable. Sets positive milliseconds allowed for a whole transfer. Defaults to 30000.

tecs.io.http.ClientOptions.timeoutMs: number

tecs.io.http.ClientOptions.connectTimeoutMs field

Caller-writable. Sets positive milliseconds allowed to establish a connection. Defaults to 10000.

tecs.io.http.ClientOptions.connectTimeoutMs: number

tecs.io.http.ClientOptions.stallTimeoutMs field

Caller-writable. Sets milliseconds a response may make no progress. Zero disables this timeout.

tecs.io.http.ClientOptions.stallTimeoutMs: number

tecs.io.http.ClientOptions.maxRedirects field

Caller-writable. Sets redirects followed. Defaults to five; zero follows none.

tecs.io.http.ClientOptions.maxRedirects: integer

tecs.io.http.ClientOptions.maxConnections field

Caller-writable. Sets requests allowed to use sockets at once. Defaults to 16.

A request holds its slot from the moment it acquires one until its response body ends, because the socket stays open for that whole time and this limit is what bounds open sockets. A caller that reads a body slowly therefore keeps its slot, and a caller that never reads one keeps it until timeoutMs expires. Close or discard a body you do not intend to read, and raise this limit rather than expecting an unread body to release its slot early.

tecs.io.http.ClientOptions.maxConnections: integer

tecs.io.http.ClientOptions.maxConnectionsPerHost field

Caller-writable. Sets requests allowed to use sockets to one host. Defaults to six. A request holds its host slot for as long as it holds a maxConnections slot.

tecs.io.http.ClientOptions.maxConnectionsPerHost: integer

tecs.io.http.ClientOptions.maxBytes field

Caller-writable. Sets maximum response-body bytes. Zero is unbounded.

tecs.io.http.ClientOptions.maxBytes: integer

tecs.io.http.ClientOptions.compressed field

Caller-writable. Accepts gzip and deflate response compression when true. Defaults to true.

tecs.io.http.ClientOptions.compressed: boolean

tecs.io.http.ClientOptions.insecureHosts field

Caller-writable. Lists host names whose TLS certificates are not verified.

tecs.io.http.ClientOptions.insecureHosts: {string}

tecs.io.http.ClientOptions.proxy field

Caller-writable. Sets the textual spelling of an absolute proxy URI. Nil follows the environment; an empty string forces a direct connection. This option accepts a string, not a URI object.

tecs.io.http.ClientOptions.proxy: string

tecs.io.http.ClientOptions.noProxy field

Caller-writable. Lists hosts excluded from an explicitly configured proxy.

tecs.io.http.ClientOptions.noProxy: string

tecs.io.http.ClientOptions.proxyCredentials field

Caller-writable. Sets proxy credentials in user:password form.

tecs.io.http.ClientOptions.proxyCredentials: string

tecs.io.http.plugin record

Read-only. plugin exposes requests and responses as ECS components.

record tecs.io.http.plugin
    record Request is Component
        url: URI
        method: string
        headers: {string: string}
        body: string | ReadableStream
        timeoutMs: number
        stallTimeoutMs: number
        maxBytes: integer
    end

    record Response is Component
        status: integer
        headers: {string: string}
        body: Stream
        url: URI
        error: string
    end

    record Pending is Component
    end

    clientOf: function(World): Client
    close: function(World)
    install: function(World, Options)
end

tecs.io.http.plugin.Request record

What a game spawns to make a request. The fields of a Request, because there is no second vocabulary for the same thing.

record tecs.io.http.plugin.Request is Component
    url: URI
    method: string
    headers: {string: string}
    body: string | ReadableStream
    timeoutMs: number
    stallTimeoutMs: number
    maxBytes: integer
end
Interfaces
Interface
Component

tecs.io.http.plugin.Request.url field

Caller-writable. Sets an absolute HTTP or HTTPS URI.

tecs.io.http.plugin.Request.url: URI

tecs.io.http.plugin.Request.method field

Caller-writable. Sets the method. Defaults to "GET".

tecs.io.http.plugin.Request.method: string

tecs.io.http.plugin.Request.headers field

Caller-writable. Sets headers merged over the client's defaults without regard to case. A Content-Length must contain decimal digits and must match a body whose length is known.

tecs.io.http.plugin.Request.headers: {string: string}

tecs.io.http.plugin.Request.body field

Caller-writable. Sets what to send. A snapshot rejects a tecs.io.newHandleStream here because its live handle cannot be reconstructed.

tecs.io.http.plugin.Request.body: string | ReadableStream

tecs.io.http.plugin.Request.timeoutMs field

Caller-writable. Sets milliseconds for this transfer, overriding the client's value.

tecs.io.http.plugin.Request.timeoutMs: number

tecs.io.http.plugin.Request.stallTimeoutMs field

Caller-writable. Sets tolerated milliseconds without progress, overriding the client's value.

tecs.io.http.plugin.Request.stallTimeoutMs: number

tecs.io.http.plugin.Request.maxBytes field

Caller-writable. Sets the body byte limit, overriding the client's value.

tecs.io.http.plugin.Request.maxBytes: integer

tecs.io.http.plugin.Response record

What replaces a Request once the transfer settles.

Present whatever happened, because "the request finished" is the event a system waits for and a failure is one of the ways it can finish. error is what says which.

record tecs.io.http.plugin.Response is Component
    status: integer
    headers: {string: string}
    body: Stream
    url: URI
    error: string
end
Interfaces
Interface
Component

tecs.io.http.plugin.Response.status field

Engine-owned. Reports the HTTP status code, or zero when the transfer never received one.

tecs.io.http.plugin.Response.status: integer

tecs.io.http.plugin.Response.headers field

Engine-owned. Reports response headers with lower-cased names, in the joined form Response.headers uses. A component is one value per name, so a repeated set-cookie keeps only its first value here; a system that needs every cookie sends its request through a client and reads Response:getAll.

tecs.io.http.plugin.Response.headers: {string: string}

tecs.io.http.plugin.Response.body field

Engine-owned. Provides a one-shot progressive response body. Despawning this entity closes a body that remains unread.

tecs.io.http.plugin.Response.body: Stream

tecs.io.http.plugin.Response.url field

Engine-owned. Reports the URI that actually answered, or nil when the request failed before it supplied a valid URI.

tecs.io.http.plugin.Response.url: URI

tecs.io.http.plugin.Response.error field

Engine-owned. Reports why the transfer did not complete, or nil when it did. A 404 is not one of these: it is a status of 404 and no error.

tecs.io.http.plugin.Response.error: string

tecs.io.http.plugin.Pending record

On an entity whose request is in flight. Internal, and the reason a request is sent once rather than every frame.

A marker, while the callback and transfer state remain in the plugin. It is transient because runtime transport state cannot be saved. A save taken mid-request keeps the Request, drops this marker, and sends the request again after loading.

record tecs.io.http.plugin.Pending is Component
end
Interfaces
Interface
Component

tecs.io.http.plugin.clientOf Static

The world's client, or nil when the plugin is not installed.

function tecs.io.http.plugin.clientOf(World): Client
Arguments
Name Type Description
#1 World
Returns
Type Description
Client

tecs.io.http.plugin.close Static

Closes the world's client and forgets it.

function tecs.io.http.plugin.close(World)
Arguments
Name Type Description
#1 World
Returns

None.

tecs.io.http.plugin.install Static

Installs the plugin: world:addPlugin(tecs.io.http.plugin.install).

Takes options because the plugin builds the world's client, and a game that wants a userAgent on it has nowhere else to say so: world:addPlugin(function(w) http.plugin.install(w, {...}) end).

function tecs.io.http.plugin.install(World, Options)
Arguments
Name Type Description
#1 World
#2 Options
Returns

None.

tecs.io.http.Request record

Request describes one request and requires only url.

record tecs.io.http.Request
    url: URI
    method: string
    headers: {string: string}
    body: string | iotypes.ReadableStream
    timeoutMs: number
    stallTimeoutMs: number
    maxBytes: integer
end

tecs.io.http.Request.url field

Caller-writable. Sets an absolute URL with an http or https scheme.

tecs.io.http.Request.url: URI

tecs.io.http.Request.method field

Caller-writable. Sets the method. Defaults to GET.

tecs.io.http.Request.method: string

tecs.io.http.Request.headers field

Caller-writable. Sets headers merged over the client's defaults. A Content-Length must contain decimal digits and must match a body whose length is known.

tecs.io.http.Request.headers: {string: string}

tecs.io.http.Request.body field

Caller-writable. Sets bytes to send. A general stream opens one reader and feeds a bounded upload queue. On the SDL storage backend, a file stream is opened and read directly by the native HTTP lane. The client owns cooperative work that waits on a composed reader.

tecs.io.http.Request.body: string | iotypes.ReadableStream

tecs.io.http.Request.timeoutMs field

Caller-writable. Overrides the client's positive whole-transfer timeout.

tecs.io.http.Request.timeoutMs: number

tecs.io.http.Request.stallTimeoutMs field

Caller-writable. Overrides the client's no-progress timeout.

tecs.io.http.Request.stallTimeoutMs: number

tecs.io.http.Request.maxBytes field

Caller-writable. Overrides the client's body limit. Zero is unbounded.

tecs.io.http.Request.maxBytes: integer

tecs.io.http.Response record

Response describes status, headers, and a progressive response body. headers joins a repeated header's values, and Response:getAll reports them one by one, which is what set-cookie needs.

record tecs.io.http.Response
    status: integer
    headers: {string: string}
    body: iotypes.Stream
    url: URI

    getAll: function(self, name: string): {string}
    ok: function(self): boolean
end

tecs.io.http.Response.status field

Read-only. Reports the status after redirects.

tecs.io.http.Response.status: integer

tecs.io.http.Response.headers field

Read-only. Reports final response headers with lower-cased names. A name the server sent more than once holds its values joined with ", ", which RFC 9110 permits for every field except Set-Cookie. A repeated set-cookie holds only the first value here, because joining cookies changes what they mean. Response:getAll reports every value of any name.

tecs.io.http.Response.headers: {string: string}

tecs.io.http.Response.body field

Read-only. Provides a one-shot progressive response body. Reading may suspend a system or block a direct caller until bytes arrive. The caller must consume, discard, or close this owned stream.

tecs.io.http.Response.body: iotypes.Stream

tecs.io.http.Response.url field

Read-only. Reports the effective URL after redirects.

tecs.io.http.Response.url: URI

tecs.io.http.Response:getAll Instance

Returns every value the server sent for one header name.

This is the accessor for a repeated header, and the only correct way to read set-cookie, which cannot be joined.

function tecs.io.http.Response.getAll(self, name: string): {string}
Arguments
Name Type Description
self Response The response to read.
name string The caller supplies a header name, and the lookup ignores case.
Returns
Type Description
{string} Returns a new array in the order the server sent the values, or an empty array when the response has no such header. The array belongs to the caller.

tecs.io.http.Response:ok Instance

Whether the status is in the 2xx range.

function tecs.io.http.Response.ok(self): boolean
Arguments
Name Type Description
self Response
Returns
Type Description
boolean

Functions

tecs.io.http.getOpenClientCount Static

Returns the open-client count.

function tecs.io.http.getOpenClientCount(): integer

Arguments

None.

Returns

Type Description
integer Returns the process-wide count of clients not yet closed.