在 Flask 应用程序中将唯一的游戏 ID 绑定到用户以处理对服务器的多个请求

Tying a unique game id to a user in a Flask app to handle multiple requests to the server

我用 Python 构建了一个国际象棋应用程序,并使用 Flask 创建了一个网站供用户下棋。我使用 Heroku 来部署应用程序 (http://pythonchessapp.herokuapp.com/)。我是网络开发的新手,想知道如何处理多个用户(在不同的笔记本电脑或选项卡上)在网站上玩应用程序?比如每个用户都有一个唯一的游戏 ID 来为不同的请求提供不同的游戏。下面是我的一些路线和初始化游戏的代码。我基本上初始化了一个处理移动和跟踪棋盘状态的棋盘对象。我使用 js 将移动信息发送到服务器以进行移动。我还想在用户退出网站后结束游戏。有人有什么想法吗?

我只包括创建板和呈现初始页面的初始路由,以及处理执行动作的路由。

from logic.chess_board import Board
from logic.chess_pieces import *

b = Board()

@app.route('/', methods=["GET", "POST"])
@app.route('/chess', methods=["GET", "POST"])
def chess():

    flipped = b.flipped

    img_dict = b.board_html()

    return render_template('base.html', img_dict=img_dict, flipped=flipped)

@app.route('/execute', methods=['GET', 'POST'])
def execute():
    if request.method == "POST":

        castle = None
        error = False
        outcome = False
        empty = None

        sq_one = eval(request.get_json()['sq_one'])
        sq_two = eval(request.get_json()['sq_two'])
        piece = b.board[sq_one]

        if type(piece) == King and (piece.castle['king_side'] == sq_two or piece.castle['queen_side'] == sq_two):
            y = sq_one[1]
            if piece.castle['king_side'] == sq_two:
                r_one = str((8, y))
                r_two = str((6, y))

            elif piece.castle['queen_side'] == sq_two:
                r_one = str((1, y))
                r_two = str((4, y))
            castle = [r_one, r_two]

        try:
            b.move(sq_one, sq_two)
            if b.game_over():
                outcome = b.outcome
            empty = b.js_remove()
        except Exception as e:
            error = str(e)
        response = {'error': error, 'castle': castle, 'empty': empty, 'outcome': outcome}

    return make_response(jsonify(response))

如果您不想在服务器上存储任何信息,使用唯一 ID 是解决问题的好方法。每个请求都足以识别谁是谁。

但是,我认为最好的方法是使用 Flask 实现会话。 https://pythonbasics.org/flask-sessions/。这是一种在网络会话期间保留有关每个用户的信息的方法。

这可以通过库 cachelib 以 pickled 格式存储您的 Board 实例,使用 Flask 的 session 对象在 cookie 中存储唯一键来实现。

安装 pip install cachelib 或将 cachelib 添加到您的 requirements.txt

首先导入所需的库并初始化缓存:

from flask import Flask, session
import pickle
from uuid import uuid4
from cachelib.simple import SimpleCache

c = SimpleCache(default_timeout=0)

app.config['SECRET_KEY'] = 'somesupersecretkey'
app = Flask(__name__)

return 唯一 ID 的快速函数:

def generate_id():
    return uuid4().__str__()

我们不会在全局级别设置 b = Board(),而是在一个函数内执行此操作,然后 return 它。

所以我们可以定义一个加载板的函数。这将查看 session 对象(cookie 存储)中是否存在键 game_id。如果是,我们从缓存中加载板。如果没有,这个函数只会创建一个新的板。您还可以在此块的 else 子句中执行其他电路板初始化步骤:

def load_board():

    if 'game_id' in session:
        pb = c.get(session['game_id'])
        board = pickle.loads(pb)
    else:
        # Initialize new board
        board = Board()

    return board

现在我们创建一个保存棋盘的函数。这会立即腌制我们作为参数传递的 board,然后将其保存在缓存中。根据 session 对象(cookie 存储)中是否存在 game_id,它将使用该 ID 或生成一个新 ID。

def save_board(board):
    
    pb = pickle.dumps(board)

    if 'game_id' in session:
        c.set(session['game_id'], pb)
    else:
        unique_id = generate_id()
        session['game_id'] = unique_id
        c.set(unique_id, pb)

使用这些实用函数,您现在可以跨请求保留一个板:

@app.route('/chess', methods=["GET", "POST"])
def chess():

    b = load_board()

    flipped = b.flipped
    img_dict = b.board_html()

    save_board(b)

    return render_template('base.html', img_dict=img_dict, flipped=flipped)

那么另一条路线:

@app.route('/execute', methods=['GET', 'POST'])
def execute():
    if request.method == "POST":
        b = load_board()

        # All of your logic

        # Finally save the board 
        save_board(b)
        return make_response(jsonify(response))

您可能有不同的方法来设计此功能。SimpleCache 将所有内容存储在内存中,这应该没问题,假设您只有 运行 和 1 个 gunicorn worker。

最终,如果您的工作量超过了单个 worker,或者发现您的 web dyno 的内存占用量过高,您可以轻松地将 SimpleCache 换成 RedisCache,而无需更改太多逻辑。这也需要在 dyno 重新启动时保留数据。

cachelib 库很小,因此您可以 read the code 查看其他可用的后端和方法。