Add verified Ktor-backed networking modules to lyngio

This commit is contained in:
sergeych committed 2026-04-02 16:19:14 +03:00
1 parent f168e9f6ed
commit d409a4bb8b
35 files changed
+4025 -23

No files matched your search

+4 -1
View File
@@ -82,8 +82,11 @@ Requires installing `lyngio` into the import manager from host code.
- `import lyng.io.fs` (filesystem `Path` API)
- `import lyng.io.process` (process execution API)
- `import lyng.io.console` (console capabilities, geometry, ANSI/output, events)
- `import lyng.io.http` (HTTP/HTTPS client API)
- `import lyng.io.ws` (WebSocket client API; currently supported on JVM, capability-gated elsewhere)
- `import lyng.io.net` (TCP/UDP transport API; currently supported on JVM, capability-gated elsewhere)
## 7. AI Generation Tips
- Assume `lyng.stdlib` APIs exist in regular script contexts.
- For platform-sensitive code (`fs`, `process`, `console`), gate assumptions and mention required module install.
- For platform-sensitive code (`fs`, `process`, `console`, `http`, `ws`, `net`), gate assumptions and mention required module install.
- Prefer extension-method style (`items.filter { ... }`) and standard scope helpers (`let`/`also`/`apply`/`run`).
+169
View File
@@ -0,0 +1,169 @@
### lyng.io.http — HTTP/HTTPS client for Lyng scripts
This module provides a compact HTTP client API for Lyng scripts. It is implemented in `lyngio` and backed by Ktor on supported runtimes.
> **Note:** `lyngio` is a separate library module. It must be explicitly added as a dependency to your host application and initialized in your Lyng scopes.
---
#### Add the library to your project (Gradle)
If you use this repository as a multi-module project, add a dependency on `:lyngio`:
```kotlin
dependencies {
implementation("net.sergeych:lyngio:0.0.1-SNAPSHOT")
}
```
For external projects, ensure you also use the Lyng Maven repository described in `lyng.io.fs`.
---
#### Install the module into a Lyng session
The HTTP module is not installed automatically. Install it into the session scope and provide a policy.
Kotlin (host) bootstrap example:
```kotlin
import net.sergeych.lyng.EvalSession
import net.sergeych.lyng.Scope
import net.sergeych.lyng.io.http.createHttpModule
import net.sergeych.lyngio.http.security.PermitAllHttpAccessPolicy
suspend fun bootstrapHttp() {
val session = EvalSession()
val scope: Scope = session.getScope()
createHttpModule(PermitAllHttpAccessPolicy, scope)
session.eval("import lyng.io.http")
}
```
---
#### Using from Lyng scripts
Simple GET:
import lyng.io.http
val r = Http.get(HTTP_TEST_URL + "/hello")
[r.status, r.text()]
>>> [200,hello from test]
Headers and response header access:
import lyng.io.http
val r = Http.get(HTTP_TEST_URL + "/headers")
[r.headers["X-Reply"], r.headers.getAll("X-Reply").size, r.text()]
>>> [one,2,header demo]
Programmatic request object:
import lyng.io.http
val q = HttpRequest()
q.method = "POST"
q.url = HTTP_TEST_URL + "/echo"
q.headers = Map("Content-Type" => "text/plain")
q.bodyText = "ping"
val r = Http.request(q)
r.text()
>>> "POST:ping"
HTTPS GET:
import lyng.io.http
val r = Http.get(HTTPS_TEST_URL + "/hello")
[r.status, r.text()]
>>> [200,hello from test]
---
#### API reference
##### `Http` (static methods)
- `isSupported(): Bool` — Whether HTTP client support is available on the current runtime.
- `request(req: HttpRequest): HttpResponse` — Execute a request described by a mutable request object.
- `get(url: String, headers...): HttpResponse` — Convenience GET request.
- `post(url: String, bodyText: String = "", contentType: String? = null, headers...): HttpResponse` — Convenience text POST request.
- `postBytes(url: String, body: Buffer, contentType: String? = null, headers...): HttpResponse` — Convenience binary POST request.
For convenience methods, `headers...` accepts:
- `MapEntry`, e.g. `"Accept" => "text/plain"`
- 2-item lists, e.g. `["Accept", "text/plain"]`
##### `HttpRequest`
- `method: String`
- `url: String`
- `headers: Map<String, String>`
- `bodyText: String?`
- `bodyBytes: Buffer?`
- `timeoutMillis: Int?`
Only one of `bodyText` and `bodyBytes` should be set.
##### `HttpResponse`
- `status: Int`
- `statusText: String`
- `headers: HttpHeaders`
- `text(): String`
- `bytes(): Buffer`
Response body decoding is cached inside the response object.
##### `HttpHeaders`
`HttpHeaders` behaves like `Map<String, String>` for the first value of each header name and additionally exposes:
- `get(name: String): String?`
- `getAll(name: String): List<String>`
- `names(): List<String>`
Header lookup is case-insensitive.
---
#### Security policy
The module uses `HttpAccessPolicy` to authorize requests before they are sent.
- `HttpAccessPolicy` — interface for custom policies
- `PermitAllHttpAccessPolicy` — allows all requests
- `HttpAccessOp.Request(method, url)` — operation checked by the policy
Example restricted policy in Kotlin:
```kotlin
import net.sergeych.lyngio.fs.security.AccessContext
import net.sergeych.lyngio.fs.security.AccessDecision
import net.sergeych.lyngio.fs.security.Decision
import net.sergeych.lyngio.http.security.HttpAccessOp
import net.sergeych.lyngio.http.security.HttpAccessPolicy
val allowLocalOnly = object : HttpAccessPolicy {
override suspend fun check(op: HttpAccessOp, ctx: AccessContext): AccessDecision =
when (op) {
is HttpAccessOp.Request ->
if (op.url.startsWith("http://127.0.0.1:") || op.url.startsWith("http://localhost:"))
AccessDecision(Decision.Allow)
else
AccessDecision(Decision.Deny, "only local HTTP requests are allowed")
}
}
```
---
#### Platform support
- **JVM:** supported
- **Other targets:** implementation may be added later; use `Http.isSupported()` before relying on it
+164
View File
@@ -0,0 +1,164 @@
### lyng.io.net — TCP and UDP sockets for Lyng scripts
This module provides minimal raw transport networking for Lyng scripts. It is implemented in `lyngio` and currently backed by Ktor sockets on the JVM.
> **Note:** `lyngio` is a separate library module. It must be explicitly added as a dependency to your host application and initialized in your Lyng scopes.
---
#### Install the module into a Lyng session
Kotlin (host) bootstrap example:
```kotlin
import net.sergeych.lyng.EvalSession
import net.sergeych.lyng.Scope
import net.sergeych.lyng.io.net.createNetModule
import net.sergeych.lyngio.net.security.PermitAllNetAccessPolicy
suspend fun bootstrapNet() {
val session = EvalSession()
val scope: Scope = session.getScope()
createNetModule(PermitAllNetAccessPolicy, scope)
session.eval("import lyng.io.net")
}
```
---
#### Using from Lyng scripts
Capability checks and address resolution:
import lyng.io.net
val a: SocketAddress = Net.resolve("127.0.0.1", 4040)[0]
[Net.isSupported(), a.toString(), a.resolved, a.ipVersion == IpVersion.IPV4]
>>> [true,127.0.0.1:4040,true,true]
TCP client connect, write, read, and close:
import lyng.buffer
import lyng.io.net
val socket = Net.tcpConnect("127.0.0.1", NET_TEST_TCP_PORT)
socket.writeUtf8("ping")
socket.flush()
val reply = (socket.read(16) as Buffer).decodeUtf8()
socket.close()
reply
>>> "reply:ping"
Lyng TCP server socket operations with `tcpListen()` and `accept()`:
import lyng.buffer
import lyng.io.net
val server = Net.tcpListen(0, "127.0.0.1")
val port = server.localAddress().port
val accepted = launch {
val client = server.accept()
val line = (client.read(4) as Buffer).decodeUtf8()
client.writeUtf8("echo:" + line)
client.flush()
client.close()
server.close()
line
}
val socket = Net.tcpConnect("127.0.0.1", port)
socket.writeUtf8("ping")
socket.flush()
val reply = (socket.read(16) as Buffer).decodeUtf8()
socket.close()
[accepted.await(), reply]
>>> [ping,echo:ping]
UDP bind, send, receive, and inspect sender address:
import lyng.buffer
import lyng.io.net
val server = Net.udpBind(0, "127.0.0.1")
val client = Net.udpBind(0, "127.0.0.1")
client.send(Buffer("ping"), "127.0.0.1", server.localAddress().port)
val d = server.receive()
client.close()
server.close()
[d.data.decodeUtf8(), d.address.port > 0]
>>> [ping,true]
---
#### API reference
##### `Net` (static methods)
- `isSupported(): Bool` — Whether any raw networking support is available.
- `isTcpAvailable(): Bool` — Whether outbound TCP sockets are available.
- `isTcpServerAvailable(): Bool` — Whether listening TCP server sockets are available.
- `isUdpAvailable(): Bool` — Whether UDP datagram sockets are available.
- `resolve(host: String, port: Int): List<SocketAddress>` — Resolve a host and port into concrete addresses.
- `tcpConnect(host: String, port: Int, timeoutMillis: Int? = null, noDelay: Bool = true): TcpSocket` — Open an outbound TCP socket.
- `tcpListen(port: Int, host: String? = null, backlog: Int = 128, reuseAddress: Bool = true): TcpServer` — Start a listening TCP server socket.
- `udpBind(port: Int = 0, host: String? = null, reuseAddress: Bool = true): UdpSocket` — Bind a UDP socket.
##### `SocketAddress`
- `host: String`
- `port: Int`
- `ipVersion: IpVersion`
- `resolved: Bool`
- `toString(): String`
##### `TcpSocket`
- `isOpen(): Bool`
- `localAddress(): SocketAddress`
- `remoteAddress(): SocketAddress`
- `read(maxBytes: Int = 65536): Buffer?`
- `readLine(): String?`
- `write(data: Buffer): void`
- `writeUtf8(text: String): void`
- `flush(): void`
- `close(): void`
##### `TcpServer`
- `isOpen(): Bool`
- `localAddress(): SocketAddress`
- `accept(): TcpSocket`
- `close(): void`
##### `UdpSocket`
- `isOpen(): Bool`
- `localAddress(): SocketAddress`
- `receive(maxBytes: Int = 65536): Datagram?`
- `send(data: Buffer, host: String, port: Int): void`
- `close(): void`
##### `Datagram`
- `data: Buffer`
- `address: SocketAddress`
---
#### Security policy
The module uses `NetAccessPolicy` to authorize network operations before they are executed.
- `NetAccessPolicy` — interface for custom policies
- `PermitAllNetAccessPolicy` — allows all network operations
- `NetAccessOp.Resolve(host, port)`
- `NetAccessOp.TcpConnect(host, port)`
- `NetAccessOp.TcpListen(host, port, backlog)`
- `NetAccessOp.UdpBind(host, port)`
---
#### Platform support
- **JVM:** supported
- **Other targets:** currently report unsupported; use capability checks before relying on raw sockets
+115
View File
@@ -0,0 +1,115 @@
### lyng.io.ws — WebSocket client for Lyng scripts
This module provides a compact WebSocket client API for Lyng scripts. It is implemented in `lyngio` and currently backed by Ktor WebSockets on the JVM.
> **Note:** `lyngio` is a separate library module. It must be explicitly added as a dependency to your host application and initialized in your Lyng scopes.
---
#### Install the module into a Lyng session
Kotlin (host) bootstrap example:
```kotlin
import net.sergeych.lyng.EvalSession
import net.sergeych.lyng.Scope
import net.sergeych.lyng.io.ws.createWsModule
import net.sergeych.lyngio.ws.security.PermitAllWsAccessPolicy
suspend fun bootstrapWs() {
val session = EvalSession()
val scope: Scope = session.getScope()
createWsModule(PermitAllWsAccessPolicy, scope)
session.eval("import lyng.io.ws")
}
```
---
#### Using from Lyng scripts
Simple text message exchange:
import lyng.io.ws
val ws = Ws.connect(WS_TEST_URL)
ws.sendText("ping")
val m: WsMessage = ws.receive()
ws.close()
[ws.url() == WS_TEST_URL, m.isText, m.text]
>>> [true,true,echo:ping]
Binary message exchange:
import lyng.buffer
import lyng.io.ws
val ws = Ws.connect(WS_TEST_BINARY_URL)
ws.sendBytes(Buffer(9, 8, 7))
val m: WsMessage = ws.receive()
ws.close()
[m.isText, (m.data as Buffer).hex]
>>> [false,010203090807]
Secure websocket (`wss`) exchange:
import lyng.io.ws
val ws = Ws.connect(WSS_TEST_URL)
ws.sendText("ping")
val m: WsMessage = ws.receive()
ws.close()
[ws.url() == WSS_TEST_URL, m.text]
>>> [true,secure:ping]
---
#### API reference
##### `Ws` (static methods)
- `isSupported(): Bool` — Whether WebSocket client support is available on the current runtime.
- `connect(url: String, headers...): WsSession` — Open a client websocket session.
`headers...` accepts:
- `MapEntry`, e.g. `"Authorization" => "Bearer x"`
- 2-item lists, e.g. `["Authorization", "Bearer x"]`
##### `WsSession`
- `isOpen(): Bool`
- `url(): String`
- `sendText(text: String): void`
- `sendBytes(data: Buffer): void`
- `receive(): WsMessage?`
- `close(code: Int = 1000, reason: String = ""): void`
`receive()` returns `null` after a clean close.
##### `WsMessage`
- `isText: Bool`
- `text: String?`
- `data: Buffer?`
Text messages populate `text`; binary messages populate `data`.
---
#### Security policy
The module uses `WsAccessPolicy` to authorize websocket operations.
- `WsAccessPolicy` — interface for custom policies
- `PermitAllWsAccessPolicy` — allows all websocket operations
- `WsAccessOp.Connect(url)`
- `WsAccessOp.Send(url, bytes, isText)`
- `WsAccessOp.Receive(url)`
---
#### Platform support
- **JVM:** supported
- **Other targets:** currently report unsupported; use `Ws.isSupported()` before relying on websocket client access
+32 -8
View File
@@ -13,6 +13,9 @@
- **[lyng.io.fs](lyng.io.fs.md):** Async filesystem access. Provides the `Path` class for file/directory operations, streaming, and globbing.
- **[lyng.io.process](lyng.io.process.md):** External process execution and shell commands. Provides `Process`, `RunningProcess`, and `Platform` information.
- **[lyng.io.console](lyng.io.console.md):** Rich console/TTY access. Provides `Console` capability detection, geometry, output, and iterable events.
- **[lyng.io.http](lyng.io.http.md):** HTTP/HTTPS client access. Provides `Http`, `HttpRequest`, `HttpResponse`, and `HttpHeaders`.
- **[lyng.io.ws](lyng.io.ws.md):** WebSocket client access. Provides `Ws`, `WsSession`, and `WsMessage`.
- **[lyng.io.net](lyng.io.net.md):** Transport networking. Provides `Net`, `TcpSocket`, `TcpServer`, `UdpSocket`, and `SocketAddress`.
---
@@ -41,9 +44,15 @@ import net.sergeych.lyng.EvalSession
import net.sergeych.lyng.io.fs.createFs
import net.sergeych.lyng.io.process.createProcessModule
import net.sergeych.lyng.io.console.createConsoleModule
import net.sergeych.lyng.io.http.createHttpModule
import net.sergeych.lyng.io.net.createNetModule
import net.sergeych.lyng.io.ws.createWsModule
import net.sergeych.lyngio.fs.security.PermitAllAccessPolicy
import net.sergeych.lyngio.process.security.PermitAllProcessAccessPolicy
import net.sergeych.lyngio.console.security.PermitAllConsoleAccessPolicy
import net.sergeych.lyngio.http.security.PermitAllHttpAccessPolicy
import net.sergeych.lyngio.net.security.PermitAllNetAccessPolicy
import net.sergeych.lyngio.ws.security.PermitAllWsAccessPolicy
suspend fun runMyScript() {
val session = EvalSession()
@@ -53,16 +62,25 @@ suspend fun runMyScript() {
createFs(PermitAllAccessPolicy, scope)
createProcessModule(PermitAllProcessAccessPolicy, scope)
createConsoleModule(PermitAllConsoleAccessPolicy, scope)
createHttpModule(PermitAllHttpAccessPolicy, scope)
createNetModule(PermitAllNetAccessPolicy, scope)
createWsModule(PermitAllWsAccessPolicy, scope)
// Now scripts can import them
session.eval("""
import lyng.io.fs
import lyng.io.process
import lyng.io.console
import lyng.io.http
import lyng.io.net
import lyng.io.ws
println("Working dir: " + Path(".").readUtf8())
println("OS: " + Platform.details().name)
println("TTY: " + Console.isTty())
println("HTTP available: " + Http.isSupported())
println("TCP available: " + Net.isTcpAvailable())
println("WS available: " + Ws.isSupported())
""")
}
```
@@ -76,21 +94,27 @@ suspend fun runMyScript() {
- **Filesystem Security:** Implement `FsAccessPolicy` to restrict access to specific paths or operations (e.g., read-only access to a sandbox directory).
- **Process Security:** Implement `ProcessAccessPolicy` to restrict which executables can be run or to disable shell execution entirely.
- **Console Security:** Implement `ConsoleAccessPolicy` to control output writes, event reads, and raw mode switching.
- **HTTP Security:** Implement `HttpAccessPolicy` to restrict which requests scripts may send.
- **Transport Security:** Implement `NetAccessPolicy` to restrict DNS resolution and TCP/UDP socket operations.
- **WebSocket Security:** Implement `WsAccessPolicy` to restrict websocket connects and message flow.
For more details, see the specific module documentation:
- [Filesystem Security Details](lyng.io.fs.md#access-policy-security)
- [Process Security Details](lyng.io.process.md#security-policy)
- [Console Module Details](lyng.io.console.md)
- [HTTP Module Details](lyng.io.http.md)
- [Transport Networking Details](lyng.io.net.md)
- [WebSocket Module Details](lyng.io.ws.md)
---
#### Platform Support Overview
| Platform | lyng.io.fs | lyng.io.process | lyng.io.console |
| :--- | :---: | :---: | :---: |
| **JVM** | ✅ | ✅ | ✅ (baseline) |
| **Native (Linux/macOS)** | ✅ | ✅ | 🚧 |
| **Native (Windows)** | ✅ | 🚧 (Planned) | 🚧 |
| **Android** | ✅ | ❌ | ❌ |
| **NodeJS** | ✅ | ❌ | ❌ |
| **Browser / Wasm** | ✅ (In-memory) | ❌ | ❌ |
| Platform | lyng.io.fs | lyng.io.process | lyng.io.console | lyng.io.http | lyng.io.ws | lyng.io.net |
| :--- | :---: | :---: | :---: | :---: | :---: | :---: |
| **JVM** | ✅ | ✅ | ✅ (baseline) | ✅ | ✅ | ✅ |
| **Native (Linux/macOS)** | ✅ | ✅ | 🚧 | 🚧 | 🚧 | 🚧 |
| **Native (Windows)** | ✅ | 🚧 (Planned) | 🚧 | 🚧 | 🚧 | 🚧 |
| **Android** | ✅ | ❌ | ❌ | 🚧 | 🚧 | 🚧 |
| **NodeJS** | ✅ | ❌ | ❌ | 🚧 | 🚧 | 🚧 |
| **Browser / Wasm** | ✅ (In-memory) | ❌ | ❌ | 🚧 | 🚧 | 🚧 |