# tecs.io.http Cooperative HTTP and HTTPS. ```teal local client = tecs.io.http.newClient({userAgent = "mygame/1.0"}) local response = 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`](/modules/Application/) owns progress; a direct headless call drives the client while it blocks. ## Module contents ### Constructors | Constructor | Description | | --- | --- | | [`newClient`](/modules/io/http/#tecs.io.http.newClient) | Builds a client. | ### Types | Type | Kind | Description | | --- | --- | --- | | [`Client`](/modules/io/http/#tecs.io.http.Client) | interface | A Client owns one connection pool and its active requests. | | [`ClientOptions`](/modules/io/http/#tecs.io.http.ClientOptions) | record | Settings copied when an HTTP client is built. | | [`plugin`](/modules/io/http/#tecs.io.http.plugin) | record | Read-only. plugin exposes requests and responses as ECS components. | | [`Request`](/modules/io/http/#tecs.io.http.Request) | record | Request describes one request and requires only url. | | [`Response`](/modules/io/http/#tecs.io.http.Response) | record | Response describes status, headers, and a progressive response body. | ### Functions | Function | Kind | Description | | --- | --- | --- | | [`getOpenClientCount`](/modules/io/http/#tecs.io.http.getOpenClientCount) | Static | Returns the open-client count. | ## Constructors ### tecs.io.http.newClient Static Builds a client. ```teal function tecs.io.http.newClient(options: ClientOptions): Client ``` #### Arguments | Name | Type | Description | | --- | --- | --- | | `options` | [`ClientOptions`](/modules/io/http/#tecs.io.http.ClientOptions) | The caller supplies client defaults or omits this record to use every default. Each `insecureHosts` entry logs a warning. | #### Returns | Type | Description | | --- | --- | | [`Client`](/modules/io/http/#tecs.io.http.Client) | Returns an open client that remains active until `close`. | #### Examples ```teal local http = tecs.io.http local endpoint, reason = tecs.io.URI.new("https://example.com/status") if endpoint == nil then error(reason) end local client = http.newClient({ userAgent = "mygame/1.0", timeoutMs = 10000, maxBytes = 1024 * 1024, }) local response = 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. ```teal interface tecs.io.http.Client is Closeable pending: function(self): integer send: function(self, request: Request): Response end ``` #### Interfaces | Interface | | --- | | [`Closeable`](/modules/#tecs.Closeable) | #### tecs.io.http.Client:pending Instance Returns the number of unsettled requests. ```teal 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. ```teal 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`](/modules/io/URI/), follows environment variables when nil, and forces a direct connection when empty. `noProxy` uses Reqwest's exclusion syntax, and `proxyCredentials` uses `user:password`. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal tecs.io.http.ClientOptions.stallTimeoutMs: number ``` #### tecs.io.http.ClientOptions.maxRedirects field Caller-writable. Sets redirects followed. Defaults to five; zero follows none. ```teal 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. ```teal 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. ```teal tecs.io.http.ClientOptions.maxConnectionsPerHost: integer ``` #### tecs.io.http.ClientOptions.maxBytes field Caller-writable. Sets maximum response-body bytes. Zero is unbounded. ```teal 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. ```teal tecs.io.http.ClientOptions.compressed: boolean ``` #### tecs.io.http.ClientOptions.insecureHosts field Caller-writable. Lists host names whose TLS certificates are not verified. ```teal tecs.io.http.ClientOptions.insecureHosts: {string} ``` #### tecs.io.http.ClientOptions.proxy field Caller-writable. Sets the textual spelling of an absolute proxy [`URI`](/modules/io/URI/). Nil follows the environment; an empty string forces a direct connection. This option accepts a string, not a `URI` object. ```teal tecs.io.http.ClientOptions.proxy: string ``` #### tecs.io.http.ClientOptions.noProxy field Caller-writable. Lists hosts excluded from an explicitly configured proxy. ```teal tecs.io.http.ClientOptions.noProxy: string ``` #### tecs.io.http.ClientOptions.proxyCredentials field Caller-writable. Sets proxy credentials in `user:password` form. ```teal tecs.io.http.ClientOptions.proxyCredentials: string ``` ### tecs.io.http.plugin record Read-only. `plugin` exposes requests and responses as ECS components. ```teal 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. ```teal 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`](/modules/ecs/#tecs.ecs.Component) | ##### tecs.io.http.plugin.Request.url field Caller-writable. Sets an absolute HTTP or HTTPS [`URI`](/modules/io/URI/). ```teal tecs.io.http.plugin.Request.url: URI ``` ##### tecs.io.http.plugin.Request.method field Caller-writable. Sets the method. Defaults to `"GET"`. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal record tecs.io.http.plugin.Response is Component status: integer headers: {string: string} body: Stream url: URI error: string end ``` ##### Interfaces | Interface | | --- | | [`Component`](/modules/ecs/#tecs.ecs.Component) | ##### tecs.io.http.plugin.Response.status field Engine-owned. Reports the HTTP status code, or zero when the transfer never received one. ```teal 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`. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal record tecs.io.http.plugin.Pending is Component end ``` ##### Interfaces | Interface | | --- | | [`Component`](/modules/ecs/#tecs.ecs.Component) | #### tecs.io.http.plugin.clientOf Static The world's client, or nil when the plugin is not installed. ```teal function tecs.io.http.plugin.clientOf(World): Client ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `#1` | [`World`](/modules/ecs/#tecs.World) | | ##### Returns | Type | Description | | --- | --- | | `Client` | | #### tecs.io.http.plugin.close Static Closes the world's client and forgets it. ```teal function tecs.io.http.plugin.close(World) ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `#1` | [`World`](/modules/ecs/#tecs.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)`. ```teal function tecs.io.http.plugin.install(World, Options) ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `#1` | [`World`](/modules/ecs/#tecs.World) | | | `#2` | `Options` | | ##### Returns None. ### tecs.io.http.Request record `Request` describes one request and requires only `url`. ```teal 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. ```teal tecs.io.http.Request.url: URI ``` #### tecs.io.http.Request.method field Caller-writable. Sets the method. Defaults to GET. ```teal 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. ```teal 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. ```teal tecs.io.http.Request.body: string | iotypes.ReadableStream ``` #### tecs.io.http.Request.timeoutMs field Caller-writable. Overrides the client's positive whole-transfer timeout. ```teal tecs.io.http.Request.timeoutMs: number ``` #### tecs.io.http.Request.stallTimeoutMs field Caller-writable. Overrides the client's no-progress timeout. ```teal tecs.io.http.Request.stallTimeoutMs: number ``` #### tecs.io.http.Request.maxBytes field Caller-writable. Overrides the client's body limit. Zero is unbounded. ```teal 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. ```teal 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. ```teal 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. ```teal 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. ```teal tecs.io.http.Response.body: iotypes.Stream ``` #### tecs.io.http.Response.url field Read-only. Reports the effective URL after redirects. ```teal 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. ```teal 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. ```teal 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. ```teal function tecs.io.http.getOpenClientCount(): integer ``` #### Arguments None. #### Returns | Type | Description | | --- | --- | | `integer` | Returns the process-wide count of clients not yet closed. |