Hello there. In this post, you are going to learn how to build a simple webchat using python and web sockets. Since that's the case, you are going to need to have python installed on your computer.
This project source code is available on this github repository and you can see it online at Papo Reto.
If you don't have the flask
package, you can install it with the following command: pip install Flask
. You'll also need flask_socketio
and eventlet
, so you can run pip install flask_socketio
and pip install eventlet
to install them.
Now that you have everything installed, this is how the files and directories of our project will look like:
templates
- index.html
static
css
- style.css
- main.py
The core of our project is the main.py
file, that's what will control our application. It will look like this:
#! -*- enconding: utf-8 -*-
from flask import Flask, render_template
from flask_socketio import SocketIO, emit
app = Flask(__name__, template_folder='templates', static_folder='static', static_url_path='/static/')
socketio = SocketIO(app)
@app.route('/')
def index():
return render_template('index.html')
@socketio.on('client_message')
def receive_message (client_msg):
emit('server_message', client_msg, broadcast=True)
if __name__ == '__main__':
socketio.run(app)
The first two lines of code import the packages and modules we are going to use in this project.
from flask import Flask, render_template
from flask_socketio import SocketIO, emit
Flask
is our web framework, and flask_socketio
is a package that allows flask
to use full-duplex low-latency communications protocols like websockets.
Then we instantiate our flask application and define where the templates and static files are going to be. We also instantiate our socketio object and pass the flask application as a argument.
app = Flask(__name__, template_folder='templates', static_folder='static', static_url_path='/static/')
socketio = SocketIO(app)
We now define our default route which is going to return the index.html
page:
@app.route('/')
def index():
return render_template('index.html')
When the user sends a message, it will emit the event client_message to the server, passing the nickname and the message as parameters. The server will then broadcast this message to every connected user:
@socketio.on('client_message')
def receive_message (client_msg):
emit('server_message', client_msg, broadcast=True)
Finally, we'll start to run our application with the following lines:
if __name__ == '__main__':
socketio.run(app)
That's it, our main.py
file is now complete, and that take us to the index.html
file. In this case, it will have 3 major parts: the message-box
, the input-box
and the nickname-box
. The contents of this file will be:
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<title>Papo Reto</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="//cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js" integrity="sha256-yr4fRk/GU1ehYJPAs8P4JlTgu0Hdsp4ZKrx8bDEDC3I=" crossorigin="anonymous"></script>
</head>
<body>
<script type="text/javascript" charset="utf-8">
const socket = io();
socket.on('server_message', (data) => {
let e = document.createElement('p');
let sp = document.createElement('span');
sp.innerHTML = data.nickname;
e.appendChild(sp);
e.innerHTML = e.innerHTML+'>> '+data.message;
if(document.getElementById('message-box').children.length>20){
document.getElementById('message-box').removeChild(document.getElementById('message-box').children[0]);
}
document.getElementById('message-box').appendChild(e);
document.getElementById('message-box').scroll(0, document.getElementById('message-box').scrollHeight);
});
function htmlEntities(str) {
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
}
function sendMessage(){
socket.emit('client_message', {'nickname': htmlEntities(document.getElementById('nickname-input').value), 'message': htmlEntities(document.getElementById('message-input').value)});
document.getElementById('message-input').value = '';
}
function isPressingEnter(e){
let k;
if(window.event){
k = e.keyCode;
if(k===13){
sendMessage();
}
}else if(e.which){
k = e.which;
if(k===13){
sendMessage();
}
}
}
</script>
<h1 id="title">Papo Reto</h1>
<section id="chat-box">
<section id="message-box">
</section>
<section id="input-box">
<input type="text" autofocus onkeypress="return isPressingEnter(event)" required placeholder="Digite sua mensagem aqui" id="message-input">
<button type="button" id="send-button" onclick="sendMessage()" >>></button>
</section>
</section>
<section id="nickname-box">
<label id="nickname-label" for="nickname-input">Nickname: </label>
<input type="text" id="nickname-input" autocomplete="off" value="Guest">
</section>
</body>
</html>
We are using the SocketIO client side api to connect and take care of handling the websockets. So in this case, we are using a CDN to do so:
<script src="//cdnjs.cloudflare.com/ajax/libs/socket.io/2.2.0/socket.io.js" integrity="sha256-yr4fRk/GU1ehYJPAs8P4JlTgu0Hdsp4ZKrx8bDEDC3I=" crossorigin="anonymous"></script>
Now, with javascript, we need to define our socket object, that will connect with the server:
const socket = io();
Since the websocket is on the same route, we don't need to pass anything as parameters.
When the user clicks on the send button, it will call the sendMessage
function. But if the user types something on the input with the input-box
id, it will call the isPressingEnter
function, which will verify if the user is pressed enter. If so, it will also call sendMessage()
:
function isPressingEnter(e){
let k;
if(window.event){
k = e.keyCode;
if(k===13){
sendMessage();
}
}else if(e.which){
k = e.which;
if(k===13){
sendMessage();
}
}
}
The sendMessage
function will then emit a client_message
event to the server, passing the nickname and the message as data:
function sendMessage(){
socket.emit('client_message', {'nickname': htmlEntities(document.getElementById('nickname-input').value), 'message': htmlEntities(document.getElementById('message-input').value)});
document.getElementById('message-input').value = '';
}
We're also using the htmlEntities
function to remove the HTML tags from the inputs:
function htmlEntities(str) {
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
}
When the server receives the message, it will broadcast the message to everybody by emiting the server_message
event. So we need to add this message to the message-box
when the server_message
event is received:
socket.on('server_message', (data) => {
let e = document.createElement('p');
let sp = document.createElement('span');
sp.innerHTML = data.nickname;
e.appendChild(sp);
e.innerHTML = e.innerHTML+'>> '+data.message;
if(document.getElementById('message-box').children.length>20){
document.getElementById('message-box').removeChild(document.getElementById('message-box').children[0]);
}
document.getElementById('message-box').appendChild(e);
document.getElementById('message-box').scroll(0, document.getElementById('message-box').scrollHeight);
});
If you now, run the server with python main.py
, you'll see our webchat is already working.
But since it's looking terrible, we'll add some css to it. Flask templates uses jinja
as its template engine, so it's a little bit different to add our css file path to the index
page. We can do so adding the following lines of code inside the head
tag:
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='./css/stylxe.css') }}">
You can do as you please, but in this case, that's the contents of our CSS file:
*{
margin: 0;
padding: 0;
}
body{
background: #202020;
color: #eee;
font-family: 'monospace';
line-height: 1.4em;
display: flex;
flex-direction: column;
justify-content: space-around;
align-items: center;
padding: 15px;
}
#title{
font-size: calc(20px + 2vw + 1vh);
padding: 40px;
}
#chat-box{
border: 2px solid #ccc;
border-radius: 10px;
box-shadow: 3px 4px 4px #111;
}
@media only screen and (max-width: 600px){
#chat-box{
width: 95vw;
}
}
@media only screen and (min-width: 600px){
#chat-box{
width: 80vw;
}
}
#message-box{
width: 100%;
height: 60vh;
background: #444;
overflow-y: scroll;
padding: 5px;
font-size: calc(10px + 1vh + 2vw);
}
#message-box p{
width: 100%;
line-height: 1.5em;
overflow-wrap: break-word;
padding: 5px 2px;
margin: 2px 0;
background: #4f4f4f;
}
#message-box span{
color: yellow;
font-weight: 900;
}
#input-box{
width: 100%;
height: 100%;
display: grid;
grid-template-columns: 90% 10%;
}
#message-input{
height: 90%;
font-size: calc(10px + 1vh + 2vw);
border-radius: 0 0 0 6px;
}
#send-button{
background: purple;
color: #f0f0a0;
height: 100%;
font-size: calc(10px + 1vh);
font-weight: 900;
}
#nickname-box{
padding: 15px;
}
#nickname-input {
border-radius: 5px;
background: purple;
color: white;
padding: 2px;
font-size: calc(10px + 1vh + 1vw);
}
#nickname-label{
font-size: calc(10px + 1vh + 1vw);
}
Thank you for your attention! That's one of the ways we can build a really simple webchat using python. Also, it's pretty easy to do so, but if you guys want, I can also show how to deploy it to heroku. So you can let it run online.
Top comments (4)
Thanks!
You're welcome o/
I loved it! Thanks :)
I'm glad you liked it o/