From dcd88de1a567d480e3e348c95eed9ba178adc685 Mon Sep 17 00:00:00 2001 From: narawat Date: Sun, 24 May 2026 08:57:14 +0700 Subject: [PATCH 01/16] update readme --- README.md | 1228 ++++++---------------- src/{msghandler.js => msghandler-ssr.js} | 0 2 files changed, 318 insertions(+), 910 deletions(-) rename src/{msghandler.js => msghandler-ssr.js} (100%) diff --git a/README.md b/README.md index e103f6f..c87a7ca 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,9 @@ # msghandler - Cross-Platform Communication Layer -A high-performance, **transport-agnostic** communication layer for **Julia**, **JavaScript**, **Python**, and **MicroPython** applications. Implements the Claim-Check pattern for efficient payload transport (direct for small payloads, URL-based for large payloads). +A high-performance, **transport-agnostic** communication layer for both **Julia** (backend) and **JavaScript** (CSR webapp) applications. Implements the Claim-Check pattern for efficient payload transport (direct for small payloads, URL-based for large payloads). + +- 🟢 **Julia Backend**: Full-featured implementation with Arrow IPC support +- 🔵 **JavaScript Frontend**: Browser-compatible implementation for CSR webapps [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) @@ -8,12 +11,14 @@ A high-performance, **transport-agnostic** communication layer for **Julia**, ** ## Table of Contents -- [Overview](#overview) -- [Cross-Platform Support](#cross-platform-support) -- [Features](#features) - [Quick Start](#quick-start) + - [Julia Backend](#julia-backend) + - [JavaScript CSR Webapp](#javascript-csr-webapp) +- [Overview](#overview) +- [Features](#features) +- [Installation](#installation) - [API Reference](#api-reference) -- [Cross-Platform Examples](#cross-platform-examples) +- [Examples](#examples) - [Testing](#testing) - [Documentation](#documentation) - [License](#license) @@ -22,34 +27,33 @@ A high-performance, **transport-agnostic** communication layer for **Julia**, ** ## Quick Data Format Reference -All platforms use the same input format for `smartpack()`: +### Input Format for `smartpack()` -### Format: `[(dataname, data, type), ...]` +**Format:** `[(dataname, data, type), ...]` | Element | Type | Description | |---------|------|-------------| -| `dataname` | String | Name for the payload (e.g., `"message"`, `"config.json"`) | -| `data` | Any | The actual data (type depends on the `type` parameter below) | -| `type` | String | Payload type identifier | +| `dataname` | `String` | Name for the payload (e.g., `"message"`, `"config.json"`) | +| `data` | `Any` | The actual data (type depends on the `type` parameter below) | +| `type` | `String` | Payload type identifier | ### Supported Payload Types -| Type | Julia Data | JavaScript Data | Python Data | Description | -|------|------------|-----------------|-------------|-------------| -| `"text"` | `String` | `string` | `str` | Plain text | -| `"dictionary"` | `Dict` | `Object` | `dict` | JSON object | -| `"arrowtable"` | `DataFrame` | ❌ | `pandas.DataFrame` | Arrow IPC table (Desktop only) | -| `"jsontable"` | `DataFrame` | `Array` | `list[dict]` | JSON table | -| `"image"` | `Vector{UInt8}` | `Uint8Array` | `bytes` | Image data | -| `"audio"` | `Vector{UInt8}` | `Uint8Array` | `bytes` | Audio data | -| `"video"` | `Vector{UInt8}` | `Uint8Array` | `bytes` | Video data | -| `"binary"` | `Vector{UInt8}` | `Uint8Array` | `bytes` | Binary data | +| Type | Julia Data | Description | +|------|------------|-------------| +| `"text"` | `String` | Plain text | +| `"dictionary"` | `Dict` | JSON object | +| `"arrowtable"` | `DataFrame`, `Arrow.Table` | Arrow IPC table | +| `"jsontable"` | `DataFrame`, `Vector{Dict}`, `Vector{NamedTuple}` | JSON table | +| `"image"` | `Vector{UInt8}` | Image data | +| `"audio"` | `Vector{UInt8}` | Audio data | +| `"video"` | `Vector{UInt8}` | Video data | +| `"binary"` | `Vector{UInt8}`, `IOBuffer` | Binary data | ### Examples **Sending multiple payloads:** ```julia -# Julia data = [ ("message", "Hello!", "text"), ("config", Dict("key" => "value"), "dictionary"), @@ -58,38 +62,16 @@ data = [ env, json_str = msghandler.smartpack("subject", data, options...) ``` -```python -# Python -data = [ - ("message", "Hello!", "text"), - ("config", {"key": "value"}, "dictionary"), - ("file", file_bytes, "binary") -] -env, json_str = await smartpack("subject", data, **options) -``` - -```javascript -// JavaScript -const data = [ - ["message", "Hello!", "text"], - ["config", {key: "value"}, "dictionary"], - ["file", fileBytes, "binary"] -]; -const [env, json_str] = await smartpack("subject", data, options); -``` - **Important Notes:** - Always wrap payloads in a list `[]`, even for single payloads - Type must be one of the supported types above - Large payloads (≥500KB) automatically use link transport -- Browser only supports: `"text"`, `"dictionary"`, `"jsontable"`, `"image"`, `"audio"`, `"video"`, `"binary"` - (No Arrow IPC due to browser incompatibility) --- ## Overview -msghandler provides a **transport-agnostic** communication layer with intelligent payload transport selection: +msghandler provides a **transport-agnostic** communication layer with intelligent payload transport selection (same API for Julia and JavaScript): | Transport | Payload Size | Method | |-----------|--------------|--------| @@ -100,68 +82,239 @@ msghandler provides a **transport-agnostic** communication layer with intelligen - **Chat Applications**: Text, images, audio, video in a single message - **File Transfer**: Efficient transfer of large files using claim-check pattern -- **IoT/Embedded**: Sensor data, telemetry, and analytics pipelines (MicroPython) -- **Cross-Platform Communication**: Interoperability between Julia, JavaScript, Python, and MicroPython systems +- **Cross-Platform Communication**: Interoperability between Julia backend and JavaScript frontend - **Any Transport**: Works with NATS, HTTP, WebSockets, WebRTC, or any custom transport mechanism +### Platform Differences + +| Feature | Julia Backend | JavaScript Frontend | +|---------|---------------|---------------------| +| **Arrow IPC** | ✅ Supported | ❌ Not supported (use "jsontable" instead) | +| **Full Type Support** | ✅ All 8 types | ✅ 7 of 8 types | +| **Transport Support** | ✅ NATS, HTTP, WebSockets, etc. | ✅ Fetch, WebSockets, HTTP | +| **File Server** | ✅ Plik, AWS S3, custom | ✅ Plik, custom HTTP server | + +### Supported Payload Types + +| Type | Julia | JavaScript | Description | +|------|-------|------------|-------------| +| `"text"` | ✅ | ✅ | Plain text | +| `"dictionary"` | ✅ | ✅ | JSON object | +| `"arrowtable"` | ✅ | ❌ | Arrow IPC table (Julia only) | +| `"jsontable"` | ✅ | ✅ | JSON table | +| `"image"` | ✅ | ✅ | Image data | +| `"audio"` | ✅ | ✅ | Audio data | +| `"video"` | ✅ | ✅ | Video data | +| `"binary"` | ✅ | ✅ | Binary data | + ### Key Design Principles - **Transport Agnostic**: Core API (`smartpack`/`smartunpack`) is decoupled from transport - **Claim-Check Pattern**: Efficient handling of large payloads via URL references -- **Type System**: Rich payload types (text, dictionary, arrowtable, jsontable, binary) -- **Cross-Platform**: Unified API across Julia, JavaScript, Python, and MicroPython - ---- - -## Cross-Platform Support - -| Platform | Implementation | Features | -|----------|----------------|----------| -| **Julia** | [`src/msghandler.jl`](src/msghandler.jl) | Full feature set, Arrow IPC, multiple dispatch | -| **JavaScript (Node.js)** | [`src/msghandler_ssr.js`](src/msghandler_ssr.js) | Node.js, async/await, Arrow IPC | -| **JavaScript (Browser)** | [`src/msghandler_csr.js`](src/msghandler_csr.js) | Browser, async/await, JSON table only | -| **Python** | [`src/msghandler.py`](src/msghandler.py) | Desktop Python, asyncio, type hints, Arrow IPC | -| **MicroPython** | [`src/msghandler_mpy.py`](src/msghandler_mpy.py) | Memory-constrained, synchronous API | - -### Platform Comparison - -| Feature | Julia | JavaScript | JavaScript (Browser) | Python | MicroPython | -|---------|-------|------------|----------------------|--------|-------------| -| Multiple Dispatch | ✅ Native | ❌ | ❌ | ❌ | ❌ | -| Async/Await | ❌ | ✅ Native | ✅ Native | ✅ Native | ⚠️ (uasyncio) | -| Type Safety | ✅ Strong | ⚠️ (TypeScript) | ⚠️ (TypeScript) | ✅ (Type hints) | ❌ | -| Arrow IPC | ✅ Native | ✅ Native | ❌ (Browser incompatible) | ✅ Native | ❌ | -| JSON Table | ✅ | ✅ | ✅ (Only table type) | ✅ | ⚠️ (Limited) | -| Direct Transport | ✅ | ✅ | ✅ | ✅ | ✅ | -| Link Transport | ✅ | ✅ | ✅ | ✅ | ⚠️ (Limited) | -| Handler Functions | ✅ | ✅ | ✅ | ✅ | ✅ | -| Cross-Platform API | ✅ | ✅ | ✅ | ✅ | ✅ | +- **Rich Type System**: Support for Arrow IPC, JSON tables, and binary data +- **Handler Abstraction**: Pluggable file server implementations (Plik, AWS S3, custom) --- ## Features +- ✅ **Cross-platform** - Same API for Julia backend and JavaScript frontend - ✅ **Transport-agnostic** - Core API works with any communication channel (NATS, HTTP, WebSockets, etc.) -- ✅ **Cross-platform** - Unified API for Julia, JavaScript, Python, and MicroPython - ✅ **Bi-directional** - Request-reply patterns with `reply_to` support - ✅ **Multi-payload** - Send multiple payloads of different types in one message - ✅ **Automatic transport selection** - Direct vs link based on payload size (threshold: 500KB) - ✅ **Claim-Check pattern** - Efficient large payload handling via URL references -- ✅ **Rich payload types** - Text, dictionary, Arrow IPC, JSON table, image, audio, video, binary +- ✅ **Rich payload types** - Text, dictionary, Arrow IPC (Julia only), JSON table, image, audio, video, binary - ✅ **Exponential backoff** - Reliable file server downloads with retry logic - ✅ **Correlation ID** - End-to-end message tracing - ✅ **Handler abstraction** - Pluggable file server implementations (Plik, AWS S3, custom) --- +## Installation + +### Julia Backend + +```julia +using Pkg +Pkg.add("msghandler") +``` + +Add to your `Project.toml`: +```toml +[deps] +msghandler = "path-to-msghandler" +``` + +### JavaScript Frontend + +**Using NPM:** +```bash +npm install msghandler-csr +``` + +**Using local source (development):** +```bash +cp ./src/msghandler-csr.js ./public/js/msghandler.js +``` + +Then import in your browser: +```javascript +import msghandlerCSR from './js/msghandler.js'; +``` + +--- + ## Quick Start +### Julia Backend + +#### Installation + +```julia +using Pkg +Pkg.add("msghandler") +``` + +Add to your `Project.toml`: +```toml +[deps] +msghandler = "path-to-msghandler" +``` + +#### Quick Start + +```julia +using msghandler + +# Data format: [(dataname, data, type), ...] +payload_1 = ("test_message", "Hello World", "text") +file_path_large_image = "./test/large_image.png" +file_data_large_image = read(file_path_large_image) +filename_large_image = basename(file_path_large_image) +payload_2 = (filename_large_image, file_data_large_image, "binary") + +payloads = [payload_1, payload_2] # List of tuples + +# Step 1: Create the message envelope (transport-agnostic) +envelope, envelope_json_str = msghandler.smartpack("test.topic", + payloads; + broker_url="nats.yiem.cc", + fileserver_url="http://192.168.88.104:8080") + +# Step 2: Send via your chosen transport (NATS in this example) +using NATS +conn = NATS.connect("nats.yiem.cc") +NATS.publish(conn, "test.topic", envelope_json_str; reply_to="test.replytopic") +NATS.drain(conn) + +# OR using request-reply pattern +reply = NATS.request(conn, "test.topic", envelope_json_str, timeout=10) +``` + +#### Receive Messages + +```julia +using msghandler, NATS + +conn = NATS.connect("nats.yiem.cc") +NATS.subscribe(conn, "test.topic") do msg + println("Received message on $(msg.subject)") + envelope_json_str = String(msg.payload) + + # Step 2: Unpack the envelope (transport-agnostic) + envelope = msghandler.smartunpack( + envelope_json_str; + max_retries = 5, + base_delay = 100, + max_delay = 5000 + ) + println(envelope.payloads[1]) +end +``` + +### JavaScript CSR Webapp + +#### Installation + +**Using NPM:** +```bash +npm install msghandler-csr +``` + +**Using ES Modules (browser):** +```javascript +import msghandlerCSR from './src/msghandler-csr.js'; +``` + +#### Features + +- ✅ No build tools required (works directly in browser) +- ✅ Uses native browser APIs (Web Crypto, Fetch, TextEncoder/Decoder) +- ✅ Browser-compatible payload types (no Arrow IPC dependency) + +#### Quick Start + +```javascript +import msghandlerCSR from './src/msghandler-csr.js'; + +const { smartpack, smartunpack, plikOneshotUpload, fetchWithBackoff } = msghandlerCSR; + +// Data format: [[dataname, data, type], ...] +const payload1 = ["test_message", "Hello World", "text"]; +const payload2 = ["config_data", { key: "value" }, "dictionary"]; + +const payloads = [payload1, payload2]; // Array of arrays + +// Step 1: Create the message envelope (transport-agnostic) +const [envelope, envelopeJsonStr] = await msghandlerCSR.smartpack("test.topic", payloads, { + broker_url: "nats.yiem.cc", + fileserver_url: "http://192.168.88.104:8080" +}); + +// Step 2: Send via your chosen transport (NATS, WebSocket, HTTP, etc.) +// await myTransport.publish("test.topic", envelopeJsonStr); + +// OR using fetch API for HTTP transport +fetch("http://localhost:3000/api/messages", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: envelopeJsonStr +}); +``` + +#### Receive Messages + +```javascript +import msghandlerCSR from './src/msghandler-csr.js'; + +const { smartunpack } = msghandlerCSR; + +// Option 1: Direct JSON string +const envelope = await msghandlerCSR.smartunpack(jsonString, { + fileserver_download_handler: msghandlerCSR.fetchWithBackoff, + max_retries: 5, + base_delay: 100, + max_delay: 5000 +}); + +// Option 2: From transport message object (e.g., NATS, WebSocket) +const envelope = await msghandlerCSR.smartunpack(natsMessage, { + fileserver_download_handler: msghandlerCSR.fetchWithBackoff +}); + +// Process payloads +for (const [dataname, data, type] of envelope.payloads) { + console.log(`${dataname}:`, data, `(type: ${type})`); +} +``` + ### Prerequisites 1. **Transport Channel** - Choose your communication channel: - - **NATS** (recommended): `docker run -p 4222:4222 nats:latest` - - **HTTP/WebSocket**: Any HTTP server or WebSocket endpoint - - **Custom**: Implement your own transport mechanism + - **NATS** (recommended): `docker run -p 4222:4222 nats:latest` + - **HTTP/WebSocket**: Any HTTP server or WebSocket endpoint + - **Custom**: Implement your own transport mechanism 2. **HTTP File Server** (optional, for payloads ≥ 500KB) - Install and run: ```bash @@ -175,21 +328,11 @@ msghandler provides a **transport-agnostic** communication layer with intelligen ### Send Your First Message -#### Julia - ```julia using msghandler, NATS # Data format: [(dataname, data, type), ...] -# Each tuple contains: -# 1. dataname: String name for the payload (e.g., "message") -# 2. data: The actual data (String, bytes, DataFrame, etc.) -# 3. type: String type indicator (e.g., "text", "binary", "dictionary") -# Supported types: "text", "dictionary", "arrowtable", "jsontable", -# "image", "audio", "video", "binary" - payload_1 = ("test_message", "Hello World", "text") - file_path_large_image = "./test/large_image.png" file_data_large_image = read(file_path_large_image) filename_large_image = basename(file_path_large_image) @@ -208,90 +351,15 @@ conn = NATS.connect("nats.yiem.cc") NATS.publish(conn, "test.topic", envelope_json_str; reply_to="test.replytopic") NATS.drain(conn) -# OR +# OR using request-reply pattern reply = NATS.request(conn, "test.topic", envelope_json_str, timeout=10) ``` -#### JavaScript (Node.js) - -```javascript -import msghandler from './src/msghandler_ssr.js'; - -const data = [["message", "Hello World", "text"]]; -const [env, env_json_str] = await msghandler.smartpack( - "/chat/room1", - data, - { broker_url: "nats://localhost:4222" } -); -console.log("Message sent!"); -``` - -#### JavaScript (Browser) - -```javascript -import msghandler from './src/msghandler_csr.js'; - -const data = [["message", "Hello World", "text"]]; -const [env, env_json_str] = await msghandler.smartpack( - "/chat/room1", - data, - { broker_url: "ws://localhost:4222" } -); -console.log("Message sent!"); -``` - -#### Python - -```python -from msghandler import smartpack - -# Data format: [(dataname, data, type), ...] -# Each tuple contains: -# 1. dataname: String name for the payload (e.g., "message") -# 2. data: The actual data (str, bytes, dict, list, etc.) -# 3. type: String type indicator (e.g., "text", "binary", "dictionary") -# Supported types: "text", "dictionary", "jsontable", "image", "audio", "video", "binary" - -data = [("message", "Hello World", "text")] -env, env_json_str = await smartpack( - "/chat/room1", - data, - broker_url="nats://localhost:4222", - fileserver_url="http://localhost:8080" -) -print("Envelope created:", env) - -# To send via transport (e.g., NATS, HTTP, WebSocket): -# transport_send_function("/chat/room1", env_json_str) -``` - -#### MicroPython - -```python -from msghandler import smartpack - -data = [("message", "Hello World", "text")] -env, env_json_str = smartpack( - "/chat/room1", - data, - broker_url="nats://localhost:4222", - fileserver_url="http://localhost:8080", - size_threshold=100000 -) -print("Envelope created:", env) - -# To send via transport (e.g., HTTP POST, WebSocket): -# transport_send_function("/chat/room1", env_json_str) -``` - ### Receive Your First Message -#### Julia - ```julia using msghandler, NATS -# Step 1: Receive message from your transport (NATS in this example) conn = NATS.connect("nats.yiem.cc") NATS.subscribe(conn, "test.topic") do msg println("Received message on $(msg.subject)") @@ -312,103 +380,9 @@ end ## API Reference -### Unified API Standard - -All platforms use the same input/output format for payloads. - -#### Input Format for `smartpack` - -**Format:** `[(dataname1, data1, type1), (dataname2, data2, type2), ...]` - -**Each tuple contains 3 elements:** -1. **`dataname`** (String) - Name for the payload (e.g., `"message"`, `"config.json"`, `"avatar.png"`) -2. **`data`** (Any) - The actual data to send (type depends on `type`): - - `text` → String - - `dictionary` → Dict/Object - - `arrowtable` → DataFrame/Arrow.Table - - `jsontable` → List of dicts/Vector{Dict} - - `image/audio/video/binary` → bytes/Uint8Array/Vector{UInt8} -3. **`type`** (String) - Payload type (required): - - `"text"` - Plain text string - - `"dictionary"` - JSON-serializable dictionary - - `"arrowtable"` - Apache Arrow IPC table (Desktop only) - - `"jsontable"` - JSON table (all platforms) - - `"image"` - Image data (PNG, JPG) - - `"audio"` - Audio data (WAV, MP3) - - `"video"` - Video data (MP4, AVI) - - `"binary"` - Generic binary data - -**Important:** -- Always wrap payloads in a list, even for single payloads -- Type must be one of the supported types above -- Large payloads (≥500KB by default) automatically use link transport - -**Example:** -```julia -# Julia -data = [ - ("message", "Hello World", "text"), - ("config", Dict("key" => "value"), "dictionary"), - ("file", file_bytes, "binary") -] -``` - -```python -# Python -data = [ - ("message", "Hello World", "text"), - ("config", {"key": "value"}, "dictionary"), - ("file", file_bytes, "binary") -] -``` - -```javascript -// JavaScript -const data = [ - ["message", "Hello World", "text"], - ["config", {key: "value"}, "dictionary"], - ["file", fileBytes, "binary"] -]; -``` - -### Common Payload Examples - -| Use Case | Data Format | Type | -|----------|-------------|------| -| Simple text message | `("message", "Hello World", "text")` | `"text"` | -| Configuration JSON | `("config", Dict("key" => "value"), "dictionary")` | `"dictionary"` | -| Table as Arrow | `("data", dataframe, "arrowtable")` | `"arrowtable"` | -| Table as JSON | `("data", [{"col": 1}], "jsontable")` | `"jsontable"` | -| Image file | `("avatar.png", read("file.png"), "image")` | `"image"` | -| Audio file | `("audio.mp3", audio_bytes, "audio")` | `"audio"` | -| Video file | `("video.mp4", video_bytes, "video")` | `"video"` | -| Binary file | `("data.bin", file_bytes, "binary")` | `"binary"` | - -#### Output Format for `smartunpack` -```json -{ - "correlation_id": "...", - "msg_id": "...", - "timestamp": "...", - "send_to": "...", - "msg_purpose": "...", - "sender_name": "...", - "sender_id": "...", - "receiver_name": "...", - "receiver_id": "...", - "reply_to": "...", - "reply_to_msg_id": "...", - "broker_url": "...", - "metadata": {...}, - "payloads": [(dataname1, data1, type1), (dataname2, data2, type2), ...] -} -``` - ### smartpack -Sends data via your chosen transport mechanism (NATS, HTTP, WebSocket, etc.) with intelligent transport selection (direct vs URL-based) based on payload size. - -#### Julia +Sends data via your chosen transport mechanism with intelligent transport selection. ```julia using msghandler @@ -427,125 +401,21 @@ env, env_json_str = msghandler.smartpack( receiver_id::String = "", reply_to::String = "", reply_to_msg_id::String = "", - is_publish::Bool = true, - NATS_connection::Union{NATS.Connection, Nothing} = nothing, msg_id::String = string(uuid4()), sender_id::String = string(uuid4()) ) # Returns: ::Tuple{msg_envelope_v1, String} ``` -#### JavaScript (Node.js) - -```javascript -import msghandler from './src/msghandler_ssr.js'; - -const [env, env_json_str] = await msghandler.smartpack( - subject, - data, // Array of [dataname, data, type] tuples - { - broker_url: 'nats://localhost:4222', - fileserver_url: 'http://localhost:8080', - fileserver_upload_handler: msghandler.plikOneshotUpload, - size_threshold: 500_000, - correlation_id: uuidv4(), - msg_purpose: 'chat', - sender_name: 'msghandler', - receiver_name: '', - receiver_id: '', - reply_to: '', - reply_to_msg_id: '', - is_publish: true, - nats_connection: null, - msg_id: uuidv4(), - sender_id: uuidv4() - } -); -// Returns: Promise<[env, env_json_str]> -``` - -#### JavaScript (Browser) - -```javascript -import msghandler from './src/msghandler_csr.js'; - -const [env, env_json_str] = await msghandler.smartpack( - subject, - data, - { - broker_url: 'ws://localhost:4222', - fileserver_url: 'http://localhost:8080', - fileserver_upload_handler: msghandler.plikOneshotUpload, - size_threshold: 500_000, - correlation_id: uuidv4(), - msg_purpose: 'chat', - sender_name: 'msghandler', - receiver_name: '', - receiver_id: '', - reply_to: '', - reply_to_msg_id: '', - is_publish: true, - nats_connection: null, - msg_id: uuidv4(), - sender_id: uuidv4() - } -); -// Returns: Promise<[env, env_json_str]> -``` - -#### Python - -```python -from msghandler import msghandler - -env, env_json_str = await msghandler.smartpack( - subject: str, - data: List[Tuple[str, Any, str]], - broker_url: str = "nats://localhost:4222", - fileserver_url: str = "http://localhost:8080", - fileserver_upload_handler: Callable = plik_oneshot_upload, - size_threshold: int = 500_000, - correlation_id: str = None, - msg_purpose: str = "chat", - sender_name: str = "msghandler", - receiver_name: str = "", - receiver_id: str = "", - reply_to: str = "", - reply_to_msg_id: str = "", - is_publish: bool = True, - nats_connection: Any = None, - msg_id: str = None, - sender_id: str = None -) -# Returns: Tuple[Dict, str] -``` - -#### MicroPython - -```python -from msghandler import smartpack - -# Limited to direct transport (< 100KB threshold) -env, env_json_str = smartpack( - "/device/config", - [("config", config, "dictionary")], - broker_url="nats://localhost:4222", - size_threshold=100000 -) -print("Message sent!") -``` - ### smartunpack -Receives and processes messages from NATS, handling both direct and link transport. - -#### Julia +Receives and processes messages from your transport. ```julia using msghandler env = msghandler.smartunpack( - msg::NATS.Msg; + msg_json_str::String; fileserver_download_handler::Function = _fetch_with_backoff, max_retries::Int = 5, base_delay::Int = 100, @@ -554,80 +424,14 @@ env = msghandler.smartunpack( # Returns: ::JSON.Object{String, Any} ``` -#### JavaScript (Node.js) - -```javascript -import msghandler from './src/msghandler_ssr.js'; - -const env = await msghandler.smartunpack( - msg, - { - fileserver_download_handler: msghandler.fetchWithBackoff, - max_retries: 5, - base_delay: 100, - max_delay: 5000 - } -); -// Returns: Promise -``` - -#### JavaScript (Browser) - -```javascript -import msghandler from './src/msghandler_csr.js'; - -const env = await msghandler.smartunpack( - msg, - { - fileserver_download_handler: msghandler.fetchWithBackoff, - max_retries: 5, - base_delay: 100, - max_delay: 5000 - } -); -// Returns: Promise -``` - -#### Python - -```python -from msghandler import msghandler - -env = await msghandler.smartunpack( - msg, - fileserver_download_handler=fetch_with_backoff, - max_retries=5, - base_delay=100, - max_delay=5000 -) -# Returns: Dict with "payloads" key -``` - -#### MicroPython - -```python -from msghandler import smartunpack - -env = smartunpack( - msg, - fileserver_download_handler=_sync_fileserver_download, - max_retries=3, - base_delay=100, - max_delay=1000 -) -print("Received payloads:", env["payloads"]) -``` - --- -## Cross-Platform Examples +## Examples ### Example 1: Chat with Mixed Content Send text, image, and large file in one message. -#### Julia - ```julia using msghandler @@ -640,95 +444,9 @@ data = [ env, env_json_str = smartpack("/chat/room1", data; fileserver_url="http://localhost:8080") ``` -#### JavaScript (Node.js) - -```javascript -import msghandler from './src/msghandler_ssr.js'; - -const data = [ - ["message_text", "Hello!", "text"], - ["user_avatar", imageData, "image"], - ["large_document", largeFileData, "binary"] -]; - -const [env, env_json_str] = await msghandler.smartpack( - "/chat/room1", - data, - { fileserver_url: 'http://localhost:8080' } -); -``` - -#### JavaScript (Browser) - -```javascript -import msghandler from './src/msghandler_csr.js'; - -const data = [ - ["message_text", "Hello!", "text"], - ["user_avatar", image_data, "image"] -]; - -const [env, env_json_str] = await msghandler.smartpack( - "/chat/room1", - data, - { broker_url: 'ws://localhost:4222' } -); -console.log("Message sent!"); -``` - -#### Python - -```python -import asyncio -from msghandler import smartpack - -async def main(): - image_data = open("user_avatar.png", "rb").read() - large_file_data = open("large_document.pdf", "rb").read() - - data = [ - ("message_text", "Hello!", "text"), - ("user_avatar", image_data, "image"), - ("large_document", large_file_data, "binary") - ] - - env, env_json_str = await smartpack( - "/chat/room1", - data, - fileserver_url="http://localhost:8080" - ) - print("Message sent!") - -if __name__ == "__main__": - asyncio.run(main()) -``` - -#### MicroPython - -```python -from msghandler import smartpack - -# Note: MicroPython only supports direct transport (< 100KB threshold) -# Large files must be uploaded via file server first -data = [ - ("message_text", "Hello!", "text"), - ("user_avatar", image_bytes, "image") -] - -env, env_json_str = smartpack( - "/chat/room1", - data, - broker_url="nats://localhost:4222", - size_threshold=100000 -) -print("Message sent!") -``` - ### Example 2: Dictionary Exchange -Send configuration data between platforms. - -#### Julia +Send configuration data. ```julia using msghandler @@ -743,72 +461,10 @@ data = [("config", config, "dictionary")] env, env_json_str = smartpack("/device/config", data) ``` -#### JavaScript (Node.js) - -```javascript -import msghandler from './src/msghandler_ssr.js'; - -const config = { - wifi_ssid: "MyNetwork", - wifi_password: "password123", - update_interval: 60 -}; - -const [env, env_json_str] = await msghandler.smartpack( - "/device/config", - [["config", config, "dictionary"]] -); -console.log("Message sent!"); -``` - -#### Python - -```python -import asyncio -from msghandler import smartpack - -async def main(): - config = { - "wifi_ssid": "MyNetwork", - "wifi_password": "password123", - "update_interval": 60 - } - - data = [("config", config, "dictionary")] - env, env_json_str = await smartpack("/device/config", data) - print("Message sent!") - -if __name__ == "__main__": - asyncio.run(main()) -``` - -#### MicroPython - -```python -from msghandler import smartpack - -config = { - "wifi_ssid": "MyNetwork", - "wifi_password": "password123", - "update_interval": 60 -} - -data = [("config", config, "dictionary")] -env, env_json_str = smartpack( - "/device/config", - data, - broker_url="nats://localhost:4222", - size_threshold=100000 -) -print("Message sent!") -``` - -### Example 3: Table Data (Arrow IPC) +### Example 3: Table Data (Arrow IPC - Julia Only) Send tabular data using Apache Arrow IPC format. -#### Julia - ```julia using msghandler using DataFrames @@ -823,87 +479,26 @@ data = [("students", df, "arrowtable")] env, env_json_str = smartpack("/data/analysis", data) ``` -#### JavaScript (Node.js) +### Example 3b: Table Data (JSON - JavaScript Compatible) + +For JavaScript frontend, use `jsontable` instead of `arrowtable`: ```javascript -import msghandler from './src/msghandler_ssr.js'; - -const df = [ +const tableData = [ { id: 1, name: "Alice", score: 95 }, { id: 2, name: "Bob", score: 88 }, { id: 3, name: "Charlie", score: 92 } ]; -const [env, env_json_str] = await msghandler.smartpack( - "/data/analysis", - [["students", df, "arrowtable"]] -); -``` - -#### Python - -```python -from msghandler import msghandler -import pandas as pd - -df = pd.DataFrame({ - "id": [1, 2, 3], - "name": ["Alice", "Bob", "Charlie"], - "score": [95, 88, 92] -}) - -data = [("students", df, "arrowtable")] -env, env_json_str = await msghandler.smartpack("/data/analysis", data) -``` - -#### JavaScript (Browser) - -```javascript -import msghandler from './src/msghandler_csr.js'; - -const df = [ - { id: 1, name: "Alice", score: 95 }, - { id: 2, name: "Bob", score: 88 }, - { id: 3, name: "Charlie", score: 92 } -]; - -const [env, env_json_str] = await msghandler.smartpack( - "/data/analysis", - [["students", df, "jsontable"]], - { broker_url: 'ws://localhost:4222' } -); -console.log("Message sent!"); -``` - -#### MicroPython - -```python -from msghandler import smartpack - -# Note: MicroPython only supports direct transport (< 100KB threshold) -# MicroPython doesn't support Arrow IPC, only jsontable -df = [ - {"id": 1, "name": "Alice", "score": 95}, - {"id": 2, "name": "Bob", "score": 88}, - {"id": 3, "name": "Charlie", "score": 92} -] - -data = [("students", df, "jsontable")] -env, env_json_str = smartpack( - "/data/analysis", - data, - broker_url="nats://localhost:4222", - size_threshold=100000 -) -print("Message sent!") +const [envelope, envelopeJsonStr] = await msghandlerCSR.smartpack("/data/analysis", [ + ["students", tableData, "jsontable"] +]); ``` ### Example 4: Request-Response Pattern Bi-directional communication with reply-to support. -#### Julia - ```julia using msghandler, NATS @@ -938,126 +533,11 @@ NATS.subscribe(conn, "/device/command") do msg end ``` -#### JavaScript (Node.js) - -```javascript -import msghandler from './src/msghandler_ssr.js'; -import { connect } from 'nats'; - -// Requester -const [env, env_json_str] = await msghandler.smartpack( - "/device/command", - [["command", { action: "read_sensor" }, "dictionary"]], - { broker_url: 'nats://localhost:4222', reply_to: '/device/response' } -); -const nc = await connect({ port: 4222 }); -nc.publish("/device/command", env_json_str); -await nc.flush(); - -// Receiver (in separate application) -const nc = await connect({ port: 4222 }); -const sub = nc.subscribe("/device/command"); -for await (const msg of sub) { - const env = await msghandler.smartunpack(msg); - console.log("Received command:", env.payloads); - - const response_env, response_json = await msghandler.smartpack( - "/device/response", - [["result", { value: 42 }, "dictionary"]], - { reply_to: '/device/command', reply_to_msg_id: env.msg_id } - ); - - nc.publish("/device/response", response_json); - await nc.flush(); -} -``` - -#### Python - -```python -import asyncio -from msghandler import smartpack - -async def main(): - # Requester - env, env_json_str = await smartpack( - "/device/command", - [("command", {"action": "read_sensor"}, "dictionary")], - broker_url="nats://localhost:4222", - reply_to="/device/response" - ) - print("Request sent!") - - # Receiver (in separate application) - # await nats_consumer.next() - # env = await smartunpack(msg) - # Process request and send response - # response_env, response_json = await smartpack( - # "/device/response", - # [("result", {"value": 42}, "dictionary")], - # reply_to="/device/command", - # reply_to_msg_id=env["msg_id"] - # ) - -if __name__ == "__main__": - asyncio.run(main()) -``` - -#### JavaScript (Browser) - -```javascript -import msghandler from './src/msghandler_csr.js'; - -// Requester -const [env, env_json_str] = await msghandler.smartpack( - "/device/command", - [["command", { action: "read_sensor" }, "dictionary"]], - { broker_url: 'ws://localhost:4222', reply_to: '/device/response' } -); -console.log("Request sent!"); - -// Receiver (in separate application) -// const msg = await natsConsumer.next(); -// const env = await msghandler.smartunpack(msg); -// console.log("Received command:", env.payloads); -// const response_env, response_json = await msghandler.smartpack( -// "/device/response", -// [["result", { value: 42 }, "dictionary"]], -// { reply_to: '/device/command', reply_to_msg_id: env.msg_id } -// ); -``` - -#### MicroPython - -```python -from msghandler import smartpack - -# Requester -env, env_json_str = smartpack( - "/device/command", - [("command", {"action": "read_sensor"}, "dictionary")], - broker_url="nats://localhost:4222", - reply_to="/device/response", - size_threshold=100000 -) -print("Request sent!") -``` - --- ## Testing -### Test File Organization - -| Platform | Sender Tests | Receiver Tests | -|----------|--------------|----------------| -| **Julia** | `test/test_julia_*_sender.jl` | `test/test_julia_*_receiver.jl` | -| **JavaScript** | `test/test_js_*_sender.js` | `test/test_js_*_receiver.js` | -| **Python** | `test/test_py_*_sender.py` | `test/test_py_*_receiver.py` | - -### Run Tests - -#### Julia +### Julia Backend ```bash # Text message exchange @@ -1081,161 +561,44 @@ julia test/test_julia_table_sender.jl julia test/test_julia_table_receiver.jl ``` -#### JavaScript (Node.js) +### JavaScript Frontend +To test the JavaScript version in a browser: + +1. Start a file server for testing: ```bash -# Text message exchange -node test/test_js_text_sender.js -node test/test_js_text_receiver.js - -# Dictionary exchange -node test/test_js_dictionary_sender.js -node test/test_js_dictionary_receiver.js - -# Binary transfer -node test/test_js_binary_sender.js -node test/test_js_binary_receiver.js - -# Table exchange -node test/test_js_table_sender.js -node test/test_js_table_receiver.js +mkdir -p /tmp/fileserver +python3 -m http.server 8080 --directory /tmp/fileserver ``` -#### Python - +2. Start a NATS server (optional): ```bash -# Text message exchange -python3 test/test_py_text_sender.py -python3 test/test_py_text_receiver.py - -# Dictionary exchange -python3 test/test_py_dictionary_sender.py -python3 test/test_py_dictionary_receiver.py - -# Binary transfer -python3 test/test_py_binary_sender.py -python3 test/test_py_binary_receiver.py - -# Table exchange -python3 test/test_py_table_sender.py -python3 test/test_py_table_receiver.py +docker run -p 4222:4222 nats:latest ``` ---- - -## Browser Deployment - -### Using with Node.js Build Tools - -The browser implementation (`src/msghandler_csr.js`) can be bundled for production deployment using modern JavaScript build tools. - -#### Prerequisites - +3. Open the test HTML file in a browser: ```bash -# Install the browser-compatible NATS client -npm install nats.ws -``` - -#### Vite (Recommended) - -```bash -npm create vite@latest my-app -- --template vanilla -cd my-app -npm install nats.ws -``` - -In `vite.config.js`: -```javascript -import { defineConfig } from 'vite'; -export default defineConfig({ - resolve: { - alias: { - 'nats.ws': 'nats.ws/dist/esm/browser.js' - } - } -}); -``` - -Build command: -```bash -npm run build # Outputs to dist/ folder -``` - -#### Webpack - -```bash -npm install webpack webpack-cli --save-dev -npm install nats.ws -``` - -In `webpack.config.js`: -```javascript -module.exports = { - entry: './src/index.js', - output: { - filename: 'bundle.js', - path: __dirname + '/dist' - }, - resolve: { - alias: { - 'nats.ws': 'nats.ws/dist/esm/browser.js' - } - } -}; -``` - -Build command: -```bash -npx webpack -``` - -#### esbuild (Simple & Fast) - -```bash -npm install esbuild nats.ws --save-dev -``` - -Create `build.js`: -```javascript -import esbuild from 'esbuild'; - -esbuild.buildSync({ - entryPoints: ['src/msghandler_csr.js'], - bundle: true, - outfile: 'dist/msghandler-csr-bundle.js', - format: 'esm', - platform: 'browser', - target: 'es2020' -}); -``` - -Build command: -```bash -node build.js -``` - -### Using in Your HTML - -```html +# Create test file (example) +cat > test-browser.html << 'EOF' - - My App - +msghandler CSR Test - - + +EOF + +python3 -m http.server 8081 +# Open http://localhost:8081/test-browser.html ``` --- @@ -1244,11 +607,56 @@ node build.js For detailed architecture and implementation information, see: -- [`docs/architecture.md`](docs/architecture.md) - Cross-platform architecture, API parity, platform-specific patterns +- [`docs/architecture.md`](docs/architecture.md) - Architecture and design patterns - [`docs/requirements.md`](docs/requirements.md) - Business requirements and user stories - [`docs/spec.md`](docs/spec.md) - Technical specification and contracts - [`docs/walkthrough.md`](docs/walkthrough.md) - Real-world application building guides +### JavaScript API Reference + +The JavaScript version provides the same core functionality as Julia, with browser-compatible APIs: + +#### smartpack + +```javascript +const [envelope, envelopeJsonStr] = await msghandlerCSR.smartpack( + subject, + data, // [[dataname, data, type], ...] + options +); +``` + +#### smartunpack + +```javascript +const envelope = await msghandlerCSR.smartunpack(msg, options); +// envelope.payloads = [[dataname, data, type], ...] +``` + +#### plikOneshotUpload + +```javascript +const result = await msghandlerCSR.plikOneshotUpload( + fileServerUrl, + dataname, + data // Uint8Array +); +// result = { status, uploadid, fileid, url } +``` + +#### fetchWithBackoff + +```javascript +const data = await msghandlerCSR.fetchWithBackoff( + url, + maxRetries, + baseDelay, + maxDelay, + correlationId +); +// data = Uint8Array +``` + --- ## License diff --git a/src/msghandler.js b/src/msghandler-ssr.js similarity index 100% rename from src/msghandler.js rename to src/msghandler-ssr.js From 42b68d5bee1c5f5789097b4643e081fcd565d440 Mon Sep 17 00:00:00 2001 From: narawat Date: Sun, 24 May 2026 11:40:27 +0700 Subject: [PATCH 02/16] update --- AI_prompt.md | 12 ++++++++---- test/test_js_mix_payloads_receiver.js | 2 +- test/test_js_mix_payloads_sender.js | 2 +- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/AI_prompt.md b/AI_prompt.md index 5cbb13a..b485996 100644 --- a/AI_prompt.md +++ b/AI_prompt.md @@ -267,10 +267,14 @@ What is the main interface of this package? - - - - +read the following files: +- ./src/msghandler.jl +- ./test/test_julia_mix_payloads_sender.jl +- ./src/msghandler-csr.js +- ./README.md +I want to add: +1) sending jsontable and arrowtable Julia example to README.md +2) sending image and jsontable Javascript(browser) example to README.md diff --git a/test/test_js_mix_payloads_receiver.js b/test/test_js_mix_payloads_receiver.js index 6f79b39..34b4110 100644 --- a/test/test_js_mix_payloads_receiver.js +++ b/test/test_js_mix_payloads_receiver.js @@ -6,7 +6,7 @@ * any combination and any number of mixed content can be received correctly. */ -const msghandler = require('../src/msghandler.js'); +const msghandler = require('../src/msghandler-csr.js'); const nats = require('nats'); const crypto = require('crypto'); diff --git a/test/test_js_mix_payloads_sender.js b/test/test_js_mix_payloads_sender.js index c88eecb..5123c56 100644 --- a/test/test_js_mix_payloads_sender.js +++ b/test/test_js_mix_payloads_sender.js @@ -6,7 +6,7 @@ * any combination and any number of mixed content can be sent correctly. */ -const msghandler = require('../src/msghandler.js'); +const msghandler = require('../src/msghandler-csr.js'); const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); From b8339897f3cc822d997c8cc92f48bfd1c0718444 Mon Sep 17 00:00:00 2001 From: narawat Date: Sun, 24 May 2026 12:39:19 +0700 Subject: [PATCH 03/16] update doc --- AI_prompt.md | 9 +++++ README.md | 56 ++++++++++++++++++++++++++--- test/test_js_mix_payloads_sender.js | 2 +- 3 files changed, 62 insertions(+), 5 deletions(-) diff --git a/AI_prompt.md b/AI_prompt.md index b485996..759d058 100644 --- a/AI_prompt.md +++ b/AI_prompt.md @@ -282,6 +282,15 @@ I want to add: +read the following files: +- ./README.md +- ./src/msghandler.jl +- ./test/test_julia_mix_payloads_sender.jl +- ./src/msghandler-csr.js +I want to: +1) add sending jsontable and arrowtable julia example in README.md +2) add sending jsontable and image Javascript example in README.md + diff --git a/README.md b/README.md index c87a7ca..5300e06 100644 --- a/README.md +++ b/README.md @@ -263,8 +263,11 @@ const { smartpack, smartunpack, plikOneshotUpload, fetchWithBackoff } = msghandl // Data format: [[dataname, data, type], ...] const payload1 = ["test_message", "Hello World", "text"]; const payload2 = ["config_data", { key: "value" }, "dictionary"]; +const payload3 = ["table_data", [{id: 1, name: "Alice"}], "jsontable"]; +const image_bytes = new Uint8Array([1, 2, 3, ...]); // Image data +const payload4 = ["user_avatar", image_bytes, "image"]; -const payloads = [payload1, payload2]; // Array of arrays +const payloads = [payload1, payload2, payload3, payload4]; // Array of arrays // Step 1: Create the message envelope (transport-agnostic) const [envelope, envelopeJsonStr] = await msghandlerCSR.smartpack("test.topic", payloads, { @@ -463,7 +466,7 @@ env, env_json_str = smartpack("/device/config", data) ### Example 3: Table Data (Arrow IPC - Julia Only) -Send tabular data using Apache Arrow IPC format. +Send tabular data using Apache Arrow IPC format for efficient binary serialization: ```julia using msghandler @@ -479,7 +482,25 @@ data = [("students", df, "arrowtable")] env, env_json_str = smartpack("/data/analysis", data) ``` -### Example 3b: Table Data (JSON - JavaScript Compatible) +### Example 3b: Table Data (JSON - Julia Compatible) + +For cross-platform compatibility or when Arrow IPC is not needed, use `jsontable`: + +```julia +using msghandler +using DataFrames + +df = DataFrame( + id = [1, 2, 3], + name = ["Alice", "Bob", "Charlie"], + score = [95, 88, 92] +) + +data = [("students", df, "jsontable")] +env, env_json_str = smartpack("/data/analysis", data) +``` + +### Example 3c: Table Data (JSON - JavaScript Compatible) For JavaScript frontend, use `jsontable` instead of `arrowtable`: @@ -495,7 +516,34 @@ const [envelope, envelopeJsonStr] = await msghandlerCSR.smartpack("/data/analysi ]); ``` -### Example 4: Request-Response Pattern +### Example 4: Image Transmission (Julia) + +Send image data directly in messages: + +```julia +using msghandler + +# Read image file +image_path = "./path/to/image.jpg" +image_data = read(image_path) + +data = [("user_avatar", image_data, "image")] +env, env_json_str = smartpack("/chat/room1", data; fileserver_url="http://localhost:8080") +``` + +### Example 5: Image Transmission (JavaScript) + +Send image data directly in messages: + +```javascript +const image_data = new Uint8Array([1, 2, 3, ...]); // Image bytes + +const [envelope, envelopeJsonStr] = await msghandlerCSR.smartpack("/chat/room1", [ + ["user_avatar", image_data, "image"] +]); +``` + +### Example 6: Request-Response Pattern Bi-directional communication with reply-to support. diff --git a/test/test_js_mix_payloads_sender.js b/test/test_js_mix_payloads_sender.js index 5123c56..d5dc950 100644 --- a/test/test_js_mix_payloads_sender.js +++ b/test/test_js_mix_payloads_sender.js @@ -6,7 +6,7 @@ * any combination and any number of mixed content can be sent correctly. */ -const msghandler = require('../src/msghandler-csr.js'); +const msghandler = require('../src/msghandler-csr.js').default; const crypto = require('crypto'); const fs = require('fs'); const path = require('path'); From 86224c1dafb7141054f17a4079c02e701939dac0 Mon Sep 17 00:00:00 2001 From: narawat Date: Sun, 24 May 2026 12:52:00 +0700 Subject: [PATCH 04/16] update docs --- README.md | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5300e06..613544c 100644 --- a/README.md +++ b/README.md @@ -264,8 +264,8 @@ const { smartpack, smartunpack, plikOneshotUpload, fetchWithBackoff } = msghandl const payload1 = ["test_message", "Hello World", "text"]; const payload2 = ["config_data", { key: "value" }, "dictionary"]; const payload3 = ["table_data", [{id: 1, name: "Alice"}], "jsontable"]; -const image_bytes = new Uint8Array([1, 2, 3, ...]); // Image data -const payload4 = ["user_avatar", image_bytes, "image"]; +const file_data = fs.readFileSync('./test/large_image.png'); // Read image from file +const payload4 = ["user_avatar", file_data, "binary"]; const payloads = [payload1, payload2, payload3, payload4]; // Array of arrays @@ -524,10 +524,10 @@ Send image data directly in messages: using msghandler # Read image file -image_path = "./path/to/image.jpg" +image_path = "./test/large_image.png" image_data = read(image_path) -data = [("user_avatar", image_data, "image")] +data = [("user_avatar", image_data, "binary")] env, env_json_str = smartpack("/chat/room1", data; fileserver_url="http://localhost:8080") ``` @@ -536,14 +536,32 @@ env, env_json_str = smartpack("/chat/room1", data; fileserver_url="http://localh Send image data directly in messages: ```javascript -const image_data = new Uint8Array([1, 2, 3, ...]); // Image bytes +import fs from 'fs'; + +const file_data = fs.readFileSync('./test/large_image.png'); const [envelope, envelopeJsonStr] = await msghandlerCSR.smartpack("/chat/room1", [ - ["user_avatar", image_data, "image"] + ["user_avatar", file_data, "binary"] ]); ``` -### Example 6: Request-Response Pattern +### Example 6: Table Data (JavaScript) + +For JavaScript frontend, use `jsontable` for tabular data: + +```javascript +const tableData = [ + { id: 1, name: "Alice", score: 95 }, + { id: 2, name: "Bob", score: 88 }, + { id: 3, name: "Charlie", score: 92 } +]; + +const [envelope, envelopeJsonStr] = await msghandlerCSR.smartpack("/data/analysis", [ + ["students", tableData, "jsontable"] +]); +``` + +### Example 7: Request-Response Pattern Bi-directional communication with reply-to support. From a134bd44bdffdfc5d15cefd8b7d00ba00b825fee Mon Sep 17 00:00:00 2001 From: narawat Date: Sun, 24 May 2026 12:57:45 +0700 Subject: [PATCH 05/16] update --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 613544c..960ab3c 100644 --- a/README.md +++ b/README.md @@ -263,7 +263,7 @@ const { smartpack, smartunpack, plikOneshotUpload, fetchWithBackoff } = msghandl // Data format: [[dataname, data, type], ...] const payload1 = ["test_message", "Hello World", "text"]; const payload2 = ["config_data", { key: "value" }, "dictionary"]; -const payload3 = ["table_data", [{id: 1, name: "Alice"}], "jsontable"]; +const payload3 = ["table_data", [{id: 1, name: "Alice"}, {id: 2, name: "Ton"}], "jsontable"]; const file_data = fs.readFileSync('./test/large_image.png'); // Read image from file const payload4 = ["user_avatar", file_data, "binary"]; @@ -332,6 +332,7 @@ for (const [dataname, data, type] of envelope.payloads) { ### Send Your First Message ```julia +# Julia using msghandler, NATS # Data format: [(dataname, data, type), ...] @@ -361,6 +362,7 @@ reply = NATS.request(conn, "test.topic", envelope_json_str, timeout=10) ### Receive Your First Message ```julia +# Julia using msghandler, NATS conn = NATS.connect("nats.yiem.cc") From 5d2b46eb3874aa322732e998394cb48103c070d3 Mon Sep 17 00:00:00 2001 From: narawat Date: Sun, 24 May 2026 13:00:30 +0700 Subject: [PATCH 06/16] update --- README.md | 53 ----------------------------------------------------- 1 file changed, 53 deletions(-) diff --git a/README.md b/README.md index 960ab3c..14b44d1 100644 --- a/README.md +++ b/README.md @@ -328,59 +328,6 @@ for (const [dataname, data, type] of envelope.payloads) { mkdir -p /tmp/fileserver python3 -m http.server 8080 --directory /tmp/fileserver ``` - -### Send Your First Message - -```julia -# Julia -using msghandler, NATS - -# Data format: [(dataname, data, type), ...] -payload_1 = ("test_message", "Hello World", "text") -file_path_large_image = "./test/large_image.png" -file_data_large_image = read(file_path_large_image) -filename_large_image = basename(file_path_large_image) -payload_2 = (filename_large_image, file_data_large_image, "binary") - -payloads = [payload_1, payload_2] # List of tuples - -# Step 1: Create the message envelope (transport-agnostic) -envelope, envelope_json_str = msghandler.smartpack("test.topic", - payloads; - broker_url="nats.yiem.cc", - fileserver_url="http://192.168.88.104:8080") - -# Step 2: Send via your chosen transport (NATS in this example) -conn = NATS.connect("nats.yiem.cc") -NATS.publish(conn, "test.topic", envelope_json_str; reply_to="test.replytopic") -NATS.drain(conn) - -# OR using request-reply pattern -reply = NATS.request(conn, "test.topic", envelope_json_str, timeout=10) -``` - -### Receive Your First Message - -```julia -# Julia -using msghandler, NATS - -conn = NATS.connect("nats.yiem.cc") -NATS.subscribe(conn, "test.topic") do msg - println("Received message on $(msg.subject)") - envelope_json_str = String(msg.payload) - - # Step 2: Unpack the envelope (transport-agnostic) - envelope = msghandler.smartunpack( - envelope_json_str; - max_retries = 5, - base_delay = 100, - max_delay = 5000 - ) - println(envelope.payloads[1]) -end -``` - --- ## API Reference From e7c78cf40d162b437a9258f6903bd8da266ddf57 Mon Sep 17 00:00:00 2001 From: narawat Date: Sun, 24 May 2026 13:03:28 +0700 Subject: [PATCH 07/16] update --- README.md | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 14b44d1..d8c5328 100644 --- a/README.md +++ b/README.md @@ -167,6 +167,23 @@ import msghandlerCSR from './js/msghandler.js'; ## Quick Start +### Prerequisites + +1. **Transport Channel** - Choose your communication channel: + - **NATS** (recommended): `docker run -p 4222:4222 nats:latest` + - **HTTP/WebSocket**: Any HTTP server or WebSocket endpoint + - **Custom**: Implement your own transport mechanism + +2. **HTTP File Server** (optional, for payloads ≥ 500KB) - Install and run: + ```bash + # Using Plik + docker run -p 8080:8080 -v /tmp/fileserver:/var/lib/plik -e PLIK_ADMIN_PASSWORD=admin plik/plik + + # OR using Python HTTP server + mkdir -p /tmp/fileserver + python3 -m http.server 8080 --directory /tmp/fileserver + ``` + ### Julia Backend #### Installation @@ -311,23 +328,6 @@ for (const [dataname, data, type] of envelope.payloads) { console.log(`${dataname}:`, data, `(type: ${type})`); } ``` - -### Prerequisites - -1. **Transport Channel** - Choose your communication channel: - - **NATS** (recommended): `docker run -p 4222:4222 nats:latest` - - **HTTP/WebSocket**: Any HTTP server or WebSocket endpoint - - **Custom**: Implement your own transport mechanism - -2. **HTTP File Server** (optional, for payloads ≥ 500KB) - Install and run: - ```bash - # Using Plik - docker run -p 8080:8080 -v /tmp/fileserver:/var/lib/plik -e PLIK_ADMIN_PASSWORD=admin plik/plik - - # OR using Python HTTP server - mkdir -p /tmp/fileserver - python3 -m http.server 8080 --directory /tmp/fileserver - ``` --- ## API Reference From bc51f20e4842444b0193c176e650191321916f3a Mon Sep 17 00:00:00 2001 From: narawat Date: Sun, 24 May 2026 13:05:39 +0700 Subject: [PATCH 08/16] update --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d8c5328..673d335 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,7 @@ Add to your `Project.toml`: msghandler = "path-to-msghandler" ``` -#### Quick Start +#### Send Messages ```julia using msghandler @@ -270,7 +270,7 @@ import msghandlerCSR from './src/msghandler-csr.js'; - ✅ Uses native browser APIs (Web Crypto, Fetch, TextEncoder/Decoder) - ✅ Browser-compatible payload types (no Arrow IPC dependency) -#### Quick Start +#### Send Messages ```javascript import msghandlerCSR from './src/msghandler-csr.js'; From b11dd456c0b0fab36d0baa213fb664fd5c0558a1 Mon Sep 17 00:00:00 2001 From: narawat Date: Sun, 24 May 2026 13:11:25 +0700 Subject: [PATCH 09/16] update --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 673d335..1845761 100644 --- a/README.md +++ b/README.md @@ -380,7 +380,7 @@ env = msghandler.smartunpack( ## Examples -### Example 1: Chat with Mixed Content +### Example 1: Chat with Mixed Content (Julia) Send text, image, and large file in one message. @@ -396,7 +396,7 @@ data = [ env, env_json_str = smartpack("/chat/room1", data; fileserver_url="http://localhost:8080") ``` -### Example 2: Dictionary Exchange +### Example 2: Dictionary Exchange (Julia) Send configuration data. From 481fef9f701514772cd7d874d4d0d9c5fce3b567 Mon Sep 17 00:00:00 2001 From: narawat Date: Sun, 24 May 2026 13:35:08 +0700 Subject: [PATCH 10/16] update readme --- README.md | 49 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1845761..512489a 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,7 @@ Add to your `Project.toml`: msghandler = "path-to-msghandler" ``` -#### Send Messages +#### Send Messages (Publish - Subscribe Pattern) ```julia using msghandler @@ -229,7 +229,7 @@ NATS.drain(conn) reply = NATS.request(conn, "test.topic", envelope_json_str, timeout=10) ``` -#### Receive Messages +#### Receive Messages (Publish - Subscribe Pattern) ```julia using msghandler, NATS @@ -250,6 +250,51 @@ NATS.subscribe(conn, "test.topic") do msg end ``` +#### OR +#### Send Messages (Request - Reply Pattern) + +```julia +using msghandler + +# Data format: [(dataname, data, type), ...] +payload_1 = ("test_message", "Hello World", "text") +file_path_large_image = "./test/large_image.png" +file_data_large_image = read(file_path_large_image) +filename_large_image = basename(file_path_large_image) +payload_2 = (filename_large_image, file_data_large_image, "binary") + +payloads = [payload_1, payload_2] # List of tuples + +# Step 1: Create the message envelope (transport-agnostic) +envelope, envelope_json_str = msghandler.smartpack("test.topic", + payloads; + broker_url="nats.yiem.cc", + fileserver_url="http://192.168.88.104:8080") + +# Step 2: Send via your chosen transport (NATS in this example) +using NATS +conn = NATS.connect("nats.yiem.cc") +reply = NATS.request(conn, "test.topic", envelope_json_str, timeout=10) +``` + +#### Receive Messages (Request - Reply Pattern) + +```julia +using msghandler, NATS + +conn = NATS.connect("nats.yiem.cc") +sub1 = NATS.reply(conn, "nats.yiem.cc"; queue_group="group1", spawn=true) do msg + envelope_json_str = String(msg.payload) + envelope = msghandler.smartunpack(envelope_json_str) + response = [("respond_message", "msg received", "text")] + _, envelope_json_str = msghandler.smartpack("requester inbox", + response; + broker_url="nats.yiem.cc", + fileserver_url="http://192.168.88.104:8080") + return envelope_json_str + end +``` + ### JavaScript CSR Webapp #### Installation From 0c981d943d2cf2af2ac4f02c6735fa3909c468ab Mon Sep 17 00:00:00 2001 From: narawat Date: Sun, 24 May 2026 13:36:39 +0700 Subject: [PATCH 11/16] update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 512489a..2712fbf 100644 --- a/README.md +++ b/README.md @@ -250,7 +250,7 @@ NATS.subscribe(conn, "test.topic") do msg end ``` -#### OR +#### --- OR --- #### Send Messages (Request - Reply Pattern) ```julia From 76c73aac030bde3c8fe941e44f95f7f2f5825be8 Mon Sep 17 00:00:00 2001 From: narawat Date: Sun, 24 May 2026 13:37:03 +0700 Subject: [PATCH 12/16] update --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2712fbf..bb3e9a7 100644 --- a/README.md +++ b/README.md @@ -250,7 +250,7 @@ NATS.subscribe(conn, "test.topic") do msg end ``` -#### --- OR --- +#### --- OR --- #### Send Messages (Request - Reply Pattern) ```julia From dff47a80f9997764fcf4a81a1b272958e564c484 Mon Sep 17 00:00:00 2001 From: narawat Date: Mon, 25 May 2026 06:46:19 +0700 Subject: [PATCH 13/16] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index bb3e9a7..6c138f1 100644 --- a/README.md +++ b/README.md @@ -555,7 +555,7 @@ const [envelope, envelopeJsonStr] = await msghandlerCSR.smartpack("/data/analysi ]); ``` -### Example 7: Request-Response Pattern +### Example 7: Publish-Subscribe Pattern (Julia) Bi-directional communication with reply-to support. From 48fddb5cbc0e99a08fcc439aca70ae6fba376478 Mon Sep 17 00:00:00 2001 From: narawat Date: Mon, 25 May 2026 07:36:17 +0700 Subject: [PATCH 14/16] update doc --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 6c138f1..dc3bd53 100644 --- a/README.md +++ b/README.md @@ -557,7 +557,7 @@ const [envelope, envelopeJsonStr] = await msghandlerCSR.smartpack("/data/analysi ### Example 7: Publish-Subscribe Pattern (Julia) -Bi-directional communication with reply-to support. +Bi-directional communication. ```julia using msghandler, NATS From da2085ad320bd8924be1609bf8e590b5ec3e12ec Mon Sep 17 00:00:00 2001 From: narawat Date: Mon, 25 May 2026 10:32:12 +0700 Subject: [PATCH 15/16] update --- README.md | 204 ++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 160 insertions(+), 44 deletions(-) diff --git a/README.md b/README.md index dc3bd53..7321287 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,121 @@ A high-performance, **transport-agnostic** communication layer for both **Julia* | `"video"` | `Vector{UInt8}` | Video data | | `"binary"` | `Vector{UInt8}`, `IOBuffer` | Binary data | +### Message Envelope Structure (JSON) with Multiple Payloads + +After calling `smartpack()`, the data is serialized into a JSON message envelope with the following structure: + +```json +{ + "correlation_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", + "msg_id": "b2c3d4e5-f6a7-8901-bcde-f12345678901", + "timestamp": "2026-05-25T08:12:40.000Z", + "send_to": "/chat/room1", + "msg_purpose": "chat", + "sender_name": "msghandler", + "sender_id": "c3d4e5f6-a7b8-9012-cdef-123456789012", + "receiver_name": "", + "receiver_id": "", + "reply_to": "", + "reply_to_msg_id": "", + "broker_url": "nats.yiem.cc", + "metadata": {}, + "payloads": [ + { + "id": "d4e5f6a7-b8c9-0123-defa-234567890123", + "dataname": "message", + "payload_type": "text", + "transport": "direct", + "encoding": "base64", + "size": 6, + "data": "SGVsbG8h", + "metadata": {} + }, + { + "id": "e5f6a7b8-c9d0-1234-efab-345678901234", + "dataname": "config", + "payload_type": "dictionary", + "transport": "direct", + "encoding": "base64", + "size": 18, + "data": "eyJrZXkiOiJ2YWx1ZSJ9", + "metadata": {} + }, + { + "id": "f6a7b8c9-d0e1-2345-fabc-456789012345", + "dataname": "small_file", + "payload_type": "binary", + "transport": "direct", + "encoding": "base64", + "size": 1024, + "data": "base64encodedbinarydata...", + "metadata": {} + }, + { + "id": "a7b8c9d0-e1f2-3456-abcd-567890123456", + "dataname": "large_image", + "payload_type": "image", + "transport": "link", + "encoding": "none", + "size": 2048576, + "data": "http://localhost:8080/file/ABC12/XYZ99/image.png", + "metadata": {} + } + ] +} +``` + +#### Payload Transport Types + +| Transport | Data Format | Use Case | +|-----------|-------------|----------| +| `direct` | Base64-encoded string | Payloads < 500KB sent directly | +| `link` | HTTP URL | Payloads ≥ 500KB uploaded to file server | + +#### Payload Encoding Types + +| Encoding | Payload Types | +|----------|---------------| +| `base64` | text, image, audio, video, binary | +| `json` | dictionary, jsontable | +| `arrow-ipc` | arrowtable | +| `none` | link transport (URL only) | + +#### Payload Field Definitions + +| Field | Type | Description | +|-------|------|-------------| +| `id` | `string` (UUID) | Unique payload identifier | +| `dataname` | `string` | Name/filename of the payload | +| `payload_type` | `string` | Type: text, dictionary, arrowtable, jsontable, image, audio, video, binary | +| `transport` | `string` | `direct` or `link` | +| `encoding` | `string` | base64, json, arrow-ipc, or none | +| `size` | `integer` | Size in bytes | +| `data` | `string` | Base64 string (direct) or URL (link) | +| `metadata` | `object` | Optional metadata | + +#### Example: Multiple Payloads with Mixed Types + +```julia +using msghandler + +data = [ + ("message", "Hello!", "text"), + ("config", Dict("key" => "value"), "dictionary"), + ("small_file", file_bytes, "binary"), + ("large_image", image_bytes, "image") +] + +message_envelope, message_envelope_json_str = msghandler.smartpack("/chat/room1", data; + broker_url="nats.yiem.cc", + fileserver_url="http://localhost:8080" +) + +# message_envelope_json_str contains the JSON message_envelope shown above +# Payloads < 500KB use 'transport': 'direct' with Base64 data +# Payloads ≥ 500KB use 'transport': 'link' with URL to file server +``` + ### Examples **Sending multiple payloads:** @@ -57,9 +172,10 @@ A high-performance, **transport-agnostic** communication layer for both **Julia* data = [ ("message", "Hello!", "text"), ("config", Dict("key" => "value"), "dictionary"), - ("file", file_bytes, "binary") + ("small_file", file_bytes, "binary"), + ("large_image", image_bytes, "image") ] -env, json_str = msghandler.smartpack("subject", data, options...) +message_envelope, message_envelope_json_str = msghandler.smartpack("subject", data, options...) ``` **Important Notes:** @@ -214,7 +330,7 @@ payload_2 = (filename_large_image, file_data_large_image, "binary") payloads = [payload_1, payload_2] # List of tuples # Step 1: Create the message envelope (transport-agnostic) -envelope, envelope_json_str = msghandler.smartpack("test.topic", +message_envelope, message_envelope_json_str = msghandler.smartpack("test.topic", payloads; broker_url="nats.yiem.cc", fileserver_url="http://192.168.88.104:8080") @@ -222,11 +338,11 @@ envelope, envelope_json_str = msghandler.smartpack("test.topic", # Step 2: Send via your chosen transport (NATS in this example) using NATS conn = NATS.connect("nats.yiem.cc") -NATS.publish(conn, "test.topic", envelope_json_str; reply_to="test.replytopic") +NATS.publish(conn, "test.topic", message_envelope_json_str; reply_to="test.replytopic") NATS.drain(conn) # OR using request-reply pattern -reply = NATS.request(conn, "test.topic", envelope_json_str, timeout=10) +reply = NATS.request(conn, "test.topic", message_envelope_json_str, timeout=10) ``` #### Receive Messages (Publish - Subscribe Pattern) @@ -237,16 +353,16 @@ using msghandler, NATS conn = NATS.connect("nats.yiem.cc") NATS.subscribe(conn, "test.topic") do msg println("Received message on $(msg.subject)") - envelope_json_str = String(msg.payload) + message_envelope_json_str = String(msg.payload) - # Step 2: Unpack the envelope (transport-agnostic) - envelope = msghandler.smartunpack( - envelope_json_str; + # Step 2: Unpack the message_envelope (transport-agnostic) + message_envelope = msghandler.smartunpack( + message_envelope_json_str; max_retries = 5, base_delay = 100, max_delay = 5000 ) - println(envelope.payloads[1]) + println(message_envelope.payloads[1]) end ``` @@ -266,7 +382,7 @@ payload_2 = (filename_large_image, file_data_large_image, "binary") payloads = [payload_1, payload_2] # List of tuples # Step 1: Create the message envelope (transport-agnostic) -envelope, envelope_json_str = msghandler.smartpack("test.topic", +message_envelope, message_envelope_json_str = msghandler.smartpack("test.topic", payloads; broker_url="nats.yiem.cc", fileserver_url="http://192.168.88.104:8080") @@ -274,7 +390,7 @@ envelope, envelope_json_str = msghandler.smartpack("test.topic", # Step 2: Send via your chosen transport (NATS in this example) using NATS conn = NATS.connect("nats.yiem.cc") -reply = NATS.request(conn, "test.topic", envelope_json_str, timeout=10) +reply = NATS.request(conn, "test.topic", message_envelope_json_str, timeout=10) ``` #### Receive Messages (Request - Reply Pattern) @@ -284,14 +400,14 @@ using msghandler, NATS conn = NATS.connect("nats.yiem.cc") sub1 = NATS.reply(conn, "nats.yiem.cc"; queue_group="group1", spawn=true) do msg - envelope_json_str = String(msg.payload) - envelope = msghandler.smartunpack(envelope_json_str) + message_envelope_json_str = String(msg.payload) + message_envelope = msghandler.smartunpack(message_envelope_json_str) response = [("respond_message", "msg received", "text")] - _, envelope_json_str = msghandler.smartpack("requester inbox", + _, response_message_envelope_json_str = msghandler.smartpack("requester inbox", response; broker_url="nats.yiem.cc", fileserver_url="http://192.168.88.104:8080") - return envelope_json_str + return response_message_envelope_json_str end ``` @@ -332,19 +448,19 @@ const payload4 = ["user_avatar", file_data, "binary"]; const payloads = [payload1, payload2, payload3, payload4]; // Array of arrays // Step 1: Create the message envelope (transport-agnostic) -const [envelope, envelopeJsonStr] = await msghandlerCSR.smartpack("test.topic", payloads, { +const [message_envelope, message_envelope_json_str] = await msghandlerCSR.smartpack("test.topic", payloads, { broker_url: "nats.yiem.cc", fileserver_url: "http://192.168.88.104:8080" }); // Step 2: Send via your chosen transport (NATS, WebSocket, HTTP, etc.) -// await myTransport.publish("test.topic", envelopeJsonStr); +// await myTransport.publish("test.topic", message_envelope_json_str); // OR using fetch API for HTTP transport fetch("http://localhost:3000/api/messages", { method: "POST", headers: { "Content-Type": "application/json" }, - body: envelopeJsonStr + body: message_envelope_json_str }); ``` @@ -356,7 +472,7 @@ import msghandlerCSR from './src/msghandler-csr.js'; const { smartunpack } = msghandlerCSR; // Option 1: Direct JSON string -const envelope = await msghandlerCSR.smartunpack(jsonString, { +const message_envelope = await msghandlerCSR.smartunpack(jsonString, { fileserver_download_handler: msghandlerCSR.fetchWithBackoff, max_retries: 5, base_delay: 100, @@ -364,12 +480,12 @@ const envelope = await msghandlerCSR.smartunpack(jsonString, { }); // Option 2: From transport message object (e.g., NATS, WebSocket) -const envelope = await msghandlerCSR.smartunpack(natsMessage, { +const message_envelope = await msghandlerCSR.smartunpack(natsMessage, { fileserver_download_handler: msghandlerCSR.fetchWithBackoff }); // Process payloads -for (const [dataname, data, type] of envelope.payloads) { +for (const [dataname, data, type] of message_envelope.payloads) { console.log(`${dataname}:`, data, `(type: ${type})`); } ``` @@ -384,7 +500,7 @@ Sends data via your chosen transport mechanism with intelligent transport select ```julia using msghandler -env, env_json_str = msghandler.smartpack( +message_envelope, message_envelope_json_str = msghandler.smartpack( subject::String, data::AbstractArray{Tuple{String, Any, String}}; broker_url::String = "nats://localhost:4222", @@ -411,7 +527,7 @@ Receives and processes messages from your transport. ```julia using msghandler -env = msghandler.smartunpack( +message_envelope = msghandler.smartunpack( msg_json_str::String; fileserver_download_handler::Function = _fetch_with_backoff, max_retries::Int = 5, @@ -438,7 +554,7 @@ data = [ ("large_document", large_file_data, "binary") ] -env, env_json_str = smartpack("/chat/room1", data; fileserver_url="http://localhost:8080") +message_envelope, message_envelope_json_str = smartpack("/chat/room1", data; fileserver_url="http://localhost:8080") ``` ### Example 2: Dictionary Exchange (Julia) @@ -455,7 +571,7 @@ config = Dict( ) data = [("config", config, "dictionary")] -env, env_json_str = smartpack("/device/config", data) +message_envelope, message_envelope_json_str = smartpack("/device/config", data) ``` ### Example 3: Table Data (Arrow IPC - Julia Only) @@ -473,7 +589,7 @@ df = DataFrame( ) data = [("students", df, "arrowtable")] -env, env_json_str = smartpack("/data/analysis", data) +message_envelope, message_envelope_json_str = smartpack("/data/analysis", data) ``` ### Example 3b: Table Data (JSON - Julia Compatible) @@ -491,7 +607,7 @@ df = DataFrame( ) data = [("students", df, "jsontable")] -env, env_json_str = smartpack("/data/analysis", data) +message_envelope, message_envelope_json_str = smartpack("/data/analysis", data) ``` ### Example 3c: Table Data (JSON - JavaScript Compatible) @@ -505,7 +621,7 @@ const tableData = [ { id: 3, name: "Charlie", score: 92 } ]; -const [envelope, envelopeJsonStr] = await msghandlerCSR.smartpack("/data/analysis", [ +const [message_envelope, message_envelope_json_str] = await msghandlerCSR.smartpack("/data/analysis", [ ["students", tableData, "jsontable"] ]); ``` @@ -522,7 +638,7 @@ image_path = "./test/large_image.png" image_data = read(image_path) data = [("user_avatar", image_data, "binary")] -env, env_json_str = smartpack("/chat/room1", data; fileserver_url="http://localhost:8080") +message_envelope, message_envelope_json_str = smartpack("/chat/room1", data; fileserver_url="http://localhost:8080") ``` ### Example 5: Image Transmission (JavaScript) @@ -534,7 +650,7 @@ import fs from 'fs'; const file_data = fs.readFileSync('./test/large_image.png'); -const [envelope, envelopeJsonStr] = await msghandlerCSR.smartpack("/chat/room1", [ +const [message_envelope, message_envelope_json_str] = await msghandlerCSR.smartpack("/chat/room1", [ ["user_avatar", file_data, "binary"] ]); ``` @@ -550,7 +666,7 @@ const tableData = [ { id: 3, name: "Charlie", score: 92 } ]; -const [envelope, envelopeJsonStr] = await msghandlerCSR.smartpack("/data/analysis", [ +const [message_envelope, message_envelope_json_str] = await msghandlerCSR.smartpack("/data/analysis", [ ["students", tableData, "jsontable"] ]); ``` @@ -563,7 +679,7 @@ Bi-directional communication. using msghandler, NATS # Requester -env, env_json_str = msghandler.smartpack( +message_envelope, message_envelope_json_str = msghandler.smartpack( "/device/command", [("command", Dict("action" => "read_sensor"), "dictionary")]; broker_url="nats://localhost:4222", @@ -571,24 +687,24 @@ env, env_json_str = msghandler.smartpack( ) conn = NATS.connect("nats://localhost:4222") -NATS.publish(conn, "/device/command", env_json_str) +NATS.publish(conn, "/device/command", message_envelope_json_str) NATS.drain(conn) # Receiver (in separate application) conn = NATS.connect("nats://localhost:4222") NATS.subscribe(conn, "/device/command") do msg - env = msghandler.smartunpack(msg) - println("Received command: ", env["payloads"]) + message_envelope = msghandler.smartunpack(msg) + println("Received command: ", message_envelope["payloads"]) result = Dict("value" => 42) - response_env, response_json = msghandler.smartpack( + response_message_envelope, response_message_envelope_json_str = msghandler.smartpack( "/device/response", [("result", result, "dictionary")], reply_to="/device/command", - reply_to_msg_id=env["msg_id"] + reply_to_msg_id=message_envelope["msg_id"] ) - NATS.publish(conn, "/device/response", response_json) + NATS.publish(conn, "/device/response", response_message_envelope_json_str) NATS.drain(conn) end ``` @@ -648,10 +764,10 @@ cat > test-browser.html << 'EOF' import msghandlerCSR from './src/msghandler-csr.js'; // Test smartpack -const [env, json] = await msghandlerCSR.smartpack("/test", [ +const [message_envelope, message_envelope_json_str] = await msghandlerCSR.smartpack("/test", [ ["msg", "Hello Browser!", "text"] ]); -console.log("Sent:", json); +console.log("Sent:", message_envelope_json_str); @@ -679,7 +795,7 @@ The JavaScript version provides the same core functionality as Julia, with brows #### smartpack ```javascript -const [envelope, envelopeJsonStr] = await msghandlerCSR.smartpack( +const [message_envelope, message_envelope_json_str] = await msghandlerCSR.smartpack( subject, data, // [[dataname, data, type], ...] options @@ -689,8 +805,8 @@ const [envelope, envelopeJsonStr] = await msghandlerCSR.smartpack( #### smartunpack ```javascript -const envelope = await msghandlerCSR.smartunpack(msg, options); -// envelope.payloads = [[dataname, data, type], ...] +const message_envelope = await msghandlerCSR.smartunpack(msg, options); +// message_envelope.payloads = [[dataname, data, type], ...] ``` #### plikOneshotUpload From 5b70df7c9d11bffe9f33399281d28dd3c3188bba Mon Sep 17 00:00:00 2001 From: narawat Date: Fri, 29 May 2026 11:23:53 +0700 Subject: [PATCH 16/16] update --- AI_prompt.md | 218 +++++++++++++++++++++++++ test/test_julia_mix_payloads_sender.jl | 3 +- 2 files changed, 220 insertions(+), 1 deletion(-) diff --git a/AI_prompt.md b/AI_prompt.md index 759d058..b1e8479 100644 --- a/AI_prompt.md +++ b/AI_prompt.md @@ -296,5 +296,223 @@ I want to: + + + +I execute test/test_julia_mix_payloads_sender.jl and I get this error: +ERROR: LoadError: HTTP.ConnectError for url = `https://fileserver.yiem.cc/upload`: IOError: SSL_ERROR_SSL +Stacktrace: + [1] macro expansion + @ ~/.julia/packages/OpenSSL/2SUGA/src/ssl.jl:476 [inlined] + [2] macro expansion + @ ./lock.jl:376 [inlined] + [3] connect(ssl::OpenSSL.SSLStream; require_ssl_verification::Bool) + @ OpenSSL ~/.julia/packages/OpenSSL/2SUGA/src/ssl.jl:443 + [4] connect + @ ~/.julia/packages/OpenSSL/2SUGA/src/ssl.jl:509 [inlined] + [5] #sslconnection#21 + @ ~/.julia/packages/HTTP/Y97L1/src/Connections.jl:595 [inlined] + [6] sslconnection + @ ~/.julia/packages/HTTP/Y97L1/src/Connections.jl:585 [inlined] + [7] getconnection(::Type{OpenSSL.SSLStream}, host::SubString{String}, port::SubString{String}; kw::@Kwargs{require_ssl_verification::Bool, keepalive::Bool, readtimeout::Int64, iofunction::Nothing, decompress::Nothing, verbose::Int64, body_is_form::Bool}) + @ HTTP.Connections ~/.julia/packages/HTTP/Y97L1/src/Connections.jl:582 + [8] getconnection + @ ~/.julia/packages/HTTP/Y97L1/src/Connections.jl:574 [inlined] + [9] #13 + @ ~/.julia/packages/HTTP/Y97L1/src/Connections.jl:463 [inlined] + [10] macro expansion + @ ~/.julia/packages/ConcurrentUtilities/hQBsU/src/try_with_timeout.jl:92 [inlined] + [11] (::ConcurrentUtilities.var"#try_with_timeout##2#try_with_timeout##3"{Any, Channel{Any}, HTTP.Connections.var"#13#14"{OpenSSL.SSLStream, Bool, Bool, @Kwargs{readtimeout::Int64, iofunction::Nothing, decompress::Nothing, verbose::Int64, body_is_form::Bool}, SubString{String}, SubString{String}}, Timer})() + @ ConcurrentUtilities ~/.julia/packages/ConcurrentUtilities/hQBsU/src/ConcurrentUtilities.jl:10 +Stacktrace: + [1] (::HTTP.ConnectionRequest.var"#connections#connectionlayer##0"{HTTP.ConnectionRequest.var"#connections#1#connectionlayer##1"{HTTP.TimeoutRequest.var"#timeouts#timeoutlayer##0"{HTTP.TimeoutRequest.var"#timeouts#1#timeoutlayer##1"{HTTP.ExceptionRequest.var"#exceptions#exceptionlayer##0"{HTTP.ExceptionRequest.var"#exceptions#1#exceptionlayer##1"{typeof(HTTP.StreamRequest.streamlayer)}}}}}})(req::HTTP.Messages.Request; proxy::Nothing, socket_type::Type, socket_type_tls::Nothing, readtimeout::Int64, connect_timeout::Int64, logerrors::Bool, logtag::Nothing, closeimmediately::Bool, kw::@Kwargs{iofunction::Nothing, decompress::Nothing, verbose::Int64, body_is_form::Bool}) + @ HTTP.ConnectionRequest ~/.julia/packages/HTTP/Y97L1/src/clientlayers/ConnectionRequest.jl:88 + [2] connections + @ ~/.julia/packages/HTTP/Y97L1/src/clientlayers/ConnectionRequest.jl:60 [inlined] + [3] (::Base.var"#46#47"{Base.var"#48#49"{ExponentialBackOff, HTTP.RetryRequest.var"#retrylayer##2#retrylayer##3"{Int64, typeof(HTTP.RetryRequest.FALSE), HTTP.Messages.Request, Base.RefValue{Int64}}, HTTP.ConnectionRequest.var"#connections#connectionlayer##0"{HTTP.ConnectionRequest.var"#connections#1#connectionlayer##1"{HTTP.TimeoutRequest.var"#timeouts#timeoutlayer##0"{HTTP.TimeoutRequest.var"#timeouts#1#timeoutlayer##1"{HTTP.ExceptionRequest.var"#exceptions#exceptionlayer##0"{HTTP.ExceptionRequest.var"#exceptions#1#exceptionlayer##1"{typeof(HTTP.StreamRequest.streamlayer)}}}}}}}})(args::HTTP.Messages.Request; kwargs::@Kwargs{iofunction::Nothing, decompress::Nothing, verbose::Int64, body_is_form::Bool}) + @ Base ./error.jl:309 + [4] (::HTTP.RetryRequest.var"#manageretries#retrylayer##0"{HTTP.RetryRequest.var"#manageretries#1#retrylayer##1"{HTTP.ConnectionRequest.var"#connections#connectionlayer##0"{HTTP.ConnectionRequest.var"#connections#1#connectionlayer##1"{HTTP.TimeoutRequest.var"#timeouts#timeoutlayer##0"{HTTP.TimeoutRequest.var"#timeouts#1#timeoutlayer##1"{HTTP.ExceptionRequest.var"#exceptions#exceptionlayer##0"{HTTP.ExceptionRequest.var"#exceptions#1#exceptionlayer##1"{typeof(HTTP.StreamRequest.streamlayer)}}}}}}}})(req::HTTP.Messages.Request; retry::Bool, retries::Int64, retry_delays::ExponentialBackOff, retry_check::Function, retry_non_idempotent::Bool, kw::@Kwargs{iofunction::Nothing, decompress::Nothing, verbose::Int64, body_is_form::Bool}) + @ HTTP.RetryRequest ~/.julia/packages/HTTP/Y97L1/src/clientlayers/RetryRequest.jl:75 + [5] manageretries + @ ~/.julia/packages/HTTP/Y97L1/src/clientlayers/RetryRequest.jl:30 [inlined] + [6] (::HTTP.CookieRequest.var"#managecookies#cookielayer##0"{HTTP.CookieRequest.var"#managecookies#1#cookielayer##1"{HTTP.RetryRequest.var"#manageretries#retrylayer##0"{HTTP.RetryRequest.var"#manageretries#1#retrylayer##1"{HTTP.ConnectionRequest.var"#connections#connectionlayer##0"{HTTP.ConnectionRequest.var"#connections#1#connectionlayer##1"{HTTP.TimeoutRequest.var"#timeouts#timeoutlayer##0"{HTTP.TimeoutRequest.var"#timeouts#1#timeoutlayer##1"{HTTP.ExceptionRequest.var"#exceptions#exceptionlayer##0"{HTTP.ExceptionRequest.var"#exceptions#1#exceptionlayer##1"{typeof(HTTP.StreamRequest.streamlayer)}}}}}}}}}})(req::HTTP.Messages.Request; cookies::Bool, cookiejar::HTTP.Cookies.CookieJar, kw::@Kwargs{iofunction::Nothing, decompress::Nothing, verbose::Int64, body_is_form::Bool}) + @ HTTP.CookieRequest ~/.julia/packages/HTTP/Y97L1/src/clientlayers/CookieRequest.jl:42 + [7] managecookies + @ ~/.julia/packages/HTTP/Y97L1/src/clientlayers/CookieRequest.jl:19 [inlined] + [8] (::HTTP.HeadersRequest.var"#defaultheaders#headerslayer##0"{HTTP.HeadersRequest.var"#defaultheaders#1#headerslayer##1"{HTTP.CookieRequest.var"#managecookies#cookielayer##0"{HTTP.CookieRequest.var"#managecookies#1#cookielayer##1"{HTTP.RetryRequest.var"#manageretries#retrylayer##0"{HTTP.RetryRequest.var"#manageretries#1#retrylayer##1"{HTTP.ConnectionRequest.var"#connections#connectionlayer##0"{HTTP.ConnectionRequest.var"#connections#1#connectionlayer##1"{HTTP.TimeoutRequest.var"#timeouts#timeoutlayer##0"{HTTP.TimeoutRequest.var"#timeouts#1#timeoutlayer##1"{HTTP.ExceptionRequest.var"#exceptions#exceptionlayer##0"{HTTP.ExceptionRequest.var"#exceptions#1#exceptionlayer##1"{typeof(HTTP.StreamRequest.streamlayer)}}}}}}}}}}}})(req::HTTP.Messages.Request; iofunction::Nothing, decompress::Nothing, basicauth::Bool, detect_content_type::Bool, canonicalize_headers::Bool, kw::@Kwargs{verbose::Int64, body_is_form::Bool}) + @ HTTP.HeadersRequest ~/.julia/packages/HTTP/Y97L1/src/clientlayers/HeadersRequest.jl:71 + [9] defaultheaders + @ ~/.julia/packages/HTTP/Y97L1/src/clientlayers/HeadersRequest.jl:14 [inlined] + [10] (::HTTP.RedirectRequest.var"#redirects#redirectlayer##0"{HTTP.RedirectRequest.var"#redirects#1#redirectlayer##1"{HTTP.HeadersRequest.var"#defaultheaders#headerslayer##0"{HTTP.HeadersRequest.var"#defaultheaders#1#headerslayer##1"{HTTP.CookieRequest.var"#managecookies#cookielayer##0"{HTTP.CookieRequest.var"#managecookies#1#cookielayer##1"{HTTP.RetryRequest.var"#manageretries#retrylayer##0"{HTTP.RetryRequest.var"#manageretries#1#retrylayer##1"{HTTP.ConnectionRequest.var"#connections#connectionlayer##0"{HTTP.ConnectionRequest.var"#connections#1#connectionlayer##1"{HTTP.TimeoutRequest.var"#timeouts#timeoutlayer##0"{HTTP.TimeoutRequest.var"#timeouts#1#timeoutlayer##1"{HTTP.ExceptionRequest.var"#exceptions#exceptionlayer##0"{HTTP.ExceptionRequest.var"#exceptions#1#exceptionlayer##1"{typeof(HTTP.StreamRequest.streamlayer)}}}}}}}}}}}}}})(req::HTTP.Messages.Request; redirect::Bool, redirect_limit::Int64, redirect_method::Nothing, forwardheaders::Bool, response_stream::Nothing, kw::@Kwargs{verbose::Int64, body_is_form::Bool}) + @ HTTP.RedirectRequest ~/.julia/packages/HTTP/Y97L1/src/clientlayers/RedirectRequest.jl:25 + [11] redirects + @ ~/.julia/packages/HTTP/Y97L1/src/clientlayers/RedirectRequest.jl:14 [inlined] + [12] (::HTTP.MessageRequest.var"#makerequest#messagelayer##0"{HTTP.MessageRequest.var"#makerequest#1#messagelayer##1"{HTTP.RedirectRequest.var"#redirects#redirectlayer##0"{HTTP.RedirectRequest.var"#redirects#1#redirectlayer##1"{HTTP.HeadersRequest.var"#defaultheaders#headerslayer##0"{HTTP.HeadersRequest.var"#defaultheaders#1#headerslayer##1"{HTTP.CookieRequest.var"#managecookies#cookielayer##0"{HTTP.CookieRequest.var"#managecookies#1#cookielayer##1"{HTTP.RetryRequest.var"#manageretries#retrylayer##0"{HTTP.RetryRequest.var"#manageretries#1#retrylayer##1"{HTTP.ConnectionRequest.var"#connections#connectionlayer##0"{HTTP.ConnectionRequest.var"#connections#1#connectionlayer##1"{HTTP.TimeoutRequest.var"#timeouts#timeoutlayer##0"{HTTP.TimeoutRequest.var"#timeouts#1#timeoutlayer##1"{HTTP.ExceptionRequest.var"#exceptions#exceptionlayer##0"{HTTP.ExceptionRequest.var"#exceptions#1#exceptionlayer##1"{typeof(HTTP.StreamRequest.streamlayer)}}}}}}}}}}}}}}}})(method::String, url::URIs.URI, headers::Vector{Pair{String, String}}, body::String; copyheaders::Bool, response_stream::Nothing, http_version::HTTP.Strings.HTTPVersion, verbose::Int64, kw::@Kwargs{body_is_form::Bool}) + @ HTTP.MessageRequest ~/.julia/packages/HTTP/Y97L1/src/clientlayers/MessageRequest.jl:35 + [13] makerequest + @ ~/.julia/packages/HTTP/Y97L1/src/clientlayers/MessageRequest.jl:24 [inlined] + [14] request(stack::HTTP.MessageRequest.var"#makerequest#messagelayer##0"{HTTP.MessageRequest.var"#makerequest#1#messagelayer##1"{HTTP.RedirectRequest.var"#redirects#redirectlayer##0"{HTTP.RedirectRequest.var"#redirects#1#redirectlayer##1"{HTTP.HeadersRequest.var"#defaultheaders#headerslayer##0"{HTTP.HeadersRequest.var"#defaultheaders#1#headerslayer##1"{HTTP.CookieRequest.var"#managecookies#cookielayer##0"{HTTP.CookieRequest.var"#managecookies#1#cookielayer##1"{HTTP.RetryRequest.var"#manageretries#retrylayer##0"{HTTP.RetryRequest.var"#manageretries#1#retrylayer##1"{HTTP.ConnectionRequest.var"#connections#connectionlayer##0"{HTTP.ConnectionRequest.var"#connections#1#connectionlayer##1"{HTTP.TimeoutRequest.var"#timeouts#timeoutlayer##0"{HTTP.TimeoutRequest.var"#timeouts#1#timeoutlayer##1"{HTTP.ExceptionRequest.var"#exceptions#exceptionlayer##0"{HTTP.ExceptionRequest.var"#exceptions#1#exceptionlayer##1"{typeof(HTTP.StreamRequest.streamlayer)}}}}}}}}}}}}}}}}, method::String, url::String, h::Vector{Pair{String, String}}, b::String, q::Nothing; headers::Vector{Pair{String, String}}, body::String, query::Nothing, kw::@Kwargs{body_is_form::Bool}) + @ HTTP ~/.julia/packages/HTTP/Y97L1/src/HTTP.jl:465 + [15] #request#21 + @ ~/.julia/packages/HTTP/Y97L1/src/HTTP.jl:323 [inlined] + [16] request + @ ~/.julia/packages/HTTP/Y97L1/src/HTTP.jl:321 [inlined] + [17] plik_upload_handler(fileserver_url::String, dataname::String, data::Vector{UInt8}) + @ Main ~/docker-apps/sommpanion/msghandler/test/test_julia_mix_payloads_sender.jl:48 + [18] smartpack(subject::String, data::Vector{Tuple{String, Any, String}}; broker_url::String, fileserver_url::String, fileserver_upload_handler::typeof(plik_upload_handler), size_threshold::Int64, correlation_id::String, msg_purpose::String, sender_name::String, receiver_name::String, receiver_id::String, reply_to::String, reply_to_msg_id::String, msg_id::String, sender_id::String) + @ Main.msghandler ~/docker-apps/sommpanion/msghandler/src/msghandler.jl:493 + [19] test_mix_send() + @ Main ~/docker-apps/sommpanion/msghandler/test/test_julia_mix_payloads_sender.jl:208 + [20] top-level scope + @ ~/docker-apps/sommpanion/msghandler/test/test_julia_mix_payloads_sender.jl:256 + [21] include(mod::Module, _path::String) + @ Base ./Base.jl:306 + [22] exec_options(opts::Base.JLOptions) + @ Base ./client.jl:317 + [23] _start() + @ Base ./client.jl:550 +in expression starting at /home/ton/docker-apps/sommpanion/msghandler/test/test_julia_mix_payloads_sender.jl:256 + +caused by: IOError: SSL_ERROR_SSL +Stacktrace: + [1] macro expansion + @ ~/.julia/packages/OpenSSL/2SUGA/src/ssl.jl:476 [inlined] + [2] macro expansion + @ ./lock.jl:376 [inlined] + [3] connect(ssl::OpenSSL.SSLStream; require_ssl_verification::Bool) + @ OpenSSL ~/.julia/packages/OpenSSL/2SUGA/src/ssl.jl:443 + [4] connect + @ ~/.julia/packages/OpenSSL/2SUGA/src/ssl.jl:509 [inlined] + [5] #sslconnection#21 + @ ~/.julia/packages/HTTP/Y97L1/src/Connections.jl:595 [inlined] + [6] sslconnection + @ ~/.julia/packages/HTTP/Y97L1/src/Connections.jl:585 [inlined] + [7] getconnection(::Type{OpenSSL.SSLStream}, host::SubString{String}, port::SubString{String}; kw::@Kwargs{require_ssl_verification::Bool, keepalive::Bool, readtimeout::Int64, iofunction::Nothing, decompress::Nothing, verbose::Int64, body_is_form::Bool}) + @ HTTP.Connections ~/.julia/packages/HTTP/Y97L1/src/Connections.jl:582 + [8] getconnection + @ ~/.julia/packages/HTTP/Y97L1/src/Connections.jl:574 [inlined] + [9] #13 + @ ~/.julia/packages/HTTP/Y97L1/src/Connections.jl:463 [inlined] + [10] macro expansion + @ ~/.julia/packages/ConcurrentUtilities/hQBsU/src/try_with_timeout.jl:92 [inlined] + [11] (::ConcurrentUtilities.var"#try_with_timeout##2#try_with_timeout##3"{Any, Channel{Any}, HTTP.Connections.var"#13#14"{OpenSSL.SSLStream, Bool, Bool, @Kwargs{readtimeout::Int64, iofunction::Nothing, decompress::Nothing, verbose::Int64, body_is_form::Bool}, SubString{String}, SubString{String}}, Timer})() + @ ConcurrentUtilities ~/.julia/packages/ConcurrentUtilities/hQBsU/src/ConcurrentUtilities.jl:10 +Stacktrace: + [1] try_yieldto(undo::typeof(Base.ensure_rescheduled)) + @ Base ./task.jl:1157 + [2] wait() + @ Base ./task.jl:1229 + [3] wait(c::Base.GenericCondition{ReentrantLock}; first::Bool) + @ Base ./condition.jl:141 + [4] wait + @ ./condition.jl:136 [inlined] + [5] take_unbuffered(c::Channel{Any}) + @ Base ./channels.jl:549 + [6] take! + @ ./channels.jl:526 [inlined] + [7] try_with_timeout(f::Function, timeout::Int64, ::Type{Any}) + @ ConcurrentUtilities ~/.julia/packages/ConcurrentUtilities/hQBsU/src/try_with_timeout.jl:99 + [8] try_with_timeout + @ ~/.julia/packages/ConcurrentUtilities/hQBsU/src/try_with_timeout.jl:77 [inlined] + [9] (::HTTP.Connections.var"#11#12"{OpenSSL.SSLStream, Int64, Int64, Bool, Bool, @Kwargs{readtimeout::Int64, iofunction::Nothing, decompress::Nothing, verbose::Int64, body_is_form::Bool}, SubString{String}, SubString{String}})() + @ HTTP.Connections ~/.julia/packages/HTTP/Y97L1/src/Connections.jl:460 + [10] acquire(f::HTTP.Connections.var"#11#12"{OpenSSL.SSLStream, Int64, Int64, Bool, Bool, @Kwargs{readtimeout::Int64, iofunction::Nothing, decompress::Nothing, verbose::Int64, body_is_form::Bool}, SubString{String}, SubString{String}}, pool::ConcurrentUtilities.Pools.Pool{Tuple{AbstractString, AbstractString, Bool, Bool, Bool}, HTTP.Connections.Connection{OpenSSL.SSLStream}}, key::Tuple{SubString{String}, SubString{String}, Bool, Bool, Bool}; forcenew::Bool, isvalid::HTTP.Connections.var"#15#16"{Int64}) + @ ConcurrentUtilities.Pools ~/.julia/packages/ConcurrentUtilities/hQBsU/src/pools.jl:159 + [11] acquire + @ ~/.julia/packages/ConcurrentUtilities/hQBsU/src/pools.jl:140 [inlined] + [12] #newconnection#7 + @ ~/.julia/packages/HTTP/Y97L1/src/Connections.jl:455 [inlined] + [13] (::HTTP.ConnectionRequest.var"#connections#connectionlayer##0"{HTTP.ConnectionRequest.var"#connections#1#connectionlayer##1"{HTTP.TimeoutRequest.var"#timeouts#timeoutlayer##0"{HTTP.TimeoutRequest.var"#timeouts#1#timeoutlayer##1"{HTTP.ExceptionRequest.var"#exceptions#exceptionlayer##0"{HTTP.ExceptionRequest.var"#exceptions#1#exceptionlayer##1"{typeof(HTTP.StreamRequest.streamlayer)}}}}}})(req::HTTP.Messages.Request; proxy::Nothing, socket_type::Type, socket_type_tls::Nothing, readtimeout::Int64, connect_timeout::Int64, logerrors::Bool, logtag::Nothing, closeimmediately::Bool, kw::@Kwargs{iofunction::Nothing, decompress::Nothing, verbose::Int64, body_is_form::Bool}) + @ HTTP.ConnectionRequest ~/.julia/packages/HTTP/Y97L1/src/clientlayers/ConnectionRequest.jl:82 + [14] connections + @ ~/.julia/packages/HTTP/Y97L1/src/clientlayers/ConnectionRequest.jl:60 [inlined] + [15] (::Base.var"#46#47"{Base.var"#48#49"{ExponentialBackOff, HTTP.RetryRequest.var"#retrylayer##2#retrylayer##3"{Int64, typeof(HTTP.RetryRequest.FALSE), HTTP.Messages.Request, Base.RefValue{Int64}}, HTTP.ConnectionRequest.var"#connections#connectionlayer##0"{HTTP.ConnectionRequest.var"#connections#1#connectionlayer##1"{HTTP.TimeoutRequest.var"#timeouts#timeoutlayer##0"{HTTP.TimeoutRequest.var"#timeouts#1#timeoutlayer##1"{HTTP.ExceptionRequest.var"#exceptions#exceptionlayer##0"{HTTP.ExceptionRequest.var"#exceptions#1#exceptionlayer##1"{typeof(HTTP.StreamRequest.streamlayer)}}}}}}}})(args::HTTP.Messages.Request; kwargs::@Kwargs{iofunction::Nothing, decompress::Nothing, verbose::Int64, body_is_form::Bool}) + @ Base ./error.jl:309 + [16] (::HTTP.RetryRequest.var"#manageretries#retrylayer##0"{HTTP.RetryRequest.var"#manageretries#1#retrylayer##1"{HTTP.ConnectionRequest.var"#connections#connectionlayer##0"{HTTP.ConnectionRequest.var"#connections#1#connectionlayer##1"{HTTP.TimeoutRequest.var"#timeouts#timeoutlayer##0"{HTTP.TimeoutRequest.var"#timeouts#1#timeoutlayer##1"{HTTP.ExceptionRequest.var"#exceptions#exceptionlayer##0"{HTTP.ExceptionRequest.var"#exceptions#1#exceptionlayer##1"{typeof(HTTP.StreamRequest.streamlayer)}}}}}}}})(req::HTTP.Messages.Request; retry::Bool, retries::Int64, retry_delays::ExponentialBackOff, retry_check::Function, retry_non_idempotent::Bool, kw::@Kwargs{iofunction::Nothing, decompress::Nothing, verbose::Int64, body_is_form::Bool}) + @ HTTP.RetryRequest ~/.julia/packages/HTTP/Y97L1/src/clientlayers/RetryRequest.jl:75 + [17] manageretries + @ ~/.julia/packages/HTTP/Y97L1/src/clientlayers/RetryRequest.jl:30 [inlined] + [18] (::HTTP.CookieRequest.var"#managecookies#cookielayer##0"{HTTP.CookieRequest.var"#managecookies#1#cookielayer##1"{HTTP.RetryRequest.var"#manageretries#retrylayer##0"{HTTP.RetryRequest.var"#manageretries#1#retrylayer##1"{HTTP.ConnectionRequest.var"#connections#connectionlayer##0"{HTTP.ConnectionRequest.var"#connections#1#connectionlayer##1"{HTTP.TimeoutRequest.var"#timeouts#timeoutlayer##0"{HTTP.TimeoutRequest.var"#timeouts#1#timeoutlayer##1"{HTTP.ExceptionRequest.var"#exceptions#exceptionlayer##0"{HTTP.ExceptionRequest.var"#exceptions#1#exceptionlayer##1"{typeof(HTTP.StreamRequest.streamlayer)}}}}}}}}}})(req::HTTP.Messages.Request; cookies::Bool, cookiejar::HTTP.Cookies.CookieJar, kw::@Kwargs{iofunction::Nothing, decompress::Nothing, verbose::Int64, body_is_form::Bool}) + @ HTTP.CookieRequest ~/.julia/packages/HTTP/Y97L1/src/clientlayers/CookieRequest.jl:42 + [19] managecookies + @ ~/.julia/packages/HTTP/Y97L1/src/clientlayers/CookieRequest.jl:19 [inlined] + [20] (::HTTP.HeadersRequest.var"#defaultheaders#headerslayer##0"{HTTP.HeadersRequest.var"#defaultheaders#1#headerslayer##1"{HTTP.CookieRequest.var"#managecookies#cookielayer##0"{HTTP.CookieRequest.var"#managecookies#1#cookielayer##1"{HTTP.RetryRequest.var"#manageretries#retrylayer##0"{HTTP.RetryRequest.var"#manageretries#1#retrylayer##1"{HTTP.ConnectionRequest.var"#connections#connectionlayer##0"{HTTP.ConnectionRequest.var"#connections#1#connectionlayer##1"{HTTP.TimeoutRequest.var"#timeouts#timeoutlayer##0"{HTTP.TimeoutRequest.var"#timeouts#1#timeoutlayer##1"{HTTP.ExceptionRequest.var"#exceptions#exceptionlayer##0"{HTTP.ExceptionRequest.var"#exceptions#1#exceptionlayer##1"{typeof(HTTP.StreamRequest.streamlayer)}}}}}}}}}}}})(req::HTTP.Messages.Request; iofunction::Nothing, decompress::Nothing, basicauth::Bool, detect_content_type::Bool, canonicalize_headers::Bool, kw::@Kwargs{verbose::Int64, body_is_form::Bool}) + @ HTTP.HeadersRequest ~/.julia/packages/HTTP/Y97L1/src/clientlayers/HeadersRequest.jl:71 + [21] defaultheaders + @ ~/.julia/packages/HTTP/Y97L1/src/clientlayers/HeadersRequest.jl:14 [inlined] + [22] (::HTTP.RedirectRequest.var"#redirects#redirectlayer##0"{HTTP.RedirectRequest.var"#redirects#1#redirectlayer##1"{HTTP.HeadersRequest.var"#defaultheaders#headerslayer##0"{HTTP.HeadersRequest.var"#defaultheaders#1#headerslayer##1"{HTTP.CookieRequest.var"#managecookies#cookielayer##0"{HTTP.CookieRequest.var"#managecookies#1#cookielayer##1"{HTTP.RetryRequest.var"#manageretries#retrylayer##0"{HTTP.RetryRequest.var"#manageretries#1#retrylayer##1"{HTTP.ConnectionRequest.var"#connections#connectionlayer##0"{HTTP.ConnectionRequest.var"#connections#1#connectionlayer##1"{HTTP.TimeoutRequest.var"#timeouts#timeoutlayer##0"{HTTP.TimeoutRequest.var"#timeouts#1#timeoutlayer##1"{HTTP.ExceptionRequest.var"#exceptions#exceptionlayer##0"{HTTP.ExceptionRequest.var"#exceptions#1#exceptionlayer##1"{typeof(HTTP.StreamRequest.streamlayer)}}}}}}}}}}}}}})(req::HTTP.Messages.Request; redirect::Bool, redirect_limit::Int64, redirect_method::Nothing, forwardheaders::Bool, response_stream::Nothing, kw::@Kwargs{verbose::Int64, body_is_form::Bool}) + @ HTTP.RedirectRequest ~/.julia/packages/HTTP/Y97L1/src/clientlayers/RedirectRequest.jl:25 + [23] redirects + @ ~/.julia/packages/HTTP/Y97L1/src/clientlayers/RedirectRequest.jl:14 [inlined] + [24] (::HTTP.MessageRequest.var"#makerequest#messagelayer##0"{HTTP.MessageRequest.var"#makerequest#1#messagelayer##1"{HTTP.RedirectRequest.var"#redirects#redirectlayer##0"{HTTP.RedirectRequest.var"#redirects#1#redirectlayer##1"{HTTP.HeadersRequest.var"#defaultheaders#headerslayer##0"{HTTP.HeadersRequest.var"#defaultheaders#1#headerslayer##1"{HTTP.CookieRequest.var"#managecookies#cookielayer##0"{HTTP.CookieRequest.var"#managecookies#1#cookielayer##1"{HTTP.RetryRequest.var"#manageretries#retrylayer##0"{HTTP.RetryRequest.var"#manageretries#1#retrylayer##1"{HTTP.ConnectionRequest.var"#connections#connectionlayer##0"{HTTP.ConnectionRequest.var"#connections#1#connectionlayer##1"{HTTP.TimeoutRequest.var"#timeouts#timeoutlayer##0"{HTTP.TimeoutRequest.var"#timeouts#1#timeoutlayer##1"{HTTP.ExceptionRequest.var"#exceptions#exceptionlayer##0"{HTTP.ExceptionRequest.var"#exceptions#1#exceptionlayer##1"{typeof(HTTP.StreamRequest.streamlayer)}}}}}}}}}}}}}}}})(method::String, url::URIs.URI, headers::Vector{Pair{String, String}}, body::String; copyheaders::Bool, response_stream::Nothing, http_version::HTTP.Strings.HTTPVersion, verbose::Int64, kw::@Kwargs{body_is_form::Bool}) + @ HTTP.MessageRequest ~/.julia/packages/HTTP/Y97L1/src/clientlayers/MessageRequest.jl:35 + [25] makerequest + @ ~/.julia/packages/HTTP/Y97L1/src/clientlayers/MessageRequest.jl:24 [inlined] + [26] request(stack::HTTP.MessageRequest.var"#makerequest#messagelayer##0"{HTTP.MessageRequest.var"#makerequest#1#messagelayer##1"{HTTP.RedirectRequest.var"#redirects#redirectlayer##0"{HTTP.RedirectRequest.var"#redirects#1#redirectlayer##1"{HTTP.HeadersRequest.var"#defaultheaders#headerslayer##0"{HTTP.HeadersRequest.var"#defaultheaders#1#headerslayer##1"{HTTP.CookieRequest.var"#managecookies#cookielayer##0"{HTTP.CookieRequest.var"#managecookies#1#cookielayer##1"{HTTP.RetryRequest.var"#manageretries#retrylayer##0"{HTTP.RetryRequest.var"#manageretries#1#retrylayer##1"{HTTP.ConnectionRequest.var"#connections#connectionlayer##0"{HTTP.ConnectionRequest.var"#connections#1#connectionlayer##1"{HTTP.TimeoutRequest.var"#timeouts#timeoutlayer##0"{HTTP.TimeoutRequest.var"#timeouts#1#timeoutlayer##1"{HTTP.ExceptionRequest.var"#exceptions#exceptionlayer##0"{HTTP.ExceptionRequest.var"#exceptions#1#exceptionlayer##1"{typeof(HTTP.StreamRequest.streamlayer)}}}}}}}}}}}}}}}}, method::String, url::String, h::Vector{Pair{String, String}}, b::String, q::Nothing; headers::Vector{Pair{String, String}}, body::String, query::Nothing, kw::@Kwargs{body_is_form::Bool}) + @ HTTP ~/.julia/packages/HTTP/Y97L1/src/HTTP.jl:465 + [27] #request#21 + @ ~/.julia/packages/HTTP/Y97L1/src/HTTP.jl:323 [inlined] + [28] request + @ ~/.julia/packages/HTTP/Y97L1/src/HTTP.jl:321 [inlined] + [29] plik_upload_handler(fileserver_url::String, dataname::String, data::Vector{UInt8}) + @ Main ~/docker-apps/sommpanion/msghandler/test/test_julia_mix_payloads_sender.jl:48 + [30] smartpack(subject::String, data::Vector{Tuple{String, Any, String}}; broker_url::String, fileserver_url::String, fileserver_upload_handler::typeof(plik_upload_handler), size_threshold::Int64, correlation_id::String, msg_purpose::String, sender_name::String, receiver_name::String, receiver_id::String, reply_to::String, reply_to_msg_id::String, msg_id::String, sender_id::String) + @ Main.msghandler ~/docker-apps/sommpanion/msghandler/src/msghandler.jl:493 + [31] test_mix_send() + @ Main ~/docker-apps/sommpanion/msghandler/test/test_julia_mix_payloads_sender.jl:208 + [32] top-level scope + @ ~/docker-apps/sommpanion/msghandler/test/test_julia_mix_payloads_sender.jl:256 + [33] include(mod::Module, _path::String) + @ Base ./Base.jl:306 + [34] exec_options(opts::Base.JLOptions) + @ Base ./client.jl:317 + [35] _start() + @ Base ./client.jl:550 +--- +This is my Caddyfile: +fileserver.mydomain.cc { + # 1. Handle OPTIONS preflight requests specifically + @options { + method OPTIONS + } + respond @options 204 + + # 2. Add CORS headers to all responses (including those from the proxy) + header { + Access-Control-Allow-Origin "*" + Access-Control-Allow-Methods "GET, HEAD, OPTIONS, POST, PUT, DELETE" + Access-Control-Allow-Headers "*" + Access-Control-Max-Age "86400" + } + + # 3. Proxy to your Plik server + reverse_proxy 192.168.88.104:8080 { + header_up Host {http.request.host} + header_up X-Forwarded-For {http.request.remote} + header_up X-Forwarded-Proto {http.request.scheme} + } +} +--- +What do you think? + + + + + + + + + + + + + + + + + + + + diff --git a/test/test_julia_mix_payloads_sender.jl b/test/test_julia_mix_payloads_sender.jl index 013aa26..9da2d47 100644 --- a/test/test_julia_mix_payloads_sender.jl +++ b/test/test_julia_mix_payloads_sender.jl @@ -20,7 +20,8 @@ using .msghandler # Configuration const SUBJECT = "/msghandler" const NATS_URL = "nats.yiem.cc" -const FILESERVER_URL = "http://192.168.88.104:8080" +# const FILESERVER_URL = "http://192.168.88.104:8080" +const FILESERVER_URL = "https://fileserver.yiem.cc" # Create correlation ID for tracing correlation_id = string(uuid4())