Slide 1

Slide 1 text

Fun with WebSocket and Flask Miguel Grinberg @miguelgrinberg

Slide 2

Slide 2 text

About Me ● Software Dev @ Rackspace ● Flask Mega-Tutorial ● Flask Web Development book ● APIs, Microservices, Socket.IO ● Python, JavaScript, C++ ● @miguelgrinberg ● blog.miguelgrinberg.com ● github.com/miguelgrinberg

Slide 3

Slide 3 text

What is WebSocket? ● Alternative to HTTP for client-server communication ● Benefits over HTTP ○ Dedicated connection ○ Bi-directional: client and server can freely send a message to the other party ○ Lightweight (works great on low powered devices such as the Raspberry Pi) ● Supported in “most” desktop and mobile web browsers currently in use ○ Internet Explorer, I’m looking at you!

Slide 4

Slide 4 text

What is ? ● Yet another client-server communication protocol for web applications ● Built on top of WebSocket and HTTP ● Benefits over plain WebSocket ○ Downgrades to long-polling transparently if WebSocket is not available ○ Socket.IO events are similar to function calls across client-server lines ○ Reconnects automatically after an interruption ○ The server can “broadcast” events to all clients or to groups of them ● Official server implementation written in JavaScript ● Official clients written in JavaScript, Swift, Java and C++ ● Several community ports to other languages

Slide 5

Slide 5 text

Socket.IO and Python ● Python Socket.IO server: python-socketio ● Adds Socket.IO to an existing web application ○ Any WSGI compliant web server (Flask, Django, Bottle, Pyramid, etc.) ○ Asyncio web servers based on aiohttp and sanic ○ For Flask, flask-socketio provides improved integration ● Python Socket.IO client: socketio-client

Slide 6

Slide 6 text

WebSocket and Async ● WebSocket servers maintain a long-term connection to each client ● Async is the most practical way to support large number of active connections ● WSGI apps must run on eventlet or gevent to get an async wrapper

Slide 7

Slide 7 text

Let’s Learn Socket.IO By Example! ● Source Repository: https://github.com/miguelgrinberg/socketio-examples ● Five examples combined into a single application ● All examples have a Python server and a JavaScript client ● See README.md for setup instructions

Slide 8

Slide 8 text

Example #1: Chat Demonstrates: event handlers, broadcasting

Slide 9

Slide 9 text

Chat Example var socketio = io.connect(location.origin); post_message.onchange = function(message) { socketio.emit('post-message', message); } socketio.on('message', function(message) { display_message(message); } socketio = SocketIO(app) @socketio.on('connect') def on_connect(): if not authenticate_user(): return False emit('message', user + 'has joined.', broadcast=True) @socketio.on('post-message') def on_post_message(message): emit('message', message, broadcast=True) @socketio.on('disconnect') def on_disconnect(): emit('message', user + 'has left.', broadcast=True) socketio.run(app) ← Client (JavaScript) Server (Python) →

Slide 10

Slide 10 text

Example #2: Audio Streaming Demonstrates: binary streaming, Flask session integration

Slide 11

Slide 11 text

Audio Streaming Example function toggleRecording( e ) { if (!recording) { recording = true; socketio.emit('start-recording', {numChannels: 1, bps: 16, fps: 44100}); } else { recording = false; socketio.emit('end-recording'); } } socketio.on('add-wavefile', function(wavefile) { add_audio_to_page(wavefile); }); onaudioprocess = function(audioEvent) { if (recording) { socketio.emit('write-audio', audio_data); } } @socketio.on('start-recording') def start_recording(options): id = uuid.uuid4().hex session['wavename'] = id + '.wav' session['wavefile'] = wave.open(session['wavename']) configure_wavefile(session['wavefile'], options) @socketio.on('end-recording') def end_recording(): session['wavefile'].close() emit('add-wavefile', session['wavename'])) del session['wavefile'] del session['wavename'] @socketio.on('write-audio') def write_audio(data): session['wavefile'].writeframes(data) ← Client (JavaScript) Server (Python) →

Slide 12

Slide 12 text

Example #3: File Uploads Demonstrates: file transfers, “acks”

Slide 13

Slide 13 text

File Upload Example upload.onclick = function() { socketio.emit('start-transfer', filename, size, transfer_cb); } transfer_cb = function(filename) { if (!filename) abort_transfer(); for (var i = 0; i < num_chunks; i++) { chunk = read_next_chunk(); socketio.emit('write-chunk', filename, offset, chunk, chunk_cb); update_progress(offset + chunk.length); } } chunk_cb = function(ack) { if (!ack) abort_transfer(); } @socketio.on('start-transfer') def start_transfer(filename, size): _, ext = os.path.splitext(filename) if ext in ['.exe', '.bin', '.sh']: return False # reject the upload id = uuid.uuid4().hex create_file(id + ext, filename, size) return id + ext @socketio.on('write-chunk') def write_chunk(filename, offset, data): return write(filename, offset, data) ← Client (JavaScript) Server (Python) →

Slide 14

Slide 14 text

Example #4: Live Polls Demonstrates: namespaces, server-push

Slide 15

Slide 15 text

Polls Example var socketio = io.connect(location.origin + '/polls'); function onButtonClick() { socketio.emit('vote', this.answer); } var socketio = io.connect(location.origin + '/polls-admin'); socketio.on('update-charts', function(results) { update_charts(results); }); @socketio.on('vote', namespace='/polls') def on_voter_vote(question, answer): add_or_update_vote(question, answer) @socketio.on('connect', namespace='/polls-admin') def on_admin_connect(): emit('update-charts', get_poll_results()) def update_task(): while True: emit('update-charts', get_poll_results(), broadcast=True, namespace='/polls-admin') socketio.sleep(5) socketio.start_background_task(update_task) ← Admin Client (JavaScript) ← Client (JavaScript) Server (Python) →

Slide 16

Slide 16 text

Example #5: Where do you live? Demonstrates: cross-namespace events

Slide 17

Slide 17 text

@socketio.on('drop-pin', namespace='/where') def on_drop_pin(lat, lng): add_or_update_pin(lat, lng) emit('update-pin', lat, lng, broadcast=True, namespace='/where-admin') @socketio.on('connect', namespace='/polls-admin') def on_admin_connect(): emit('update-pins', get_all_pins()) Maps Example var socketio = io.connect(location.origin + '/polls'); function onMapClick() { socketio.emit('drop-pin', lat, lng); } var socketio = io.connect(location.origin + '/polls-admin'); socketio.on('update-pin', function(lat, lng) { update_pin_on_map(lat, lng); }); socketio.on('update-pins', function(pins) { for (var i = 0; i < pins.length; i++) update_pin_on_map(pins[i].lat, pins[i].lng); }); ← Admin Client (JavaScript) ← Client (JavaScript) Server (Python) →

Slide 18

Slide 18 text

Your Turn To Have Fun! ● I hope this talk motivates you to play with Socket.IO! ● Full documentation and several examples available in the GitHub Repos ○ miguelgrinberg/python-socketio ○ miguelgrinberg/flask-socketio ○ miguelgrinberg/socketio-examples ● I answer Python Socket.IO questions on Stack Overflow, Twitter, etc. ● I gladly (desperately?) accept contributions! ○ New features ○ Wrappers for more web frameworks ○ Cool examples or tutorials ○ Documentation fixes and improvements

Slide 19

Slide 19 text

Thank You!