# tecs.io.http Asynchronous HTTP and HTTPS. ```teal local endpoint = tecs.io.URI.new( "https://example.com/manifest.json" ) tecs.io.http.newClient({userAgent = "mygame/1.0"}) :send({url = endpoint}) :map(function(response: tecs.io.http.Response): string local body = response.body if body is tecs.io.ReadableStream then return body:readAll() end error("the response destination is not readable") end) :onSettle(function(manifest: tecs.Future) print(manifest.value) end) ``` The process runtime turns every open client once per iteration, so requests advance without game code pumping them. An HTTP error status still produces a ready response. A failed future means that DNS, connection, TLS, timeout, or streaming produced no response. Call `Client:close` when you no longer need its connection pool. Losing the last Lua reference does not cancel its requests. A headless tool calls [`tecs.runtime.poll`](/modules/runtime/#tecs.runtime.poll); [`Application`](/modules/Application/) calls it once per frame. ## 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 the futures using it. | | [`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 what a completed transfer settles to regardless of status code. | ### 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}).value print(response.status, response.url:host()) response.body:close() client:close() ``` ## Types ### tecs.io.http.Client interface A `Client` owns one connection pool and the futures using it. Closing the client cancels every pending request, releases the pool, and remains safe to repeat. A client also drives its futures as their source, but an application or `tecs.runtime.poll` normally performs that work rather than game code calling `poll` or `advance` directly. ```teal interface tecs.io.http.Client is Closeable sliceMs: number defaultWaitMs: number advance: function(self, ms: number): integer cancel: function(self, future: Future) pending: function(self): integer poll: function(self): integer pump: function(self): integer send: function(self, request: Request): Future end ``` #### Interfaces | Interface | | --- | | [`Closeable`](/modules/#tecs.Closeable) | #### tecs.io.http.Client.sliceMs field Engine-owned. Sets the maximum duration of one blocking future wait slice. Ordinary game code should ignore it. ```teal tecs.io.http.Client.sliceMs: number ``` #### tecs.io.http.Client.defaultWaitMs field Engine-owned. Sets the timeout used when `Future:wait` receives no explicit timeout. Ordinary game code should ignore it. ```teal tecs.io.http.Client.defaultWaitMs: number ``` #### tecs.io.http.Client:advance Instance Waits briefly for an event and drains the events then ready. `Future:wait` calls this through its source. Application code normally does not call it directly. ```teal function tecs.io.http.Client.advance(self, ms: number): integer ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Client` | The open client to advance. | | `ms` | `number` | The caller supplies the maximum milliseconds to wait. | ##### Returns | Type | Description | | --- | --- | | `integer` | Returns the number of response events processed. | #### tecs.io.http.Client:cancel Instance Cancels the request belonging to a future. A settled future or a future from another source changes nothing. ```teal function tecs.io.http.Client.cancel(self, future: Future) ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Client` | The client that owns the request. | | `future` | [`Future`](/modules/Future/)`` | The caller supplies the request future to cancel. | ##### Returns None. #### 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:poll Instance Polls this client as a future source without blocking. Application code normally lets the process runtime call this. ```teal function tecs.io.http.Client.poll(self): integer ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Client` | The open client to poll. | ##### Returns | Type | Description | | --- | --- | | `integer` | Returns the number of response events processed. | #### tecs.io.http.Client:pump Instance Drains every response event currently ready without blocking. Application code normally lets the process runtime call this. ```teal function tecs.io.http.Client.pump(self): integer ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Client` | The open client to advance. | ##### Returns | Type | Description | | --- | --- | | `integer` | Returns the number of response events processed. | #### tecs.io.http.Client:send Instance Starts one HTTP transfer and returns immediately. A transport failure settles the future as failed. An HTTP error status settles it as ready with that status in the response. ```teal function tecs.io.http.Client.send( self, request: Request ): Future ``` ##### Arguments | Name | Type | Description | | --- | --- | --- | | `self` | `Client` | The open client that owns the request. | | `request` | `Request` | The caller supplies the URL and optional method, headers, body, destination, timeouts, and byte limit. | ##### Returns | Type | Description | | --- | --- | | [`Future`](/modules/Future/)`` | Returns a future settled by the process runtime. | ### 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. `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 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 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. ```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. ```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 into: string | WritableStream 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 into: string | WritableStream 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.into field Caller-writable. Sets where the body goes. Left out, the response carries a replayable in-memory [`Stream`](/modules/io/#tecs.io.Stream). A snapshot rejects a `tecs.io.newHandleStream` here because its live handle cannot be reconstructed. ```teal tecs.io.http.plugin.Request.into: string | WritableStream ``` ##### 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. ```teal tecs.io.http.plugin.Response.headers: {string: string} ``` ##### tecs.io.http.plugin.Response.body field Engine-owned. Provides the body wherever it went. ```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, and the future it stands for is not a field on it: a future holds listeners and the source that settles them, so nothing a save writes can carry one. Transient for the same reason, which is what makes a save taken mid-request coherent: the `Request` survives, this does not, and the load sends it again. ```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 into: string | iotypes.WritableStream 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 stream opens one reader when this request begins and feeds it through a bounded upload queue. ```teal tecs.io.http.Request.body: string | iotypes.ReadableStream ``` #### tecs.io.http.Request.into field Caller-writable. Sets where response chunks are written. A string is a file path. ```teal tecs.io.http.Request.into: string | iotypes.WritableStream ``` #### tecs.io.http.Request.timeoutMs field Caller-writable. Overrides the client's 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 what a completed transfer settles to regardless of status code. ```teal record tecs.io.http.Response status: integer headers: {string: string} body: iotypes.Stream url: URI 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. ```teal tecs.io.http.Response.headers: {string: string} ``` #### tecs.io.http.Response.body field Read-only. Provides response bytes in memory or in the requested destination. ```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: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. |