Implementation Notes

Access to Flask’s Context Globals

Handlers for SocketIO events are different than handlers for routes and thatintroduces a lot of confusion around what can and cannot be done in a SocketIOhandler. The main difference is that all the SocketIO events generated for aclient occur in the context of a single long running request.

In spite of the differences, Flask-SocketIO attempts to make working withSocketIO event handlers easier by making the environment similar to that of aregular HTTP request. The following list describes what works and what doesn’t:

  • An application context is pushed before invoking an event handler makingcurrent_app andg available to the handler.

  • A request context is also pushed before invoking a handler, also makingrequest andsession available. But note that WebSocket events do nothave individual requests associated with them, so the request context thatstarted the connection is pushed for all the events that are dispatchedduring the life of the connection.

  • Therequest context global is enhanced with asid member that is setto a unique session ID for the connection. This value is used as an initialroom where the client is added.

  • Therequest context global is enhanced withnamespace andeventmembers that contain the currently handled namespace and event arguments.Theevent member is a dictionary withmessage andargs keys.

  • Thesession context global behaves in a different way than in regularrequests. A copy of the user session at the time the SocketIO connection isestablished is made available to handlers invoked in the context of thatconnection. If a SocketIO handler modifies the session, the modified sessionwill be preserved for future SocketIO handlers, but regular HTTP routehandlers will not see these changes. Effectively, when a SocketIO handlermodifies the session, a “fork” of the session is created exclusively forthese handlers. The technical reason for this limitation is that to save theuser session a cookie needs to be sent to the client, and that requires HTTPrequest and response, which do not exist in a SocketIO connection. Whenusing server-side sessions such as those provided by the Flask-Session orFlask-KVSession extensions, changes made to the session in HTTP routehandlers can be seen by SocketIO handlers, as long as the session is notmodified in the SocketIO handlers.

  • Thebefore_request andafter_request hooks are not invoked forSocketIO event handlers.

  • SocketIO handlers can take custom decorators, but most Flask decorators willnot be appropriate to use for a SocketIO handler, given that there is noconcept of aResponse object during a SocketIO connection.

Authentication

A common need of applications is to validate the identity of their users. Thetraditional mechanisms based on web forms and HTTP requests cannot be used ina SocketIO connection, since there is no place to send HTTP requests andresponses. If necessary, an application can implement a customized login formthat sends credentials to the server as a SocketIO message when the submitbutton is pressed by the user.

However, in most cases it is more convenient to perform the traditionalauthentication process before the SocketIO connection is established. Theuser’s identity can then be recorded in the user session or in a cookie, andlater when the SocketIO connection is established that information will beaccessible to SocketIO event handlers.

Recent revisions of the Socket.IO protocol include the ability to pass adictionary with authentication information during the connection. This is anideal place for the client to include a token or other authentication details.If the client uses this capability, the server will provide this dictionary asan argument to theconnect event handler, as shown above.

Using Flask-Login with Flask-SocketIO

Flask-SocketIO can access login information maintained byFlask-Login. After aregular Flask-Login authentication is performed and thelogin_user()function is called to record the user in the user session, any SocketIOconnections will have access to thecurrent_user context variable:

@socketio.on('connect')defconnect_handler():ifcurrent_user.is_authenticated:emit('my response',{'message':'{0} has joined'.format(current_user.name)},broadcast=True)else:returnFalse# not allowed here

Note that thelogin_required decorator cannot be used with SocketIO eventhandlers, but a custom decorator that disconnects non-authenticated users canbe created as follows:

importfunctoolsfromflaskimportrequestfromflask_loginimportcurrent_userfromflask_socketioimportdisconnect,emitdefauthenticated_only(f):@functools.wraps(f)defwrapped(*args,**kwargs):ifnotcurrent_user.is_authenticated:disconnect()else:returnf(*args,**kwargs)returnwrapped@socketio.on('my event')@authenticated_onlydefhandle_my_custom_event(data):emit('my response',{'message':'{0} has joined'.format(current_user.name)},broadcast=True)