Chat App Using Flask, SocketIO and Google Colab

Posted by Shanaka DeSoysa on Sat 29 June 2019

Open In Colab

Chat App Using Flask, SocketIO and Google Colab

In this short tutorial, you'll learn how to create a simple but elegant chat app and host it on Google Cloud (for free) under 5 minutes .

Here's how the App would look like:

On Mobile:

Working Flask Chat app.

On Browser:

Working Flask Chat app.

What you'll build

  • A fully working real-time multi user chat app.
  • Mobile and web friendly Progressive Web App (PWA).
  • Public URL to share with family/friends.
  • Hosted on Google Cloud for free.

What you'll learn

  • Dveloping a useful Flask web app.
  • Using WebSocket with Flask-SocketIO for bi-directional real-time communication.
  • How to create a public URL with ngrok to share your web app.
  • How to run a web server on Colab.

What you'll need

  • Gmail account to access Google Colaboratory for free.
  • A browser such as Chrome.
  • The sample notebook. Click on the Open in Colab button below to get started.



Open In Colab

Python Packages

Packages required to run the app. You'll use pip to install these packages.

In [1]:
%%writefile requirements.txt
Flask==0.12.2
flask-socketio
eventlet==0.17.4
gunicorn==18.0.0
Overwriting requirements.txt

Flask App

Here you are defining event handlers to receive WebSocket (ws) messages using the socketio.on decorator and it sends reply messages to the connected client using the send() and emit() functions.

Also you are defining a simple http endpoint /hello for testing purpose.

In [2]:
%%writefile main.py
import os
import logging

from flask import Flask, render_template
from flask_socketio import SocketIO

secret = os.urandom(24).hex()

app = Flask(__name__)
app.logger.info("Starting...")
app.config['SECRET_KEY'] = secret
app.logger.critical("secret: %s" % secret)
socketio = SocketIO(app)

# render home page
@app.route('/')
def index():
    return render_template('index.html')

# Simple http endpoint
@app.route('/hello')
def hello():
    return "Hello World!"

# ws callback
def message_received(methods=['GET', 'POST']):
    app.logger.info('message was received!')

# ws event handler
@socketio.on('flask-chat-event')
def handle_flask_chat_event(json, methods=['GET', 'POST']):
    app.logger.info('received flask-chat-event: ' + str(json))
    socketio.emit('flask-chat-response', json, callback=message_received)

if __name__ == '__main__':
    socketio.run(app, debug=True)
Overwriting main.py

HTML Template

Here you are defineing:

  1. Web UI for the app.
  2. JavaScript functions to establish ws connection, send and receive messages with socket.io.
In [0]:
%mkdir templates -p
In [12]:
%%writefile templates/index.html
<!doctype html>
<html lang="en">

<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="description" content="Learn how to create a chat app using Flask">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Flask Chat</title>

  <!-- Disable tap highlight on IE -->
  <meta name="msapplication-tap-highlight" content="no">

  <!-- Web Application Manifest -->
  <link rel="manifest" href='data:application/manifest+json,{ "name": "Flask Chat", "short_name": "Flask Chat", "display": "standalone" }' />
  
  <!-- Add to homescreen for Chrome on Android -->
  <meta name="mobile-web-app-capable" content="yes">
  <meta name="application-name" content="Flask Chat">
  <meta name="theme-color" content="#303F9F">

  <!-- Add to homescreen for Safari on iOS -->
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
  <meta name="apple-mobile-web-app-title" content="Flask Chat">
  <meta name="apple-mobile-web-app-status-bar-style" content="#303F9F">

  <!-- Tile icon for Win8 -->
  <meta name="msapplication-TileColor" content="#3372DF">
  <meta name="msapplication-navbutton-color" content="#303F9F">
  <script async src="https://www.googletagmanager.com/gtag/js?id=UA-135532366-1"></script>
  <script>function gtag(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],gtag("js",new Date),gtag("config","UA-135532366-1");</script>

  <!-- Material Design Lite -->
  <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
  <link rel="stylesheet" href="https://code.getmdl.io/1.1.3/material.orange-indigo.min.css">
  <script defer src="https://code.getmdl.io/1.1.3/material.min.js"></script>

  <!-- App Styling -->
  <link rel="stylesheet"
    href="https://fonts.googleapis.com/css?family=Roboto:regular,bold,italic,thin,light,bolditalic,black,medium&amp;lang=en">
  <style>
    .message-container .spacing {
      display: table-cell;
      vertical-align: top;
    }

    .message-container .message {
      display: table-cell;
      width: calc(100% - 40px);
      padding: 5px 0 5px 10px;
    }

    .message-container .name {
      display: inline-block;
      width: 100%;
      padding-left: 40px;
      color: #bbb;
      font-style: italic;
      font-size: 12px;
      box-sizing: border-box;
    }
  </style>
</head>

<body>
  <div class="demo-layout mdl-layout mdl-js-layout mdl-layout--fixed-header">

    <!-- Header section containing logo -->
    <header class="mdl-layout__header mdl-color-text--white mdl-color--light-blue-700">
      <div class="mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-grid">
        <div class="mdl-layout__header-row mdl-cell mdl-cell--12-col mdl-cell--12-col-tablet mdl-cell--12-col-desktop">
          <h3><i class="material-icons">chat_bubble_outline</i> Flask Chat</h3>
        </div>
      </div>
    </header>

    <main class="mdl-layout__content mdl-color--grey-100">
      <div id="messages-card-container" class="mdl-cell mdl-cell--12-col mdl-grid">

        <!-- Messages container -->
        <div id="messages-card"
          class="mdl-card mdl-shadow--2dp mdl-cell mdl-cell--12-col mdl-cell--6-col-tablet mdl-cell--6-col-desktop">
          <div class="mdl-card__supporting-text mdl-color-text--grey-600">
            <div id="messages">
              <span id="message-filler"></span>
              <div class="message-container visible">
                <div class="spacing">
                </div>
                <div class="message">Welcome!</div>
                <div class="name">Shanaka DeSoysa</div>
              </div>
            </div>
            <form id="message-form" action="POST">
              <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
                <input class="mdl-textfield__input" type="text" id="username">
                <label class="mdl-textfield__label" for="username">User...</label>
              </div>
              <div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
                <input class="mdl-textfield__input" type="text" id="message">
                <label class="mdl-textfield__label" for="message">Message...</label>
              </div>
              <button id="submit" type="submit"
                class="mdl-button mdl-js-button mdl-button--raised mdl-js-ripple-effect">
                Send
              </button>
            </form>
          </div>
        </div>
      </div>
    </main>
  </div>
  <!-- jQuery (necessary for Bootstrap's JavaScript plugins) -->
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/1.7.3/socket.io.min.js"></script>
  <script type="text/javascript">
    var socket = io.connect('https://' + document.domain + ':' + location.port);

    socket.on('connect', function () {
      socket.emit('flask-chat-event', {
        data: 'User Connected'
      })
      var form = $('form').on('submit', function (e) {
        e.preventDefault()
        let user_name = $('#username').val()
        let user_input = $('#message').val()
        socket.emit('flask-chat-event', {
          user_name: user_name,
          message: user_input
        })
        $('#message').val('').focus()
      })
    })
    socket.on('flask-chat-response', function (msg) {
      console.log(msg)
      if (typeof msg.user_name !== 'undefined') {
        $('#messages').append('<div class="message-container visible"><div class="spacing"></div><div class="message">' + msg.message + '</div><div class="name">' + msg.user_name + '</div></div>')
      }
    })
  </script>
</body>

</html>
Overwriting templates/index.html

Installing Packages and Running Web Server

In [0]:
get_ipython().system_raw(
    'pip3 install -r requirements.txt && python3 main.py > logs.txt 2>&1 &'
)

Checking the Log File

You can check the log file with this command.

In [6]:
!tail logs.txt
[2019-06-29 18:04:09,804] CRITICAL in main: secret: 9aba1627141610d3ea12a10e7a54a08d4c595a7cb9496089
 * Restarting with stat
[2019-06-29 18:04:10,159] CRITICAL in main: secret: 74c1b6b00c53dce5d804e82e823e623c61224d955158072f
 * Debugger is active!
 * Debugger PIN: 127-941-347

Verifying the Web Server is Running

You can do a quick check if the server is up and running with curl.

In [7]:
# Make sure it's running on local port
PORT = 5000

!curl http://localhost:{PORT}/hello
Hello World!

Getting a Shareable Public URL from ngrok

Here you are installing ngrok and obtaining a shareable URL.

In [0]:
!wget --quiet https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip -O ngrok-stable-linux-amd64.zip
!unzip -q -f ngrok-stable-linux-amd64.zip

Running ngrok.

In [0]:
get_ipython().system_raw(
    './ngrok http {} &'
    .format(PORT)
)

Public URL

In [10]:
public_url = !curl -s http://localhost:4040/api/tunnels | python3 -c \
    "import sys, json; print(json.load(sys.stdin)['tunnels'][0]['public_url'])"

print(public_url[0])
http://8ab37579.ngrok.io

Verifying the Public URL is Accessible

In [11]:
!curl {public_url[0]}/hello
Hello World!

Congratulations! You have successfully built a chat application using Flask and WebSockets. You can share this URL with your friends and chat. You could also open multiple tabs in the browser to test it out. Don't forget to check it out on your mobile.

The source-code for the article can be found here.

Open In Colab


Comments !