Как настроить небольшой пример клиент-сервера веб-сокета с помощью nim/prologue?
Я использую структуру пролога языка программирования nim для своего веб-сервера и хочу поиграть с веб-сокетами.
В документации пролога есть раздел о веб-сокетах, но в основном он рассказывает мне, как настроить обработчик для создания веб-сокета:
import prologue
import prologue/websocket
proc hello*(ctx: Context) {.async.} =
var ws = await newWebSocket(ctx)
await ws.send("Welcome to simple echo server")
while ws.readyState == Open:
let packet = await ws.receiveStrPacket()
await ws.send(packet)
resp "<h1>Hello, Prologue!</h1>"
Это не совсем говорит мне, как это на самом деле работает и как должен выглядеть клиент, чтобы подключиться к этому. Что мне здесь нужно сделать?
1 ответ
Клиент
Жизнеспособный клиент на стороне JS на самом деле не намного сложнее, чем просто написать: const url = "ws://localhost:8080/ws"
const ws = new WebSocket(url);
ws.addEventListener("open", () => ws.send("Connection open!"));
ws.addEventListener("message", event => console.log("Received: " event));
Это будет записывать сообщение в консоль браузера каждый раз при получении сообщения и первоначально отправлять сообщение на сервер при установке соединения.
Однако давайте для экспериментов напишем немного более сложный клиент, который покажет вам обмен сообщениями между вами и сервером:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Websocket Prototype</title>
</head>
<body>
<h1> Hyper client !</h1>
<input type="text">
<button> Send Message </button>
<h3> Conversation </h3>
<ul></ul>
<script>
const list = document.querySelector("ul");
function addMessage (sender, message){
const element = document.createElement("li");
element.innerHTML = `${sender}: ${message}`;
list.appendChild(element);
}
const url = "ws://localhost:8080/ws"
const ws = new WebSocket(url);
ws.addEventListener("open", event => ws.send("Connection open!"));
ws.addEventListener("message", event => addMessage("server", event.data));
const input = document.querySelector("input");
function sendMessage(){
const clientMsg = input.value;
ws.send(clientMsg);
addMessage("user", clientMsg);
input.value = null;
}
document.querySelector("button").addEventListener("click", sendMessage);
document.querySelector('input').addEventListener('keypress', (e) => {
if (e.key === 'Enter') {
sendMessage(event);
}
});
</script>
</body>
</html>
Сервер
Серверу необходимо сделать 2 вещи:- Обработка создания + получения сообщений веб-сокета
- Обслуживать клиента
1. Обработка создания и получения сообщений веб-сокета.
Вот как вы можете обрабатывать сообщения (Пролог использует древовидные формыws
библиотека под капотом):
import std/options
import prologue
import prologue/websocket
var connections = newSeq[WebSocket]()
proc handleMessage(ctx: Context, message: string): Option[string] =
echo "Received: ", message
return some message
proc initialWebsocketHandler*(ctx: Context) {.async, gcsafe.} =
var ws = await newWebSocket(ctx)
{.cast(gcsafe).}:
connections.add(ws)
await ws.send("Welcome to simple echo server")
while ws.readyState == Open:
let message = await ws.receiveStrPacket()
let response = ctx.handleMessage(message)
if response.isSome():
await ws.send(response.get())
await ws.send("Connection is closed")
resp "<h1>Hello, Prologue!</h1>"
Пролог продолжает ждать внутри
Если вы хотите направить данное сообщение в определенные процессы, которые по-разному обрабатывают разные типы сообщений, вы можете реализовать это, начиная с
The
Примечание. Этот сервер не реализует механику сердцебиения (пакет websocket ее предоставляет) и не занимается закрытыми соединениями ! Итак, в настоящее время ваша последовательность подключений будет только заполняться.
2. Обслуживание клиента
Что касается обслуживания клиента, вы можете просто прочитать HTML-файл во время компиляции и использовать эту HTML-строку в качестве ответа:
proc client*(ctx: Context) {.async, gcsafe.} =
const html = staticRead("./client.html")
resp html
Остальная часть сервера
Затем ваш реальный сервер может использовать эти два процесса-обработчика (также известных как контроллеры), как вы обычно настраиваете приложение-пролог. Оба можно сделать довольно быстро: #server.nim
import prologue
import ./controller # Where the 2 handler/controller procs are located
proc main() =
var app: Prologue = newApp()
app.addRoute(
route = "/ws",
handler = initialWebsocketHandler,
httpMethod = HttpGet
)
app.addRoute(
route = "/client",
handler = client,
httpMethod = HttpGet
)
app.run()
main()