Quart и websocket: как отправлять данные только выбранным пользователям (личное сообщение)

#python #websocket #message #private #quart

#python #websocket #Сообщение #Частное #quart

Вопрос:

Я знаю, как транслировать, но я не могу настроить таргетинг на клиентов. Вот мой скрипт:

 import json    
import trio
from quart import render_template, websocket, render_template_string
from quart_trio import QuartTrio    
from quart_auth import current_user,login_required    
from quart_auth import AuthUser, login_user, logout_user, AuthManager    
import random

connections = set()

app = QuartTrio(__name__)
AuthManager(app)    
app.secret_key = "secret key"    

@app.route("/")
async def index():        
    clean_guy = await current_user.is_authenticated        
    if not clean_guy:        
        fake_ID = random.randrange(0, 9999) #quick dirty to test
        login_user(AuthUser(fake_ID)) 
        return await render_template_string("{{ current_user.__dict__ }}")
    return await render_template_string("{{ current_user.__dict__ }}")      

@app.websocket("/ws")
async def chat():
    try:
        connections.add(websocket._get_current_object())
        async with trio.open_nursery() as nursery:
            nursery.start_soon(heartbeat)
            while True:
                message = await websocket.receive()
                await broadcast(message)
    finally:
        connections.remove(websocket._get_current_object())


async def broadcast(message):
    for connection in connections:
        await connection.send(json.dumps({"type": "message", "value": message}))    

async def heartbeat():
    while True:
        await trio.sleep(1)
        await websocket.send(json.dumps({"type": "heartbeat"}))    

if __name__ == '__main__':    
    app.run(host='0.0.0.0', port=5000)
  

Вот мой шаблон:

 <div>      
  <div>
    <ul>    
    </ul>
  </div>
  <form>
    <input type="text">
    <button type="submit">Send</button>
  </form>
</div>

<script type="text/javascript">
  document.addEventListener("DOMContentLoaded", function() {
    const ws = new WebSocket(`ws://${window.location.host}/ws`);
    ws.onmessage = function(event) {
      const data = JSON.parse(event.data);
      if (data.type === "message") {
        const ulDOM = document.querySelectorAll("ul")[0];
        const liDOM = document.createElement("li");
        liDOM.innerText = data.value;
        ulDOM.appendChild(liDOM);
      }
    }
    document.querySelectorAll("form")[0].onsubmit = function(event) {
      event.preventDefault();
      const inputDOM = document.querySelectorAll("input")[0];
      ws.send(inputDOM.value);
      inputDOM.value = "";
      return false;
    };
  });
</script>
  

Также одна проблема:
если я использую это в своем скрипте:

 return await render_template("{{ current_user.__dict__ }}")
  

я не могу отобразить его с помощью моего шаблона jinja, даже если добавлю {{ current_user .dict }} в моем шаблоне.

Я также заметил, что:

  • с mozilla: я получаю что-то стабильное, например {‘_auth_id’: 9635, ‘action’:ПРОХОД: 2>}
  • с Chrome: он меняется при каждом обновлении, выглядит как {‘_auth_id’: 529, ‘action’:НАПИШИТЕ: 3>}

Мне нужно отобразить автора, адресата и ввод с помощью кнопки отправки, как исправить шаблон?

Возможно ли также отправлять сообщения целевым пользователям с помощью post через curl или websocat? как это сделать?

Ответ №1:

Quart-Auth использует файлы cookie для идентификации пользователя при каждом запросе / websocket-запросе, поэтому вы всегда можете получить идентификатор пользователя от current_user, если запрос аутентифицирован. Тогда для ваших нужд вам нужно будет сопоставить соединения websocket с каждым пользователем (чтобы вы могли настраивать сообщения), следовательно, сопоставление соединений должно быть словарем соединений, например

 import random
from collections import defaultdict

from quart import request, websocket
from quart_trio import QuartTrio      
from quart_auth import (
    AuthUser, current_user, login_required, login_user, logout_user, AuthManager 
)   

connections = defaultdict(set)

app = QuartTrio(__name__)
AuthManager(app)    
app.secret_key = "secret key"    

@app.route("/login", methods=["POST"])
async def login():
    # Figure out who the user is,
    user_id = random.randrange(0, 9999)
    login_user(AuthUser(fake_ID)) 
    return {}

@app.websocket("/ws")
@login_required
async def chat():
    user_id = await current_user.auth_id
    try:
        connections[user_id].add(websocket._get_current_object())
        while True:
            data = await websocket.receive_json()
            await broadcast(data["message"])
    finally:
        connections[user_id].remove(websocket._get_current_object())

@app.route('/broadcast', methods=['POST'])
@login_required
async def send_broadcast():   
    data = await request.get_json()
    await broadcast(data["message"], data.get("target_id"))
    return {}

async def broadcast(message, target = None):
    if target is None:
        for user_connections in connections.values():
            for connection in user_connections:
                await connection.send_json({"type": "message", "value": message})
    else:
        for connection in connections[target]:
            await connection.send_json({"type": "message", "value": message})

 
if __name__ == '__main__':    
    app.run(host='0.0.0.0', port=5000)
  

Затем вы можете отправить /broadcast либо JSON, который представляет собой просто сообщение {"message": "something"} , либо сообщение с идентификатором, предназначенное для кого-то конкретно {"message": "something for user 2", "target_id": 2} . Также обратите @login_required внимание, что декоратор гарантирует, что обработчик маршрута вызывается только для зарегистрированных пользователей.