如何使用nim/prologue建立一个小型的WebSocket客户端-服务器示例?

cl25kdpy  于 2023-08-05  发布在  其他
关注(0)|答案(1)|浏览(102)

我正在为我的Web服务器使用nim programming languageprologue framework,并希望使用websocket。
prologue docs中有一节是关于websocket的,但它主要告诉我如何设置用于建立websocket的处理程序:

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>"

字符串
这并没有告诉我它实际上是如何工作的,也没有告诉我客户端需要什么样子才能连接到它。我需要在这里做什么?

j2datikz

j2datikz1#

客户端

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>

服务端

服务器需要做两件事:
1.处理创建+接收WebSocket消息
1.为客户服务

1.处理创建+接收WebSocket消息

下面是处理消息的方法(Prologue在后台使用了treeforms 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>"


只要WebSocket打开,Prologue就会在while循环中等待。函数handleMessage将在每次收到消息时被触发。
如果你想将一个给定的消息路由到以不同方式处理不同类型消息的特定进程,你可以从handleMessage开始实现它,并根据事件本身决定返回或不返回响应消息。
处理程序上的{.gcsafe.}杂注通知编译器,此proc * 假定 * 是垃圾收集安全的(在此proc运行时,不能访问可能被垃圾收集的内存)。这将导致编译出错,因为访问像connections这样的全局可变变量是nevergc-safe的,因为理论上它可能会消失。在这种情况下,这种情况不会发生,因为全局变量将在程序的整个运行时都存在。所以我们必须通知编译器使用{.cast(gcsafe).}:是可以的。
注意:此服务器不实现心跳机制(WebSocket包提供了一个),也不处理 * 关闭的连接 *!因此,目前您的连接seq将只填充。

2.服务客户端

至于为客户端提供服务,您可以在编译时读取HTML文件并将该HTML字符串作为响应提供:

proc client*(ctx: Context) {.async, gcsafe.} =
  const html = staticRead("./client.html")
  resp html

服务器其余部分

然后,您的实际服务器可以使用这2个handler-procs(又名控制器),就像您通常设置一个序言应用程序一样。

#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()

相关问题