939 lines
27 KiB
Markdown
939 lines
27 KiB
Markdown
# NATSBridge.jl Walkthrough: Building a Chat System
|
|
|
|
A step-by-step guided walkthrough for building a real-time chat system using NATSBridge.jl with mixed content support (text, images, audio, video, and files).
|
|
|
|
## Prerequisites
|
|
|
|
- Julia 1.7+
|
|
- NATS server running
|
|
- HTTP file server (Plik) running
|
|
|
|
## Step 1: Understanding the Chat System Architecture
|
|
|
|
### System Components
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
│ Chat System │
|
|
├─────────────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌──────────────┐ NATS ┌──────────────┐ │
|
|
│ │ Julia │◄───────┬───────► │ JavaScript │ │
|
|
│ │ Service │ │ │ Client │ │
|
|
│ │ │ │ │ │ │
|
|
│ │ - Text │ │ │ - Text │ │
|
|
│ │ - Images │ │ │ - Images │ │
|
|
│ │ - Audio │ ▼ │ - Audio │ │
|
|
│ │ - Video │ NATSBridge.jl │ - Files │ │
|
|
│ │ - Files │ │ │ - Tables │ │
|
|
│ └──────────────┘ │ └──────────────┘ │
|
|
│ │ │
|
|
│ ┌───────┴───────┐ │
|
|
│ │ NATS │ │
|
|
│ │ Server │ │
|
|
│ └─────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────────────────┘
|
|
|
|
For large payloads (> 1MB):
|
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
│ File Server (Plik) │
|
|
│ │
|
|
│ Julia Service ──► Upload ──► File Server ──► Download ◄── JavaScript Client│
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Message Format
|
|
|
|
Each chat message is an envelope containing multiple payloads:
|
|
|
|
```json
|
|
{
|
|
"correlationId": "uuid4",
|
|
"msgId": "uuid4",
|
|
"timestamp": "2024-01-15T10:30:00Z",
|
|
"sendTo": "/chat/room1",
|
|
"msgPurpose": "chat",
|
|
"senderName": "user-1",
|
|
"senderId": "uuid4",
|
|
"receiverName": "user-2",
|
|
"receiverId": "uuid4",
|
|
"brokerURL": "nats://localhost:4222",
|
|
"payloads": [
|
|
{
|
|
"id": "uuid4",
|
|
"dataname": "message_text",
|
|
"type": "text",
|
|
"transport": "direct",
|
|
"encoding": "base64",
|
|
"size": 256,
|
|
"data": "SGVsbG8gV29ybGQh",
|
|
"metadata": {}
|
|
},
|
|
{
|
|
"id": "uuid4",
|
|
"dataname": "user_image",
|
|
"type": "image",
|
|
"transport": "link",
|
|
"encoding": "none",
|
|
"size": 15433,
|
|
"data": "http://localhost:8080/file/UPLOAD_ID/FILE_ID/image.jpg",
|
|
"metadata": {}
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
## Step 2: Setting Up the Environment
|
|
|
|
### 1. Start NATS Server
|
|
|
|
```bash
|
|
# Using Docker
|
|
docker run -d -p 4222:4222 -p 8222:8222 --name nats-server nats:latest
|
|
|
|
# Or download from https://github.com/nats-io/nats-server/releases
|
|
./nats-server
|
|
```
|
|
|
|
### 2. Start HTTP File Server (Plik)
|
|
|
|
```bash
|
|
# Using Docker
|
|
docker run -d -p 8080:8080 --name plik plik/plik:latest
|
|
|
|
# Or download from https://github.com/arnaud-lb/plik/releases
|
|
./plikd -d
|
|
```
|
|
|
|
### 3. Install Julia Dependencies
|
|
|
|
```julia
|
|
using Pkg
|
|
Pkg.add("NATS")
|
|
Pkg.add("JSON")
|
|
Pkg.add("Arrow")
|
|
Pkg.add("HTTP")
|
|
Pkg.add("UUIDs")
|
|
Pkg.add("Dates")
|
|
Pkg.add("Base64")
|
|
Pkg.add("PrettyPrinting")
|
|
Pkg.add("DataFrames")
|
|
```
|
|
|
|
## Step 3: Basic Text-Only Chat
|
|
|
|
### Sender (User 1)
|
|
|
|
```julia
|
|
using NATS
|
|
using JSON
|
|
using UUIDs
|
|
using Dates
|
|
using PrettyPrinting
|
|
using DataFrames
|
|
using Arrow
|
|
using HTTP
|
|
using Base64
|
|
|
|
# Include the bridge module
|
|
include("NATSBridge.jl")
|
|
using .NATSBridge
|
|
|
|
# Configuration
|
|
const NATS_URL = "nats://localhost:4222"
|
|
const FILESERVER_URL = "http://localhost:8080"
|
|
const SUBJECT = "/chat/room1"
|
|
|
|
# File upload handler for plik server
|
|
function plik_upload_handler(fileserver_url::String, dataname::String, data::Vector{UInt8})::Dict{String, Any}
|
|
url_getUploadID = "$fileserver_url/upload"
|
|
headers = ["Content-Type" => "application/json"]
|
|
body = """{ "OneShot" : true }"""
|
|
httpResponse = HTTP.request("POST", url_getUploadID, headers, body; body_is_form=false)
|
|
responseJson = JSON.parse(String(httpResponse.body))
|
|
uploadid = responseJson["id"]
|
|
uploadtoken = responseJson["uploadToken"]
|
|
|
|
file_multipart = HTTP.Multipart(dataname, IOBuffer(data), "application/octet-stream")
|
|
url_upload = "$fileserver_url/file/$uploadid"
|
|
headers = ["X-UploadToken" => uploadtoken]
|
|
|
|
form = HTTP.Form(Dict("file" => file_multipart))
|
|
httpResponse = HTTP.post(url_upload, headers, form)
|
|
responseJson = JSON.parse(String(httpResponse.body))
|
|
|
|
fileid = responseJson["id"]
|
|
url = "$fileserver_url/file/$uploadid/$fileid/$dataname"
|
|
|
|
return Dict("status" => httpResponse.status, "uploadid" => uploadid, "fileid" => fileid, "url" => url)
|
|
end
|
|
|
|
# Send a simple text message
|
|
function send_text_message()
|
|
message_text = "Hello, how are you today?"
|
|
|
|
env = NATSBridge.smartsend(
|
|
SUBJECT,
|
|
[("message", message_text, "text")],
|
|
nats_url = NATS_URL,
|
|
fileserver_url = FILESERVER_URL,
|
|
fileserverUploadHandler = plik_upload_handler,
|
|
size_threshold = 1_000_000,
|
|
correlation_id = string(uuid4()),
|
|
msg_purpose = "chat",
|
|
sender_name = "user-1"
|
|
)
|
|
|
|
println("Sent text message with correlation ID: $(env.correlationId)")
|
|
end
|
|
|
|
send_text_message()
|
|
```
|
|
|
|
### Receiver (User 2)
|
|
|
|
```julia
|
|
using NATS
|
|
using JSON
|
|
using UUIDs
|
|
using Dates
|
|
using PrettyPrinting
|
|
using DataFrames
|
|
using Arrow
|
|
using HTTP
|
|
using Base64
|
|
|
|
# Include the bridge module
|
|
include("NATSBridge.jl")
|
|
using .NATSBridge
|
|
|
|
# Configuration
|
|
const NATS_URL = "nats://localhost:4222"
|
|
const SUBJECT = "/chat/room1"
|
|
|
|
# Message handler
|
|
function message_handler(msg::NATS.Msg)
|
|
payloads = NATSBridge.smartreceive(
|
|
msg,
|
|
max_retries = 5,
|
|
base_delay = 100,
|
|
max_delay = 5000
|
|
)
|
|
|
|
# Extract the text message
|
|
for (dataname, data, data_type) in payloads
|
|
if data_type == "text"
|
|
println("Received message: $data")
|
|
# Save to file
|
|
write("./received_$dataname.txt", data)
|
|
end
|
|
end
|
|
end
|
|
|
|
# Subscribe to the chat room
|
|
NATS.subscribe(SUBJECT) do msg
|
|
message_handler(msg)
|
|
end
|
|
|
|
# Keep the program running
|
|
while true
|
|
sleep(1)
|
|
end
|
|
```
|
|
|
|
## Step 4: Adding Image Support
|
|
|
|
### Sending an Image
|
|
|
|
```julia
|
|
using NATS
|
|
using JSON
|
|
using UUIDs
|
|
using Dates
|
|
using PrettyPrinting
|
|
using DataFrames
|
|
using Arrow
|
|
using HTTP
|
|
using Base64
|
|
|
|
include("NATSBridge.jl")
|
|
using .NATSBridge
|
|
|
|
const NATS_URL = "nats://localhost:4222"
|
|
const FILESERVER_URL = "http://localhost:8080"
|
|
const SUBJECT = "/chat/room1"
|
|
|
|
function plik_upload_handler(fileserver_url::String, dataname::String, data::Vector{UInt8})::Dict{String, Any}
|
|
url_getUploadID = "$fileserver_url/upload"
|
|
headers = ["Content-Type" => "application/json"]
|
|
body = """{ "OneShot" : true }"""
|
|
httpResponse = HTTP.request("POST", url_getUploadID, headers, body; body_is_form=false)
|
|
responseJson = JSON.parse(String(httpResponse.body))
|
|
uploadid = responseJson["id"]
|
|
uploadtoken = responseJson["uploadToken"]
|
|
|
|
file_multipart = HTTP.Multipart(dataname, IOBuffer(data), "application/octet-stream")
|
|
url_upload = "$fileserver_url/file/$uploadid"
|
|
headers = ["X-UploadToken" => uploadtoken]
|
|
|
|
form = HTTP.Form(Dict("file" => file_multipart))
|
|
httpResponse = HTTP.post(url_upload, headers, form)
|
|
responseJson = JSON.parse(String(httpResponse.body))
|
|
|
|
fileid = responseJson["id"]
|
|
url = "$fileserver_url/file/$uploadid/$fileid/$dataname"
|
|
|
|
return Dict("status" => httpResponse.status, "uploadid" => uploadid, "fileid" => fileid, "url" => url)
|
|
end
|
|
|
|
function send_image()
|
|
# Read image file
|
|
image_data = read("screenshot.png", Vector{UInt8})
|
|
|
|
# Send with text message
|
|
env = NATSBridge.smartsend(
|
|
SUBJECT,
|
|
[
|
|
("text", "Check out this screenshot!", "text"),
|
|
("screenshot", image_data, "image")
|
|
],
|
|
nats_url = NATS_URL,
|
|
fileserver_url = FILESERVER_URL,
|
|
fileserverUploadHandler = plik_upload_handler,
|
|
size_threshold = 1_000_000,
|
|
correlation_id = string(uuid4()),
|
|
msg_purpose = "chat",
|
|
sender_name = "user-1"
|
|
)
|
|
|
|
println("Sent image with correlation ID: $(env.correlationId)")
|
|
end
|
|
|
|
send_image()
|
|
```
|
|
|
|
### Receiving an Image
|
|
|
|
```julia
|
|
using NATS
|
|
using JSON
|
|
using UUIDs
|
|
using Dates
|
|
using PrettyPrinting
|
|
using DataFrames
|
|
using Arrow
|
|
using HTTP
|
|
using Base64
|
|
|
|
include("NATSBridge.jl")
|
|
using .NATSBridge
|
|
|
|
const NATS_URL = "nats://localhost:4222"
|
|
const SUBJECT = "/chat/room1"
|
|
|
|
function message_handler(msg::NATS.Msg)
|
|
payloads = NATSBridge.smartreceive(
|
|
msg,
|
|
max_retries = 5,
|
|
base_delay = 100,
|
|
max_delay = 5000
|
|
)
|
|
|
|
for (dataname, data, data_type) in payloads
|
|
if data_type == "text"
|
|
println("Text: $data")
|
|
elseif data_type == "image"
|
|
# Save image to file
|
|
filename = "received_$dataname.bin"
|
|
write(filename, data)
|
|
println("Saved image: $filename")
|
|
end
|
|
end
|
|
end
|
|
|
|
NATS.subscribe(SUBJECT) do msg
|
|
message_handler(msg)
|
|
end
|
|
```
|
|
|
|
## Step 5: Handling Large Files with Link Transport
|
|
|
|
### Automatic Transport Selection
|
|
|
|
```julia
|
|
using NATS
|
|
using JSON
|
|
using UUIDs
|
|
using Dates
|
|
using PrettyPrinting
|
|
using DataFrames
|
|
using Arrow
|
|
using HTTP
|
|
using Base64
|
|
|
|
include("NATSBridge.jl")
|
|
using .NATSBridge
|
|
|
|
const NATS_URL = "nats://localhost:4222"
|
|
const FILESERVER_URL = "http://localhost:8080"
|
|
const SUBJECT = "/chat/room1"
|
|
|
|
function plik_upload_handler(fileserver_url::String, dataname::String, data::Vector{UInt8})::Dict{String, Any}
|
|
url_getUploadID = "$fileserver_url/upload"
|
|
headers = ["Content-Type" => "application/json"]
|
|
body = """{ "OneShot" : true }"""
|
|
httpResponse = HTTP.request("POST", url_getUploadID, headers, body; body_is_form=false)
|
|
responseJson = JSON.parse(String(httpResponse.body))
|
|
uploadid = responseJson["id"]
|
|
uploadtoken = responseJson["uploadToken"]
|
|
|
|
file_multipart = HTTP.Multipart(dataname, IOBuffer(data), "application/octet-stream")
|
|
url_upload = "$fileserver_url/file/$uploadid"
|
|
headers = ["X-UploadToken" => uploadtoken]
|
|
|
|
form = HTTP.Form(Dict("file" => file_multipart))
|
|
httpResponse = HTTP.post(url_upload, headers, form)
|
|
responseJson = JSON.parse(String(httpResponse.body))
|
|
|
|
fileid = responseJson["id"]
|
|
url = "$fileserver_url/file/$uploadid/$fileid/$dataname"
|
|
|
|
return Dict("status" => httpResponse.status, "uploadid" => uploadid, "fileid" => fileid, "url" => url)
|
|
end
|
|
|
|
function send_large_file()
|
|
# Create a large file (> 1MB triggers link transport)
|
|
large_data = rand(10_000_000) # ~80MB
|
|
|
|
env = NATSBridge.smartsend(
|
|
SUBJECT,
|
|
[("large_file", large_data, "binary")],
|
|
nats_url = NATS_URL,
|
|
fileserver_url = FILESERVER_URL,
|
|
fileserverUploadHandler = plik_upload_handler,
|
|
size_threshold = 1_000_000,
|
|
correlation_id = string(uuid4()),
|
|
msg_purpose = "chat",
|
|
sender_name = "user-1"
|
|
)
|
|
|
|
println("Uploaded large file to: $(env.payloads[1].data)")
|
|
println("Correlation ID: $(env.correlationId)")
|
|
end
|
|
|
|
send_large_file()
|
|
```
|
|
|
|
## Step 6: Audio and Video Support
|
|
|
|
### Sending Audio
|
|
|
|
```julia
|
|
using NATS
|
|
using JSON
|
|
using UUIDs
|
|
using Dates
|
|
using PrettyPrinting
|
|
using DataFrames
|
|
using Arrow
|
|
using HTTP
|
|
using Base64
|
|
|
|
include("NATSBridge.jl")
|
|
using .NATSBridge
|
|
|
|
const NATS_URL = "nats://localhost:4222"
|
|
const FILESERVER_URL = "http://localhost:8080"
|
|
const SUBJECT = "/chat/room1"
|
|
|
|
function plik_upload_handler(fileserver_url::String, dataname::String, data::Vector{UInt8})::Dict{String, Any}
|
|
url_getUploadID = "$fileserver_url/upload"
|
|
headers = ["Content-Type" => "application/json"]
|
|
body = """{ "OneShot" : true }"""
|
|
httpResponse = HTTP.request("POST", url_getUploadID, headers, body; body_is_form=false)
|
|
responseJson = JSON.parse(String(httpResponse.body))
|
|
uploadid = responseJson["id"]
|
|
uploadtoken = responseJson["uploadToken"]
|
|
|
|
file_multipart = HTTP.Multipart(dataname, IOBuffer(data), "application/octet-stream")
|
|
url_upload = "$fileserver_url/file/$uploadid"
|
|
headers = ["X-UploadToken" => uploadtoken]
|
|
|
|
form = HTTP.Form(Dict("file" => file_multipart))
|
|
httpResponse = HTTP.post(url_upload, headers, form)
|
|
responseJson = JSON.parse(String(httpResponse.body))
|
|
|
|
fileid = responseJson["id"]
|
|
url = "$fileserver_url/file/$uploadid/$fileid/$dataname"
|
|
|
|
return Dict("status" => httpResponse.status, "uploadid" => uploadid, "fileid" => fileid, "url" => url)
|
|
end
|
|
|
|
function send_audio()
|
|
# Read audio file (WAV, MP3, etc.)
|
|
audio_data = read("voice_message.mp3", Vector{UInt8})
|
|
|
|
env = NATSBridge.smartsend(
|
|
SUBJECT,
|
|
[("voice_message", audio_data, "audio")],
|
|
nats_url = NATS_URL,
|
|
fileserver_url = FILESERVER_URL,
|
|
fileserverUploadHandler = plik_upload_handler,
|
|
size_threshold = 1_000_000,
|
|
correlation_id = string(uuid4()),
|
|
msg_purpose = "chat",
|
|
sender_name = "user-1"
|
|
)
|
|
|
|
println("Sent audio message: $(env.correlationId)")
|
|
end
|
|
|
|
send_audio()
|
|
```
|
|
|
|
### Sending Video
|
|
|
|
```julia
|
|
using NATS
|
|
using JSON
|
|
using UUIDs
|
|
using Dates
|
|
using PrettyPrinting
|
|
using DataFrames
|
|
using Arrow
|
|
using HTTP
|
|
using Base64
|
|
|
|
include("NATSBridge.jl")
|
|
using .NATSBridge
|
|
|
|
const NATS_URL = "nats://localhost:4222"
|
|
const FILESERVER_URL = "http://localhost:8080"
|
|
const SUBJECT = "/chat/room1"
|
|
|
|
function plik_upload_handler(fileserver_url::String, dataname::String, data::Vector{UInt8})::Dict{String, Any}
|
|
url_getUploadID = "$fileserver_url/upload"
|
|
headers = ["Content-Type" => "application/json"]
|
|
body = """{ "OneShot" : true }"""
|
|
httpResponse = HTTP.request("POST", url_getUploadID, headers, body; body_is_form=false)
|
|
responseJson = JSON.parse(String(httpResponse.body))
|
|
uploadid = responseJson["id"]
|
|
uploadtoken = responseJson["uploadToken"]
|
|
|
|
file_multipart = HTTP.Multipart(dataname, IOBuffer(data), "application/octet-stream")
|
|
url_upload = "$fileserver_url/file/$uploadid"
|
|
headers = ["X-UploadToken" => uploadtoken]
|
|
|
|
form = HTTP.Form(Dict("file" => file_multipart))
|
|
httpResponse = HTTP.post(url_upload, headers, form)
|
|
responseJson = JSON.parse(String(httpResponse.body))
|
|
|
|
fileid = responseJson["id"]
|
|
url = "$fileserver_url/file/$uploadid/$fileid/$dataname"
|
|
|
|
return Dict("status" => httpResponse.status, "uploadid" => uploadid, "fileid" => fileid, "url" => url)
|
|
end
|
|
|
|
function send_video()
|
|
# Read video file (MP4, AVI, etc.)
|
|
video_data = read("video_message.mp4", Vector{UInt8})
|
|
|
|
env = NATSBridge.smartsend(
|
|
SUBJECT,
|
|
[("video_message", video_data, "video")],
|
|
nats_url = NATS_URL,
|
|
fileserver_url = FILESERVER_URL,
|
|
fileserverUploadHandler = plik_upload_handler,
|
|
size_threshold = 1_000_000,
|
|
correlation_id = string(uuid4()),
|
|
msg_purpose = "chat",
|
|
sender_name = "user-1"
|
|
)
|
|
|
|
println("Sent video message: $(env.correlationId)")
|
|
end
|
|
|
|
send_video()
|
|
```
|
|
|
|
## Step 7: Table/Data Exchange
|
|
|
|
### Sending Tabular Data
|
|
|
|
```julia
|
|
using NATS
|
|
using JSON
|
|
using UUIDs
|
|
using Dates
|
|
using PrettyPrinting
|
|
using DataFrames
|
|
using Arrow
|
|
using HTTP
|
|
using Base64
|
|
|
|
include("NATSBridge.jl")
|
|
using .NATSBridge
|
|
|
|
const NATS_URL = "nats://localhost:4222"
|
|
const FILESERVER_URL = "http://localhost:8080"
|
|
const SUBJECT = "/chat/room1"
|
|
|
|
function plik_upload_handler(fileserver_url::String, dataname::String, data::Vector{UInt8})::Dict{String, Any}
|
|
url_getUploadID = "$fileserver_url/upload"
|
|
headers = ["Content-Type" => "application/json"]
|
|
body = """{ "OneShot" : true }"""
|
|
httpResponse = HTTP.request("POST", url_getUploadID, headers, body; body_is_form=false)
|
|
responseJson = JSON.parse(String(httpResponse.body))
|
|
uploadid = responseJson["id"]
|
|
uploadtoken = responseJson["uploadToken"]
|
|
|
|
file_multipart = HTTP.Multipart(dataname, IOBuffer(data), "application/octet-stream")
|
|
url_upload = "$fileserver_url/file/$uploadid"
|
|
headers = ["X-UploadToken" => uploadtoken]
|
|
|
|
form = HTTP.Form(Dict("file" => file_multipart))
|
|
httpResponse = HTTP.post(url_upload, headers, form)
|
|
responseJson = JSON.parse(String(httpResponse.body))
|
|
|
|
fileid = responseJson["id"]
|
|
url = "$fileserver_url/file/$uploadid/$fileid/$dataname"
|
|
|
|
return Dict("status" => httpResponse.status, "uploadid" => uploadid, "fileid" => fileid, "url" => url)
|
|
end
|
|
|
|
function send_table()
|
|
# Create a DataFrame
|
|
df = DataFrame(
|
|
id = 1:5,
|
|
name = ["Alice", "Bob", "Charlie", "Diana", "Eve"],
|
|
score = [95, 88, 92, 98, 85],
|
|
grade = ['A', 'B', 'A', 'B', 'B']
|
|
)
|
|
|
|
env = NATSBridge.smartsend(
|
|
SUBJECT,
|
|
[("student_scores", df, "table")],
|
|
nats_url = NATS_URL,
|
|
fileserver_url = FILESERVER_URL,
|
|
fileserverUploadHandler = plik_upload_handler,
|
|
size_threshold = 1_000_000,
|
|
correlation_id = string(uuid4()),
|
|
msg_purpose = "chat",
|
|
sender_name = "user-1"
|
|
)
|
|
|
|
println("Sent table with $(nrow(df)) rows")
|
|
end
|
|
|
|
send_table()
|
|
```
|
|
|
|
### Receiving and Using Tables
|
|
|
|
```julia
|
|
using NATS
|
|
using JSON
|
|
using UUIDs
|
|
using Dates
|
|
using PrettyPrinting
|
|
using DataFrames
|
|
using Arrow
|
|
using HTTP
|
|
using Base64
|
|
|
|
include("NATSBridge.jl")
|
|
using .NATSBridge
|
|
|
|
const NATS_URL = "nats://localhost:4222"
|
|
const SUBJECT = "/chat/room1"
|
|
|
|
function message_handler(msg::NATS.Msg)
|
|
payloads = NATSBridge.smartreceive(
|
|
msg,
|
|
max_retries = 5,
|
|
base_delay = 100,
|
|
max_delay = 5000
|
|
)
|
|
|
|
for (dataname, data, data_type) in payloads
|
|
if data_type == "table"
|
|
data = DataFrame(data)
|
|
println("Received table:")
|
|
show(data)
|
|
println("\nAverage score: $(mean(data.score))")
|
|
end
|
|
end
|
|
end
|
|
|
|
NATS.subscribe(SUBJECT) do msg
|
|
message_handler(msg)
|
|
end
|
|
```
|
|
|
|
## Step 8: Bidirectional Communication
|
|
|
|
### Request-Response Pattern
|
|
|
|
```julia
|
|
using NATS
|
|
using JSON
|
|
using UUIDs
|
|
using Dates
|
|
using PrettyPrinting
|
|
using DataFrames
|
|
using Arrow
|
|
using HTTP
|
|
using Base64
|
|
|
|
include("NATSBridge.jl")
|
|
using .NATSBridge
|
|
|
|
const NATS_URL = "nats://localhost:4222"
|
|
const SUBJECT = "/api/query"
|
|
const REPLY_SUBJECT = "/api/response"
|
|
|
|
# Request
|
|
function send_request()
|
|
query_data = Dict("query" => "SELECT * FROM users")
|
|
|
|
env = NATSBridge.smartsend(
|
|
SUBJECT,
|
|
[("sql_query", query_data, "dictionary")],
|
|
nats_url = NATS_URL,
|
|
fileserver_url = "http://localhost:8080",
|
|
fileserverUploadHandler = plik_upload_handler,
|
|
size_threshold = 1_000_000,
|
|
correlation_id = string(uuid4()),
|
|
msg_purpose = "request",
|
|
sender_name = "frontend",
|
|
receiver_name = "backend",
|
|
reply_to = REPLY_SUBJECT,
|
|
reply_to_msg_id = string(uuid4())
|
|
)
|
|
|
|
println("Request sent: $(env.correlationId)")
|
|
end
|
|
|
|
# Response handler
|
|
function response_handler(msg::NATS.Msg)
|
|
payloads = NATSBridge.smartreceive(
|
|
msg,
|
|
max_retries = 5,
|
|
base_delay = 100,
|
|
max_delay = 5000
|
|
)
|
|
|
|
for (dataname, data, data_type) in payloads
|
|
if data_type == "table"
|
|
data = DataFrame(data)
|
|
println("Query results:")
|
|
show(data)
|
|
end
|
|
end
|
|
end
|
|
|
|
NATS.subscribe(REPLY_SUBJECT) do msg
|
|
response_handler(msg)
|
|
end
|
|
```
|
|
|
|
## Step 9: Complete Chat Application
|
|
|
|
### Full Chat System
|
|
|
|
```julia
|
|
module ChatApp
|
|
using NATS
|
|
using JSON
|
|
using UUIDs
|
|
using Dates
|
|
using PrettyPrinting
|
|
using DataFrames
|
|
using Arrow
|
|
using HTTP
|
|
using Base64
|
|
|
|
# Include the bridge module
|
|
include("../src/NATSBridge.jl")
|
|
using .NATSBridge
|
|
|
|
# Configuration
|
|
const NATS_URL = "nats://localhost:4222"
|
|
const FILESERVER_URL = "http://localhost:8080"
|
|
const SUBJECT = "/chat/room1"
|
|
|
|
# File upload handler for plik server
|
|
function plik_upload_handler(fileserver_url::String, dataname::String, data::Vector{UInt8})::Dict{String, Any}
|
|
url_getUploadID = "$fileserver_url/upload"
|
|
headers = ["Content-Type" => "application/json"]
|
|
body = """{ "OneShot" : true }"""
|
|
httpResponse = HTTP.request("POST", url_getUploadID, headers, body; body_is_form=false)
|
|
responseJson = JSON.parse(String(httpResponse.body))
|
|
uploadid = responseJson["id"]
|
|
uploadtoken = responseJson["uploadToken"]
|
|
|
|
file_multipart = HTTP.Multipart(dataname, IOBuffer(data), "application/octet-stream")
|
|
url_upload = "$fileserver_url/file/$uploadid"
|
|
headers = ["X-UploadToken" => uploadtoken]
|
|
|
|
form = HTTP.Form(Dict("file" => file_multipart))
|
|
httpResponse = HTTP.post(url_upload, headers, form)
|
|
responseJson = JSON.parse(String(httpResponse.body))
|
|
|
|
fileid = responseJson["id"]
|
|
url = "$fileserver_url/file/$uploadid/$fileid/$dataname"
|
|
|
|
return Dict("status" => httpResponse.status, "uploadid" => uploadid, "fileid" => fileid, "url" => url)
|
|
end
|
|
|
|
function send_chat_message(
|
|
text::String,
|
|
image_path::Union{String, Nothing}=nothing,
|
|
audio_path::Union{String, Nothing}=nothing
|
|
)
|
|
# Build payloads list
|
|
payloads = [("message_text", text, "text")]
|
|
|
|
if image_path !== nothing
|
|
image_data = read(image_path, Vector{UInt8})
|
|
push!(payloads, ("user_image", image_data, "image"))
|
|
end
|
|
|
|
if audio_path !== nothing
|
|
audio_data = read(audio_path, Vector{UInt8})
|
|
push!(payloads, ("user_audio", audio_data, "audio"))
|
|
end
|
|
|
|
env = NATSBridge.smartsend(
|
|
SUBJECT,
|
|
payloads,
|
|
nats_url = NATS_URL,
|
|
fileserver_url = FILESERVER_URL,
|
|
fileserverUploadHandler = plik_upload_handler,
|
|
size_threshold = 1_000_000,
|
|
correlation_id = string(uuid4()),
|
|
msg_purpose = "chat",
|
|
sender_name = "user-1"
|
|
)
|
|
|
|
println("Message sent with correlation ID: $(env.correlationId)")
|
|
end
|
|
|
|
function receive_chat_messages()
|
|
function message_handler(msg::NATS.Msg)
|
|
payloads = NATSBridge.smartreceive(
|
|
msg,
|
|
max_retries = 5,
|
|
base_delay = 100,
|
|
max_delay = 5000
|
|
)
|
|
|
|
println("\n--- New Message ---")
|
|
for (dataname, data, data_type) in payloads
|
|
if data_type == "text"
|
|
println("Text: $data")
|
|
elseif data_type == "image"
|
|
filename = "received_$dataname.bin"
|
|
write(filename, data)
|
|
println("Image saved: $filename")
|
|
elseif data_type == "audio"
|
|
filename = "received_$dataname.bin"
|
|
write(filename, data)
|
|
println("Audio saved: $filename")
|
|
elseif data_type == "table"
|
|
println("Table received:")
|
|
data = DataFrame(data)
|
|
show(data)
|
|
end
|
|
end
|
|
end
|
|
|
|
NATS.subscribe(SUBJECT) do msg
|
|
message_handler(msg)
|
|
end
|
|
println("Subscribed to: $SUBJECT")
|
|
end
|
|
|
|
function run_interactive_chat()
|
|
println("\n=== Interactive Chat ===")
|
|
println("1. Send a message")
|
|
println("2. Join a chat room")
|
|
println("3. Exit")
|
|
|
|
while true
|
|
print("\nSelect option (1-3): ")
|
|
choice = readline()
|
|
|
|
if choice == "1"
|
|
print("Enter message text: ")
|
|
text = readline()
|
|
send_chat_message(text)
|
|
elseif choice == "2"
|
|
receive_chat_messages()
|
|
elseif choice == "3"
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
end # module
|
|
|
|
# Run the chat app
|
|
using .ChatApp
|
|
ChatApp.run_interactive_chat()
|
|
```
|
|
|
|
## Step 10: Testing the Chat System
|
|
|
|
### Test Scenario 1: Text-Only Chat
|
|
|
|
```bash
|
|
# Terminal 1: Start the chat receiver
|
|
julia test_julia_to_julia_text_receiver.jl
|
|
|
|
# Terminal 2: Send a message
|
|
julia test_julia_to_julia_text_sender.jl
|
|
```
|
|
|
|
### Test Scenario 2: Image Chat
|
|
|
|
```bash
|
|
# Terminal 1: Receive messages
|
|
julia test_julia_to_julia_mix_payloads_receiver.jl
|
|
|
|
# Terminal 2: Send image
|
|
julia test_julia_to_julia_mix_payload_sender.jl
|
|
```
|
|
|
|
### Test Scenario 3: Large File Transfer
|
|
|
|
```bash
|
|
# Terminal 2: Send large file
|
|
julia test_julia_to_julia_mix_payload_sender.jl
|
|
```
|
|
|
|
## Conclusion
|
|
|
|
This walkthrough demonstrated how to build a chat system using NATSBridge.jl with support for:
|
|
|
|
- Text messages
|
|
- Images (direct transport for small, link transport for large)
|
|
- Audio files
|
|
- Video files
|
|
- Tabular data (DataFrames)
|
|
- Bidirectional communication
|
|
- Mixed-content messages
|
|
|
|
The key takeaways are:
|
|
|
|
1. **Always wrap payloads in a list** - Even for single payloads: `[("dataname", data, "type")]`
|
|
2. **Use appropriate transport** - NATSBridge automatically handles size-based routing
|
|
3. **Support mixed content** - Multiple payloads of different types in one message
|
|
4. **Handle errors** - Implement proper error handling for network failures
|
|
5. **Use correlation IDs** - Track messages across distributed systems
|
|
|
|
For more information, see:
|
|
- [`docs/architecture.md`](./docs/architecture.md) - Detailed architecture documentation
|
|
- [`docs/implementation.md`](./docs/implementation.md) - Implementation details |