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

折月煮酒 提交于 2020-07-23 07:39:06

问题


I built a chess app with Python and used Flask to create a site for users to play on. I used Heroku to deploy the app (http://pythonchessapp.herokuapp.com/). I am new to web development and was wondering how I can handle multiple users (on separate laptops or tabs) going on the site to play the app? Something like having a unique game id per user to serve a different game to different requests. Below is some of my code for routes and initializing games. I basically initialize a Board object that handles moves and tracks board states. I use js to send info on moves to the server to make moves. I would also like to end a game after a user exits the site. Does anyone have any ideas?

I've only included the initial route that creates the board and renders the initial page, and the route that deals with executing moves.

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

回答1:


This could be achieved with the library cachelib to store your instance of Board in pickled format, using Flask's session object to store a unique key in the cookie.

Install with pip install cachelib or add cachelib to your requirements.txt.

Start by importing the required libs and initialising the cache:

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__)

A quick function to return a unique ID:

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

Instead of setting b = Board() at the global level, we will do this inside a function and return it.

So we could define a function which loads a board. This looks to see if the key game_id exists in the session object (cookie storage). If it does, we load the board from our cache. If not, this function will just create a new board. You could also do other board initialization steps in the else clause of this block:

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

Now we create a function which saves the board. This immediately pickles the board we pass as an argument, then saves it in the cache. Depending on whether a game_id exists in the session object (cookie storage) it will either use that ID, or generate a new one.

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)

With these utility functions, you can now persist a board across requests:

@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)

Then the other route:

@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))

There's probably different ways you could design this functionality in. SimpleCache stores everything in memory, which should be fine, assuming you only run with 1 gunicorn worker.

Eventually if you outgrew a single worker, or found the memory footprint of your web dyno was too high, you could switch SimpleCache out for RedisCache easily without changing much logic. This would also be needed to persist the data across dyno restarts.

The cachelib library is tiny so you can read the code to see the other available backends and methods.




回答2:


Using an unique ID is a good way to solve your problem if you don't want to store any information on the server. Each request is sufficient to identify who is who.

However, I would say the best way is to implement sessions with flask. https://pythonbasics.org/flask-sessions/. It's a way to persist information about each user during a web session.



来源:https://stackoverflow.com/questions/62963360/tying-a-unique-game-id-to-a-user-in-a-flask-app-to-handle-multiple-requests-to-t

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!