neyuki 0.5.0

A small Lua-like scripting language implemented in Rust
# @neyuki/net

Long-lived connections: local pipes and Unix sockets, TCP sockets and a WebSocket client. Each connection has a reader thread that fills a buffer, so reading never blocks the interpreter. Inside a task, `read`, `receive`, `write` and the connect functions suspend only that task while they wait (see `task`); in the main chunk they block, like `http.request`. Binary data is passed as a `buffer`, so framing can be done with the `buffer` read and write functions. The streams of a child process started with `os.spawn` are the same kind of connection.

```lua
local net = require("@neyuki/net")

local conn = net.connect("127.0.0.1", 6463)
conn:write("hello\n")
local reply = conn:read(5)
print(buffer.tostring(reply))
conn:close()
```

## net.connect(host: string, port: number, timeout: number?): Connection

Opens a TCP connection. `timeout` is how many seconds to wait for it (default 10). Raises when the host cannot be resolved or the connection is refused.

## net.connectPipe(name: string, timeout: number?): Connection

Opens a local pipe. On Windows `name` is a named pipe, `\.\pipe\<name>` (a name that already starts with `\` is used as given); elsewhere it is the path of a Unix domain socket. `timeout` is how many seconds to wait for a busy Windows pipe (default 10).

```lua
local path = os.getenv("XDG_RUNTIME_DIR") or ""
local conn = net.connectPipe(os.getenv("OS") and "discord-ipc-0" or path .. "/discord-ipc-0")
```

## net.connectUnix(path: string): Connection

Opens the Unix domain socket at `path`. Raises on Windows, where `connectPipe` is the equivalent.

## net.websocket(url: string, options: table?): WebSocket

Opens a WebSocket client connection to a `ws://` or `wss://` URL (TLS through the operating system's library). `options` may hold `headers`, a `{ name = value }` table sent with the upgrade request, and `timeout` in seconds (default 10). Pings are answered automatically.

## net.spawn(command: string | {string}, options: table?): Process

The same as `os.spawn`.

## conn:read(n: number): buffer?

Returns a buffer of exactly `n` bytes, waiting for them to arrive. If the peer hangs up first, returns what arrived, and nil once nothing is left. Raises when the connection broke or was closed. `n` may be at most 64 MiB.

```lua
local header = conn:read(8)
if header == nil then
    return -- the peer hung up
end
local opcode = buffer.readu32(header, 0)
local length = buffer.readu32(header, 4)
```

## conn:readAvailable(): buffer

Whatever has arrived and was not read yet, without waiting. The buffer is empty when nothing has arrived; check `atEof` to tell a quiet connection from a closed one.

## conn:write(data: string | buffer): number

Sends all of `data` and returns how many bytes that was. Raises when the peer is gone, and on a stream that cannot be written to (the output of a child process).

## conn:atEof(): boolean

True once the peer has hung up and everything it sent has been read.

## conn:isOpen(): boolean

False after `close` and once the peer has hung up. To reconnect when a service restarts, check this (or watch for nil from `read`), then connect again.

## conn:close()

Closes the connection; closing twice is fine. Closing the `stdin` of a child process tells it that input is over.

## ws:send(data: string | buffer): number

Sends a text message for a string and a binary message for a buffer; returns the payload size.

## ws:receive(): string | buffer, string

Waits for the next message and returns it with its kind: a string and `"text"`, or a buffer and `"binary"`. Returns nil once the connection has closed and every message was read. Raises when the connection broke.

```lua
local ws = net.websocket("ws://127.0.0.1:9222/devtools/page/1")
ws:send('{"id":1,"method":"Page.enable"}')
local message, kind = ws:receive()
print(kind, message)
ws:close()
```

## ws:isOpen(): boolean

False after `close` and once the server has closed the connection.

## ws:close()

Sends a close frame and shuts the connection down.