miguelgrinberg / flask-sock

Modern WebSocket support for Flask.
MIT License
274 stars 24 forks source link

registering blueprint with flask-sock Sock object #30

Closed nmz787-intel closed 1 year ago

nmz787-intel commented 2 years ago

I am trying to convert from flask-sockets but I used blueprints before, having so many routes in my project for normal webpages, and multiple uses of websockets.

I tried simply replacing my flask-sockets import with flask-sock, but get this error:

    sockets.register_blueprint(vnc.vnc_bp, url_prefix=r'/')
AttributeError: 'Sock' object has no attribute 'register_blueprint'

how can I proceed?

miguelgrinberg commented 2 years ago

There's no register_blueprint in this extension. See the documentation for usage.

nmz787-intel commented 2 years ago

I indeed searched the docs, there was no result for blueprint.

I also tried porting the register_blueprint method and associated members from flask_sockets, but while I got no errors, my client's websocket requests were not heard in the server.

miguelgrinberg commented 2 years ago

Have you considered not using a blueprint for your websocket route, which is how this is designed to work with this extension?

As I said, blueprints are currently not implemented, but this shouldn't impose any big limitations. For a Flask route you would need to have a global app object. This isn't the case for Flask-Sock, since your route is defined on the sock object, not on app.

nmz787-intel commented 2 years ago

Can you provide a quick 2-file example? One where your app and Sock is setup, and another with the routes for the Sock? In my current server, my main.py file has the app and flask_socket, and then I have many other files (blueprints) which have the actual routes and response logic. I have 2 separate files for different websocket pages/applications... which are currently registered with the main.py flask_socket object. So unless I make all my blueprints into classes, and then instantiate them after I create a flask_sock Sock object and pass that into their constructor, I'm at a loss for a quick and clean way to go about transitioning.

miguelgrinberg commented 2 years ago

Here you go. Added to this repo to help others with the same question: https://github.com/miguelgrinberg/flask-sock/tree/main/examples/blueprints

ceprio commented 1 year ago

Another solution is to change the code of flask-sock to support blueprints:

In flask_sock/__init__.py :
1) Add a reference to Blueprints by changing line 2:

from flask import Flask, Blueprint, request, Response, current_app

2) change the init function of class Sockets for this one:

    def __init__(self, app=None):
        self.app = None
        self.bp = None
        if app is None:
            self.bp = Blueprint('__flask_sock', __name__)
        else:
            if isinstance(app, Flask):
                self.app = app
                self.init_app(app)
            elif isinstance(app, Blueprint):
                self.bp = app
            else:
                raise AssertionError("Unknown type for app argument (must be Flask or Blueprint)")

Then in your blueprint file you can simply write:

app = Blueprint('MyBlueprint', __name__)
sockets= Sockets(app)
...
@sockets.route('/handler.sock')
def handler_socket(ws):
...

This works for me.

miguelgrinberg commented 1 year ago

@ceprio that is a really bad idea, unless you plan on maintaining your own fork of this package. You will not be able to install upgrade if you make custom changes. You are obviously free to do whatever you like, but for the vast majority of people this isn't a workable solution.

ceprio commented 1 year ago

@miguelgrinberg, I can also make a pull request when I get time. It would be for after the holidays. Though you seem negative about it: Do you see any issues in this implementation?

miguelgrinberg commented 1 year ago

@ceprio I'm negative about this change because it doesn't make this package better. There is no need to use a blueprint as the parent of this extension, that does not provide any benefit. The OP wasn't asking about this, they were asking about how to incorporate Flask-Sock in a multi-file app that has blueprints, not to initialize this extension inside of a blueprint.

ceprio commented 1 year ago

I see that we will not agree on this one. For me keeping the socket handler in the blueprint solution is of the upmost importance as each blueprint must be independant of each other, attached to that blueprint and the socket should be able to use the facilities of the blueprint. For example, when a user enters my blueprint I want this user to be authenticated and approved for this perticular blueprint. Same goes for the socket, if the socket is at the app level, I cannot validate permissions, if it is at the blueprint level then I can use Blueprint.before_request to validate that user can indeed use the socket.

Here is an example of this (with the changes to flask_sock described above):

from flask import Blueprint, Flask, render_template
from flask_sock import Sockets

app = Flask(__name__)
play = Blueprint('simple_page', 'simple_page')
play_socket = Sockets(play)

@app.route('/')
def first_hit():
    return 'Welcome to Websockets sample!<br>' + \
        '<a href="/play/">Go to the Blueprint</a>'

@play.route('/')
def hello():
    return 'You are in the Blueprint!<br>' + \
        '<a href="/play/echo_test">Run the test</a>'

@play.route('/echo_test', methods=['GET'])
def echo_test():
    return render_template('echo_test.html')

@play.before_request
def validate():
    print("Validate the user permissions here.")

@play_socket.route('/echo')
def echo_socket(ws):
    print(ws.environ['QUERY_STRING'])
    print("Socket opened")
    try:
        while True:
            message = ws.receive()
            ws.send(message[::-1])
    except Exception as e:
        print(f"ws.exception: {type(e)}")
    finally:
        if ws.connected:
            ws.close()
        print("Socket closed")

@play_socket.route('/status_ping')
def ping(ws):
    while not ws.closed:
        message = ws.receive()
        response = json.dumps({"Message":message, "Response":"Message received"})
        ws.send(response)

########### Register blueprints ###########
app.register_blueprint(play, url_prefix=r"/play")

if __name__ == '__main__':
    app.run()
miguelgrinberg commented 1 year ago

Thanks for the example. I will think about how to support this use case.

miguelgrinberg commented 1 year ago

@ceprio Just released Flask-Sock 0.6.0 with the option to put the WebSocket route in a blueprint. Example:

app = Flask(__name__)
play = Blueprint('play', __name__)
sock = Sock(app)

@sock.route('/ws', bp=play)
def websocket(ws):
    pass
ceprio commented 1 year ago

Thank you, your implementation is different than mine. I wonder why you are initializing the socket with the app instead of directly using the blueprint? You then need to specify the socket at each @sock.route. Was there any thing wrong with my code that I did not see?

miguelgrinberg commented 1 year ago

@ceprio Flask-Sock is a Flask extension. The normal way to use extensions is to initialize them on the application instance. Your solution does not agree with this standard way of initializing extensions, and would create complications if you want to use websocket routes in more than one blueprint.