Compare commits
1 Commits
main
...
networking
| Author | SHA1 | Date | |
|---|---|---|---|
| bad32fdef2 |
283
NETWORKING.md
Normal file
283
NETWORKING.md
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
# Networking in Orchid
|
||||||
|
|
||||||
|
This document explains how to use TCP networking in the Orchid programming language.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Build the project with the nightly Rust toolchain:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
rustup run nightly cargo build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Socket Module Overview
|
||||||
|
|
||||||
|
The socket module is available at `std::socket::tcp` and provides functions for TCP client/server communication.
|
||||||
|
|
||||||
|
### Importing
|
||||||
|
|
||||||
|
```orchid
|
||||||
|
import std::socket::tcp::(bind, accept, connect, read, write_all, close)
|
||||||
|
```
|
||||||
|
|
||||||
|
Or import all functions:
|
||||||
|
|
||||||
|
```orchid
|
||||||
|
import std::socket::tcp
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### Server Functions
|
||||||
|
|
||||||
|
| Function | Signature | Description |
|
||||||
|
|----------|-----------|-------------|
|
||||||
|
| `bind` | `String -> TcpListener` | Create a TCP listener bound to an address |
|
||||||
|
| `accept` | `TcpListener -> TcpStream` | Accept an incoming connection |
|
||||||
|
| `listener_addr` | `TcpListener -> String` | Get the address the listener is bound to |
|
||||||
|
|
||||||
|
### Client Functions
|
||||||
|
|
||||||
|
| Function | Signature | Description |
|
||||||
|
|----------|-----------|-------------|
|
||||||
|
| `connect` | `String -> TcpStream` | Connect to a TCP server |
|
||||||
|
|
||||||
|
### Stream Functions
|
||||||
|
|
||||||
|
| Function | Signature | Description |
|
||||||
|
|----------|-----------|-------------|
|
||||||
|
| `read` | `TcpStream -> Int -> String` | Read up to N bytes as UTF-8 string |
|
||||||
|
| `read_bytes` | `TcpStream -> Int -> String` | Read up to N bytes as raw byte string |
|
||||||
|
| `read_exact` | `TcpStream -> Int -> String` | Read exactly N bytes |
|
||||||
|
| `write` | `TcpStream -> String -> Int` | Write data, returns bytes written |
|
||||||
|
| `write_all` | `TcpStream -> String -> Int` | Write all data |
|
||||||
|
| `flush` | `TcpStream -> Int` | Flush the stream buffer |
|
||||||
|
| `close` | `TcpStream -> Int` | Close the connection |
|
||||||
|
| `peer_addr` | `TcpStream -> String` | Get the remote peer's address |
|
||||||
|
| `local_addr` | `TcpStream -> String` | Get the local address |
|
||||||
|
|
||||||
|
## Quick Start Examples
|
||||||
|
|
||||||
|
### Testing Socket Bind
|
||||||
|
|
||||||
|
```sh
|
||||||
|
rustup run nightly cargo orcx -- exec "std::socket::tcp::bind \"127.0.0.1:8080\""
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
```
|
||||||
|
<TcpListener 127.0.0.1:8080>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing Connection (expect error if no server running)
|
||||||
|
|
||||||
|
```sh
|
||||||
|
rustup run nightly cargo orcx -- exec "std::socket::tcp::connect \"127.0.0.1:8080\""
|
||||||
|
```
|
||||||
|
|
||||||
|
Output (if no server):
|
||||||
|
```
|
||||||
|
error: IO error: Connection refused (os error 111): @
|
||||||
|
```
|
||||||
|
|
||||||
|
## Example Scripts
|
||||||
|
|
||||||
|
### TCP Echo Server
|
||||||
|
|
||||||
|
Location: `examples/tcp-echo-server/src/main.orc`
|
||||||
|
|
||||||
|
```orchid
|
||||||
|
import std::socket::tcp::(bind, accept, read, write_all, close)
|
||||||
|
|
||||||
|
const handle_client := \client. do cps {
|
||||||
|
cps data = read client 1024;
|
||||||
|
cps _ = write_all client data;
|
||||||
|
cps _ = close client;
|
||||||
|
cps pass 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const accept_loop := \server. do cps {
|
||||||
|
cps client = accept server;
|
||||||
|
cps _ = handle_client client;
|
||||||
|
cps pass $ accept_loop server;
|
||||||
|
}
|
||||||
|
|
||||||
|
const main := do cps {
|
||||||
|
cps server = bind "127.0.0.1:8080";
|
||||||
|
cps pass $ accept_loop server;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the echo server:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
rustup run nightly cargo orcx -- exec --proj ./examples/tcp-echo-server "src::main::main"
|
||||||
|
```
|
||||||
|
|
||||||
|
### TCP Client
|
||||||
|
|
||||||
|
Location: `examples/tcp-client/src/main.orc`
|
||||||
|
|
||||||
|
```orchid
|
||||||
|
import std::socket::tcp::(connect, read, write_all, close, peer_addr)
|
||||||
|
import system::io::println
|
||||||
|
|
||||||
|
const main := do cps {
|
||||||
|
cps conn = connect "127.0.0.1:8080";
|
||||||
|
cps addr = peer_addr conn;
|
||||||
|
cps println $ "Connected to ${addr}";
|
||||||
|
cps _ = write_all conn "Hello from Orchid!\n";
|
||||||
|
cps response = read conn 1024;
|
||||||
|
cps println $ "Server response: ${response}";
|
||||||
|
cps _ = close conn;
|
||||||
|
cps pass 0;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the client (requires a server running on port 8080):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
rustup run nightly cargo orcx -- exec --proj ./examples/tcp-client "src::main::main"
|
||||||
|
```
|
||||||
|
|
||||||
|
### HTTP Server
|
||||||
|
|
||||||
|
Location: `examples/http-server/src/main.orc`
|
||||||
|
|
||||||
|
```orchid
|
||||||
|
import std::socket::tcp::(bind, accept, read, write_all, close, peer_addr, listener_addr)
|
||||||
|
import system::io::println
|
||||||
|
|
||||||
|
const http_response := "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 44\r\nConnection: close\r\n\r\n<html><body><h1>Hello Orchid!</h1></body></html>"
|
||||||
|
|
||||||
|
const handle_client := \client. do cps {
|
||||||
|
cps addr = peer_addr client;
|
||||||
|
cps println $ "Client connected: ${addr}";
|
||||||
|
cps request = read client 4096;
|
||||||
|
cps println $ "Received request from ${addr}";
|
||||||
|
cps _ = write_all client http_response;
|
||||||
|
cps _ = close client;
|
||||||
|
cps println $ "Connection closed: ${addr}";
|
||||||
|
cps pass 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const accept_loop := \server. do cps {
|
||||||
|
cps client = accept server;
|
||||||
|
cps _ = handle_client client;
|
||||||
|
cps pass $ accept_loop server;
|
||||||
|
}
|
||||||
|
|
||||||
|
const main := do cps {
|
||||||
|
cps server = bind "127.0.0.1:8080";
|
||||||
|
cps addr = listener_addr server;
|
||||||
|
cps println $ "HTTP Server listening on ${addr}";
|
||||||
|
cps println "Press Ctrl+C to stop";
|
||||||
|
cps pass $ accept_loop server;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the HTTP server:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
rustup run nightly cargo orcx -- exec --proj ./examples/http-server "src::main::main"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then open http://127.0.0.1:8080 in your browser or test with curl:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
curl http://127.0.0.1:8080
|
||||||
|
```
|
||||||
|
|
||||||
|
## Understanding the CPS Syntax
|
||||||
|
|
||||||
|
Orchid uses Continuation-Passing Style (CPS) for side effects. The `do cps { ... }` block is used for sequential operations:
|
||||||
|
|
||||||
|
- `cps variable = expression;` - Bind the result of an expression to a variable
|
||||||
|
- `cps expression;` - Execute an expression for its side effects
|
||||||
|
- `cps pass value;` - Return a value from the block
|
||||||
|
|
||||||
|
Example breakdown:
|
||||||
|
|
||||||
|
```orchid
|
||||||
|
const main := do cps {
|
||||||
|
cps server = bind "127.0.0.1:8080"; -- Bind socket, store in 'server'
|
||||||
|
cps client = accept server; -- Accept connection, store in 'client'
|
||||||
|
cps data = read client 1024; -- Read data from client
|
||||||
|
cps _ = write_all client data; -- Write data back (ignore result)
|
||||||
|
cps _ = close client; -- Close connection
|
||||||
|
cps pass 0; -- Return 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
Socket operations return errors when they fail. Errors are displayed with the IO error message:
|
||||||
|
|
||||||
|
```
|
||||||
|
error: IO error: Connection refused (os error 111): @
|
||||||
|
```
|
||||||
|
|
||||||
|
Common errors:
|
||||||
|
- `Connection refused` - No server listening on the target address
|
||||||
|
- `Address already in use` - Port is already bound by another process
|
||||||
|
- `Connection reset by peer` - Remote end closed the connection unexpectedly
|
||||||
|
|
||||||
|
## Building a Simple Protocol
|
||||||
|
|
||||||
|
Here's an example of a simple request/response protocol:
|
||||||
|
|
||||||
|
```orchid
|
||||||
|
import std::socket::tcp::(bind, accept, read, write_all, close)
|
||||||
|
|
||||||
|
const handle_request := \request.
|
||||||
|
if request == "PING\n" then "PONG\n"
|
||||||
|
else if request == "TIME\n" then "2024-01-01 00:00:00\n"
|
||||||
|
else "UNKNOWN\n"
|
||||||
|
|
||||||
|
const handle_client := \client. do cps {
|
||||||
|
cps request = read client 1024;
|
||||||
|
cps response = pass $ handle_request request;
|
||||||
|
cps _ = write_all client response;
|
||||||
|
cps _ = close client;
|
||||||
|
cps pass 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const server_loop := \server. do cps {
|
||||||
|
cps client = accept server;
|
||||||
|
cps _ = handle_client client;
|
||||||
|
cps pass $ server_loop server;
|
||||||
|
}
|
||||||
|
|
||||||
|
const main := do cps {
|
||||||
|
cps server = bind "127.0.0.1:9000";
|
||||||
|
cps pass $ server_loop server;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
- The current implementation handles one client at a time (sequential accept loop)
|
||||||
|
- Sockets are non-serializable (cannot be persisted or transferred across boundaries)
|
||||||
|
- No UDP support (TCP only)
|
||||||
|
- No TLS/SSL support
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Port Already in Use
|
||||||
|
|
||||||
|
If you get "Address already in use", either:
|
||||||
|
1. Wait for the previous process to release the port
|
||||||
|
2. Use a different port
|
||||||
|
3. Kill the process using the port: `lsof -i :8080` then `kill <pid>`
|
||||||
|
|
||||||
|
### Connection Refused
|
||||||
|
|
||||||
|
Ensure the server is running before starting the client.
|
||||||
|
|
||||||
|
### Build Errors
|
||||||
|
|
||||||
|
Make sure to use the nightly toolchain:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
rustup run nightly cargo build
|
||||||
|
```
|
||||||
29
examples/http-server-simple/main.orc
Normal file
29
examples/http-server-simple/main.orc
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import std::socket::tcp::(bind, accept, read, write_all, close, peer_addr, listener_addr)
|
||||||
|
import system::io::println
|
||||||
|
|
||||||
|
let http_response = "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 44\r\nConnection: close\r\n\r\n<html><body><h1>Hello Orchid!</h1></body></html>"
|
||||||
|
|
||||||
|
let handle_client = \client. do cps {
|
||||||
|
cps addr = peer_addr client;
|
||||||
|
cps println $ "Client connected: ${addr}";
|
||||||
|
cps request = read client 4096;
|
||||||
|
cps println $ "Received request from ${addr}";
|
||||||
|
cps _ = write_all client http_response;
|
||||||
|
cps _ = close client;
|
||||||
|
cps println $ "Connection closed: ${addr}";
|
||||||
|
cps pass 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
let accept_loop = \server. do cps {
|
||||||
|
cps client = accept server;
|
||||||
|
cps _ = handle_client client;
|
||||||
|
cps pass $ accept_loop server;
|
||||||
|
}
|
||||||
|
|
||||||
|
let main = do cps {
|
||||||
|
cps server = bind "127.0.0.1:8080";
|
||||||
|
cps addr = listener_addr server;
|
||||||
|
cps println $ "HTTP Server listening on ${addr}";
|
||||||
|
cps println "Press Ctrl+C to stop";
|
||||||
|
cps pass $ accept_loop server;
|
||||||
|
}
|
||||||
31
examples/http-server/src/main.orc
Normal file
31
examples/http-server/src/main.orc
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import std::socket::tcp::(bind, accept, read, write_all, close, peer_addr, listener_addr)
|
||||||
|
import system::io::println
|
||||||
|
|
||||||
|
cps println $ "Hello Orchid!";
|
||||||
|
|
||||||
|
const http_response := "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: 44\r\nConnection: close\r\n\r\n<html><body><h1>Hello Orchid!</h1></body></html>"
|
||||||
|
|
||||||
|
const handle_client := \client. do cps {
|
||||||
|
cps addr = peer_addr client;
|
||||||
|
cps println $ "Client connected: ${addr}";
|
||||||
|
cps request = read client 4096;
|
||||||
|
cps println $ "Received request from ${addr}";
|
||||||
|
cps _ = write_all client http_response;
|
||||||
|
cps _ = close client;
|
||||||
|
cps println $ "Connection closed: ${addr}";
|
||||||
|
cps pass 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const accept_loop := \server. do cps {
|
||||||
|
cps client = accept server;
|
||||||
|
cps _ = handle_client client;
|
||||||
|
cps pass $ accept_loop server;
|
||||||
|
}
|
||||||
|
|
||||||
|
const main := do cps {
|
||||||
|
cps server = bind "127.0.0.1:8080";
|
||||||
|
cps addr = listener_addr server;
|
||||||
|
cps println $ "HTTP Server listening on ${addr}";
|
||||||
|
cps println "Press Ctrl+C to stop";
|
||||||
|
cps pass $ accept_loop server;
|
||||||
|
}
|
||||||
13
examples/tcp-client/src/main.orc
Normal file
13
examples/tcp-client/src/main.orc
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import std::socket::tcp::(connect, read, write_all, close, peer_addr)
|
||||||
|
import system::io::println
|
||||||
|
|
||||||
|
const main := do cps {
|
||||||
|
cps conn = connect "127.0.0.1:8080";
|
||||||
|
cps addr = peer_addr conn;
|
||||||
|
cps println $ "Connected to ${addr}";
|
||||||
|
cps _ = write_all conn "Hello from Orchid!\n";
|
||||||
|
cps response = read conn 1024;
|
||||||
|
cps println $ "Server response: ${response}";
|
||||||
|
cps _ = close conn;
|
||||||
|
cps pass 0;
|
||||||
|
}
|
||||||
19
examples/tcp-echo-server/src/main.orc
Normal file
19
examples/tcp-echo-server/src/main.orc
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import std::socket::tcp::(bind, accept, read, write_all, close)
|
||||||
|
|
||||||
|
const handle_client := \client. do cps {
|
||||||
|
cps data = read client 1024;
|
||||||
|
cps _ = write_all client data;
|
||||||
|
cps _ = close client;
|
||||||
|
cps pass 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
const accept_loop := \server. do cps {
|
||||||
|
cps client = accept server;
|
||||||
|
cps _ = handle_client client;
|
||||||
|
cps pass $ accept_loop server;
|
||||||
|
}
|
||||||
|
|
||||||
|
const main := do cps {
|
||||||
|
cps server = bind "127.0.0.1:8080";
|
||||||
|
cps pass $ accept_loop server;
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ pub mod option;
|
|||||||
pub mod protocol;
|
pub mod protocol;
|
||||||
pub mod record;
|
pub mod record;
|
||||||
pub mod reflection;
|
pub mod reflection;
|
||||||
|
pub mod socket;
|
||||||
pub mod std_system;
|
pub mod std_system;
|
||||||
pub mod string;
|
pub mod string;
|
||||||
pub mod tuple;
|
pub mod tuple;
|
||||||
|
|||||||
2
orchid-std/src/std/socket/mod.rs
Normal file
2
orchid-std/src/std/socket/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pub mod socket_atom;
|
||||||
|
pub mod socket_lib;
|
||||||
110
orchid-std/src/std/socket/socket_atom.rs
Normal file
110
orchid-std/src/std/socket/socket_atom.rs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
use std::borrow::Cow;
|
||||||
|
use std::rc::Rc;
|
||||||
|
use std::cell::RefCell;
|
||||||
|
|
||||||
|
use futures::lock::Mutex;
|
||||||
|
use never::Never;
|
||||||
|
use orchid_base::format::{FmtCtx, FmtUnit};
|
||||||
|
use orchid_extension::atom::{Atomic, MethodSetBuilder};
|
||||||
|
use orchid_extension::atom_owned::{OwnedAtom, OwnedVariant};
|
||||||
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
|
|
||||||
|
pub struct TcpListenerAtom {
|
||||||
|
pub listener: Rc<Mutex<TcpListener>>,
|
||||||
|
pub addr: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for TcpListenerAtom {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
listener: self.listener.clone(),
|
||||||
|
addr: self.addr.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Atomic for TcpListenerAtom {
|
||||||
|
type Variant = OwnedVariant;
|
||||||
|
type Data = ();
|
||||||
|
fn reg_reqs() -> MethodSetBuilder<Self> { MethodSetBuilder::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OwnedAtom for TcpListenerAtom {
|
||||||
|
type Refs = Never;
|
||||||
|
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
|
||||||
|
async fn print_atom<'a>(&'a self, _: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
|
||||||
|
format!("<TcpListener {}>", self.addr).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct TcpStreamAtom {
|
||||||
|
pub stream: Rc<RefCell<Option<TcpStream>>>,
|
||||||
|
pub peer_addr: String,
|
||||||
|
pub local_addr: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for TcpStreamAtom {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
stream: self.stream.clone(),
|
||||||
|
peer_addr: self.peer_addr.clone(),
|
||||||
|
local_addr: self.local_addr.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Atomic for TcpStreamAtom {
|
||||||
|
type Variant = OwnedVariant;
|
||||||
|
type Data = ();
|
||||||
|
fn reg_reqs() -> MethodSetBuilder<Self> { MethodSetBuilder::new() }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OwnedAtom for TcpStreamAtom {
|
||||||
|
type Refs = Never;
|
||||||
|
async fn val(&self) -> Cow<'_, Self::Data> { Cow::Owned(()) }
|
||||||
|
async fn print_atom<'a>(&'a self, _: &'a (impl FmtCtx + ?Sized + 'a)) -> FmtUnit {
|
||||||
|
format!("<TcpStream {} -> {}>", self.local_addr, self.peer_addr).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TcpStreamAtom {
|
||||||
|
pub async fn read(&self, max_bytes: usize) -> std::io::Result<Vec<u8>> {
|
||||||
|
let mut stream_opt = self.stream.borrow_mut();
|
||||||
|
let stream = stream_opt.as_mut().ok_or_else(|| {
|
||||||
|
std::io::Error::new(std::io::ErrorKind::NotConnected, "Stream closed")
|
||||||
|
})?;
|
||||||
|
let mut buf = vec![0u8; max_bytes];
|
||||||
|
let n = stream.read(&mut buf).await?;
|
||||||
|
buf.truncate(n);
|
||||||
|
Ok(buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn write(&self, data: &[u8]) -> std::io::Result<usize> {
|
||||||
|
let mut stream_opt = self.stream.borrow_mut();
|
||||||
|
let stream = stream_opt.as_mut().ok_or_else(|| {
|
||||||
|
std::io::Error::new(std::io::ErrorKind::NotConnected, "Stream closed")
|
||||||
|
})?;
|
||||||
|
stream.write(data).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn write_all(&self, data: &[u8]) -> std::io::Result<()> {
|
||||||
|
let mut stream_opt = self.stream.borrow_mut();
|
||||||
|
let stream = stream_opt.as_mut().ok_or_else(|| {
|
||||||
|
std::io::Error::new(std::io::ErrorKind::NotConnected, "Stream closed")
|
||||||
|
})?;
|
||||||
|
stream.write_all(data).await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn flush(&self) -> std::io::Result<()> {
|
||||||
|
let mut stream_opt = self.stream.borrow_mut();
|
||||||
|
let stream = stream_opt.as_mut().ok_or_else(|| {
|
||||||
|
std::io::Error::new(std::io::ErrorKind::NotConnected, "Stream closed")
|
||||||
|
})?;
|
||||||
|
stream.flush().await
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn close(&self) {
|
||||||
|
let _ = self.stream.borrow_mut().take();
|
||||||
|
}
|
||||||
|
}
|
||||||
131
orchid-std/src/std/socket/socket_lib.rs
Normal file
131
orchid-std/src/std/socket/socket_lib.rs
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
use std::cell::RefCell;
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use futures::lock::Mutex;
|
||||||
|
use orchid_base::error::{OrcRes, OrcErrv, mk_errv_floating};
|
||||||
|
use orchid_extension::atom::TAtom;
|
||||||
|
use orchid_extension::atom_owned::own;
|
||||||
|
use orchid_extension::context::i;
|
||||||
|
use orchid_extension::conv::TryFromExpr;
|
||||||
|
use orchid_extension::expr::Expr;
|
||||||
|
use orchid_extension::tree::{GenMember, fun, prefix};
|
||||||
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
|
|
||||||
|
use super::socket_atom::{TcpListenerAtom, TcpStreamAtom};
|
||||||
|
use crate::std::string::str_atom::StrAtom;
|
||||||
|
use crate::{Int, OrcString};
|
||||||
|
|
||||||
|
async fn io_err(e: std::io::Error) -> OrcErrv {
|
||||||
|
mk_errv_floating(i().i(&format!("IO error: {}", e)).await, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! try_io {
|
||||||
|
($e:expr) => {
|
||||||
|
match $e {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => return Err(io_err(e).await),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFromExpr for TcpListenerAtom {
|
||||||
|
async fn try_from_expr(expr: Expr) -> OrcRes<Self> {
|
||||||
|
let tatom = TAtom::<TcpListenerAtom>::try_from_expr(expr).await?;
|
||||||
|
Ok(own(&tatom).await)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFromExpr for TcpStreamAtom {
|
||||||
|
async fn try_from_expr(expr: Expr) -> OrcRes<Self> {
|
||||||
|
let tatom = TAtom::<TcpStreamAtom>::try_from_expr(expr).await?;
|
||||||
|
Ok(own(&tatom).await)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn gen_socket_lib() -> Vec<GenMember> {
|
||||||
|
prefix("std::socket", [
|
||||||
|
prefix("tcp", [
|
||||||
|
fun(true, "bind", async |addr: OrcString| -> OrcRes<TcpListenerAtom> {
|
||||||
|
let addr_str = addr.get_string().await;
|
||||||
|
let listener = try_io!(TcpListener::bind(addr_str.as_str()).await);
|
||||||
|
let local_addr = listener.local_addr().map(|a| a.to_string()).unwrap_or_default();
|
||||||
|
Ok(TcpListenerAtom {
|
||||||
|
listener: Rc::new(Mutex::new(listener)),
|
||||||
|
addr: local_addr,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
fun(true, "accept", async |listener: TcpListenerAtom| -> OrcRes<TcpStreamAtom> {
|
||||||
|
let guard = listener.listener.lock().await;
|
||||||
|
let (stream, peer_addr) = try_io!(guard.accept().await);
|
||||||
|
let local_addr = stream.local_addr().map(|a| a.to_string()).unwrap_or_default();
|
||||||
|
Ok(TcpStreamAtom {
|
||||||
|
stream: Rc::new(RefCell::new(Some(stream))),
|
||||||
|
peer_addr: peer_addr.to_string(),
|
||||||
|
local_addr,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
fun(true, "connect", async |addr: OrcString| -> OrcRes<TcpStreamAtom> {
|
||||||
|
let addr_str = addr.get_string().await;
|
||||||
|
let stream = try_io!(TcpStream::connect(addr_str.as_str()).await);
|
||||||
|
let peer_addr = stream.peer_addr().map(|a| a.to_string()).unwrap_or_default();
|
||||||
|
let local_addr = stream.local_addr().map(|a| a.to_string()).unwrap_or_default();
|
||||||
|
Ok(TcpStreamAtom {
|
||||||
|
stream: Rc::new(RefCell::new(Some(stream))),
|
||||||
|
peer_addr,
|
||||||
|
local_addr,
|
||||||
|
})
|
||||||
|
}),
|
||||||
|
fun(true, "read", async |stream: TcpStreamAtom, max_bytes: Int| -> OrcRes<StrAtom> {
|
||||||
|
let data = try_io!(stream.read(max_bytes.0 as usize).await);
|
||||||
|
let s = String::from_utf8_lossy(&data).to_string();
|
||||||
|
Ok(StrAtom::new(Rc::new(s)))
|
||||||
|
}),
|
||||||
|
fun(true, "read_bytes", async |stream: TcpStreamAtom, max_bytes: Int| -> OrcRes<StrAtom> {
|
||||||
|
let data = try_io!(stream.read(max_bytes.0 as usize).await);
|
||||||
|
let s = data.iter().map(|b| *b as char).collect::<String>();
|
||||||
|
Ok(StrAtom::new(Rc::new(s)))
|
||||||
|
}),
|
||||||
|
fun(true, "read_exact", async |stream: TcpStreamAtom, num_bytes: Int| -> OrcRes<StrAtom> {
|
||||||
|
let mut result = Vec::new();
|
||||||
|
let target = num_bytes.0 as usize;
|
||||||
|
while result.len() < target {
|
||||||
|
let remaining = target - result.len();
|
||||||
|
let chunk = try_io!(stream.read(remaining).await);
|
||||||
|
if chunk.is_empty() {
|
||||||
|
return Err(mk_errv_floating(i().i("Connection closed before receiving all bytes").await, ""));
|
||||||
|
}
|
||||||
|
result.extend(chunk);
|
||||||
|
}
|
||||||
|
let s = String::from_utf8_lossy(&result).to_string();
|
||||||
|
Ok(StrAtom::new(Rc::new(s)))
|
||||||
|
}),
|
||||||
|
fun(true, "write", async |stream: TcpStreamAtom, data: OrcString| -> OrcRes<Int> {
|
||||||
|
let data_str = data.get_string().await;
|
||||||
|
let n = try_io!(stream.write(data_str.as_bytes()).await);
|
||||||
|
Ok(Int(n as i64))
|
||||||
|
}),
|
||||||
|
fun(true, "write_all", async |stream: TcpStreamAtom, data: OrcString| -> OrcRes<Int> {
|
||||||
|
let data_str = data.get_string().await;
|
||||||
|
try_io!(stream.write_all(data_str.as_bytes()).await);
|
||||||
|
Ok(Int(0))
|
||||||
|
}),
|
||||||
|
fun(true, "flush", async |stream: TcpStreamAtom| -> OrcRes<Int> {
|
||||||
|
try_io!(stream.flush().await);
|
||||||
|
Ok(Int(0))
|
||||||
|
}),
|
||||||
|
fun(true, "close", async |stream: TcpStreamAtom| -> Int {
|
||||||
|
stream.close();
|
||||||
|
Int(0)
|
||||||
|
}),
|
||||||
|
fun(true, "peer_addr", async |stream: TcpStreamAtom| {
|
||||||
|
StrAtom::new(Rc::new(stream.peer_addr.clone()))
|
||||||
|
}),
|
||||||
|
fun(true, "local_addr", async |stream: TcpStreamAtom| {
|
||||||
|
StrAtom::new(Rc::new(stream.local_addr.clone()))
|
||||||
|
}),
|
||||||
|
fun(true, "listener_addr", async |listener: TcpListenerAtom| {
|
||||||
|
StrAtom::new(Rc::new(listener.addr.clone()))
|
||||||
|
}),
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
}
|
||||||
@@ -27,6 +27,8 @@ use crate::std::protocol::types::{Tag, Tagged, gen_protocol_lib};
|
|||||||
use crate::std::record::record_atom::Record;
|
use crate::std::record::record_atom::Record;
|
||||||
use crate::std::record::record_lib::gen_record_lib;
|
use crate::std::record::record_lib::gen_record_lib;
|
||||||
use crate::std::reflection::sym_atom::{CreateSymAtom, SymAtom, gen_sym_lib};
|
use crate::std::reflection::sym_atom::{CreateSymAtom, SymAtom, gen_sym_lib};
|
||||||
|
use crate::std::socket::socket_atom::{TcpListenerAtom, TcpStreamAtom};
|
||||||
|
use crate::std::socket::socket_lib::gen_socket_lib;
|
||||||
use crate::std::string::str_lexer::StringLexer;
|
use crate::std::string::str_lexer::StringLexer;
|
||||||
use crate::std::string::to_string::AsStrTag;
|
use crate::std::string::to_string::AsStrTag;
|
||||||
use crate::std::tuple::{CreateTuple, Tuple, TupleBuilder, gen_tuple_lib};
|
use crate::std::tuple::{CreateTuple, Tuple, TupleBuilder, gen_tuple_lib};
|
||||||
@@ -64,6 +66,8 @@ impl SystemCard for StdSystem {
|
|||||||
Some(Tag::dynfo()),
|
Some(Tag::dynfo()),
|
||||||
Some(Tagged::dynfo()),
|
Some(Tagged::dynfo()),
|
||||||
Some(AsStrTag::dynfo()),
|
Some(AsStrTag::dynfo()),
|
||||||
|
Some(TcpListenerAtom::dynfo()),
|
||||||
|
Some(TcpStreamAtom::dynfo()),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,6 +96,7 @@ impl System for StdSystem {
|
|||||||
gen_tuple_lib(),
|
gen_tuple_lib(),
|
||||||
gen_protocol_lib(),
|
gen_protocol_lib(),
|
||||||
gen_sym_lib().await,
|
gen_sym_lib().await,
|
||||||
|
gen_socket_lib(),
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
async fn prelude() -> Vec<Sym> {
|
async fn prelude() -> Vec<Sym> {
|
||||||
|
|||||||
Reference in New Issue
Block a user