Compare commits
1 Commits
main
...
networking
| Author | SHA1 | Date | |
|---|---|---|---|
| bad32fdef2 |
2
LICENCE
2
LICENCE
@@ -1,5 +1,3 @@
|
|||||||
THIS SOFTWARE IS PROVIDED WITHOUT WARRANTY
|
|
||||||
|
|
||||||
The code in this repository is free for noncommercial use, including derivative works and inclusion in other software if those are also free for noncommercial use. Commercial use, or inclusion in any derivative works licensed for commercial use is forbidden under this general licence.
|
The code in this repository is free for noncommercial use, including derivative works and inclusion in other software if those are also free for noncommercial use. Commercial use, or inclusion in any derivative works licensed for commercial use is forbidden under this general licence.
|
||||||
|
|
||||||
Identifying marks stored in the repository are restricted for use with an unmodified copy of this software. If you distribute modified versions of this software, you must either replace these identifying marks or modify them in a way that clearly indicates that what you are distributing is a derivative work and not this official vversion. You must also replace any contact information in such a way that your derivative work does not suggest that we may be contacted about issues. Your derivative work may use the original identifying marks and contact information to identify this project as its basis, while emphasizing that the authors of the original project are neither in control of, nor liable for the derivative work.
|
Identifying marks stored in the repository are restricted for use with an unmodified copy of this software. If you distribute modified versions of this software, you must either replace these identifying marks or modify them in a way that clearly indicates that what you are distributing is a derivative work and not this official vversion. You must also replace any contact information in such a way that your derivative work does not suggest that we may be contacted about issues. Your derivative work may use the original identifying marks and contact information to identify this project as its basis, while emphasizing that the authors of the original project are neither in control of, nor liable for the derivative work.
|
||||||
|
|||||||
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
|
||||||
|
```
|
||||||
@@ -35,16 +35,14 @@ Orchids and mangrove trees form complex ecosystems; The flowers persuade the tre
|
|||||||
|
|
||||||
All contributions are welcome. For the time being, use the issue tracker to discuss ideas.
|
All contributions are welcome. For the time being, use the issue tracker to discuss ideas.
|
||||||
|
|
||||||
Unless we agree on different terms, by contributing to this software you declare that you have created or otherwise have the right to license your contribution, agree to license it publicly under the general noncommercial licence included in this repository, and grant me (the owner of the project) a permanent, unrestricted license to use, modify, distribute and relicense your contribution.
|
Unless we agree on different terms, by contributing to this software you declare that you have created or otherwise have the right to license your contribution, agree to license it publicly under the general noncommercial licence included in this repository, and grant me (the owner of the project) a permanent, unrestricted license to use, modify, distribute and relicense your contribution under any terms I see fit.
|
||||||
|
|
||||||
You retain ownership of your intellectual property to ensure that the copyleft protections cementing the noncommercial availability of the code are preserved.
|
|
||||||
|
|
||||||
## About the license
|
## About the license
|
||||||
|
|
||||||
This software is free for noncommercial use. If you would like to use it for commercial purposes, or distribute your derivative work under a license that permits commercial use, contact me for a separate license. These licences are provided on a case-by-case basis with any limitations and compensation we agree on.
|
This software is free for noncommercial use. If you would like to use it for commercial purposes, or distribute your derivative work under a license that permits commercial use, contact me for a separate license. These licences are provided on a case-by-case basis with any limitations and compensation we agree on.
|
||||||
|
|
||||||
I generally appreciate the ethos of free software, and particularly the patterns used in copyleft to cement the guarantees of the licence. However, I don't think commercial entities fit that ethos, and I think they should be addressed separately rather than attempting to ignore the inherent unfairness towards contributors.
|
I generally appreciate the ethos of open-source software, and particularly the patterns used in copyleft to cement the longevity of the guarantees of the licence. However, I don't think commercial entities fit that ethos, and I think they should be addressed separately rather than attempting to ignore the inherent unfairness towards contributors.
|
||||||
|
|
||||||
My intent with the custom license included in this project is to enable the strong guarantees of copyleft towards noncommercial users, while leaving commercial users to engage with this project and its possible future ecosystem in a commercial way; if you intend to profit off my work, the barest cash flow should justify shooting me an email and agreeing on a simple temporary profit sharing deal until you figure out your business model, and the cash flow of a full scale business should more than justify dedicated attention to the software you rely on.
|
My intent with the custom license included in this project is to enable the strong guarantees of open-source towards open-source, while leaving commercial users to engage with this project and its possible future ecosystem in a commercial way; if you intend to profit off my work, the barest cash flow should justify shooting me an email and agreeing on a simple temporary profit sharing deal until you figure out your business model, and the cash flow of a full scale business should more than justify dedicated attention to the software you rely on.
|
||||||
|
|
||||||
The clause about identifying marks is intended to prevent another pitfall of open-source, wherein Linux distros borrow entire codebases, break them, and then distribute the result under the original author's name. If would like to package Orchid, I'd be delighted if you would talk to me about making it official, but if you would rather operate independently, you should present your project as the rogue derivative work that it is rather than borrowing the original project's identity for something its owner has no control over.
|
The clause about identifying marks is intended to prevent another pitfall of open-source, wherein Linux distros borrow entire codebases, break them, and then distribute the result under the original author's name. If would like to package Orchid, I'd be delighted if you would talk to me about making it official, but if you would rather operate independently, you should present your project as the rogue derivative work that it is rather than borrowing the original project's identity for something its owner has no control over.
|
||||||
|
|||||||
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