Warning: Bocadillo is now UNMAINTAINED. Users are recommended to migrate to a supported alternative, such as Starlette or FastAPI. Please see #344 for more information.
socket.io
Real-time messaging with If you're interested in using socket.io (a.k.a. SocketIO) to build a real-time web application, Bocadillo got you covered.
In this guide, we'll go through the process of rewriting the socket.io chat tutorial with Bocadillo, python-socketio and the socket.io-client library.
All the code can be found in the socketio-example repository on GitHub.
Planning
What are we going to build, exactly?
Well, as described in the socket.io chat tutorial, we'll need:
- A backend socket.io server to handle incoming messages and broadcast them to all connected clients. This will be done via python-socketio.
- A JavaScript socket.io client to send messages typed by the user, and listen to and display messages from other users. We'll use the socket.io-client Node.js package for this purpose.
- A web application server to serve the HTML page, the socket.io server and any static files we need. This is where Bocadillo kicks in!
Let's get down to business, shall we?
Basic application
First, we'll create the Bocadillo application. Install Bocadillo if not done already.
Due to python-engineio
not supporting ASGI3 yet (see #107), you need to use bocadillo < 0.15
for the moment.
Then, create the following app.py
file:
# app.py
from bocadillo import App, configure, Templates
app = App()
configure(app)
templates = Templates()
@app.route("/")
async def index(req, res):
res.html = await templates.render("index.html")
All we're doing here is creating a Bocadillo application with a root endpoint that serves an HTML page, which we're now going to set up. We'll start from the HTML page provided in the socket.io chat tutorial.
- First, create a
static
directory in the project root directory, and place the following CSS file there. You can check out the Static files guide to know how Bocadillo will pick that up.
/* static/styles.css */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font: 13px Helvetica, Arial;
}
form {
background: #000;
padding: 3px;
position: fixed;
bottom: 0;
width: 100%;
}
form input {
border: 0;
padding: 10px;
width: 90%;
margin-right: 0.5%;
}
form button {
width: 9%;
background: rgb(130, 224, 255);
border: none;
padding: 10px;
}
#messages {
list-style-type: none;
margin: 0;
padding: 0;
}
#messages li {
padding: 5px 10px;
}
#messages li:nth-child(odd) {
background: #eee;
}
- Next, create a
templates
directory and place the followingindex.html
HTML file there. See Templates if you need a quick refresher about serving templates in Bocadillo.
<!-- templates/index.html -->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Chat | Bocadillo + socket.io</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" type="text/css" href="/static/styles.css" />
</head>
<body>
<ul id="messages"></ul>
<form id="form" action="">
<input id="message" autocomplete="off" /><button>Send</button>
</form>
</body>
</html>
python-socketio
Integrating with It's now time we integrate python-socketio to build the socket.io server.
First, let's install it:
pip install python-socketio
Now, let's update the app.py
script:
import socketio
from bocadillo import App, configure, Templates
app = App()
configure(app)
templates = Templates()
sio = socketio.AsyncServer(async_mode="asgi")
app.mount("/sio", socketio.ASGIApp(sio))
...
Let's break this code down:
- We import the
socketio
package made available bypython-socketio
. - We create an
AsyncServer
instance. We need to use theasgi
async mode so that the server can be wrapped as an ASGI application (see Deployment strategies (python-socketio)). - We wrap the server in an
ASGIApp
. It implements the ASGI interface, so we can mount it under the/sio
URL prefix to have Bocadillo pass it on requests made to/sio*
.
The rest of app.py
is unchanged.
That's it! We've just integrated python-socketio
within our Bocadillo application. We're not quite done yet, but we'll only need to work with python-socketio
from now on.
socket.io-client
Integrating with To build the client, we'll use the socket.io-client library. Let's install it:
npm install --save socket.io-client
Our approach here is to install the socket.io
library and use the static JavaScript files distributed with it.
Alternatively, you could retrieve these files from a CDN. See also the JavaScript client documentation (socket.io).
Next, we'll update the app.py
script to serve the static files associated to the socket.io client:
# app.py
from bocadillo import App, Templates, static
app = App()
...
app.mount("/socket.io", static("node_modules/socket.io-client/dist"))
Finally, let's have the HTML page load the socket.io client by adding an <src>
tag after the page's <body>
:
<!-- templates/index.html -->
<!-- ... -->
<body>
<!-- ... -->
<script src="/socket.io/socket.io.js"></script>
</body>
All set! We can now proceed to build the application-level logic for the chat application.
Server: receiving and broadcasting messages
We'll start with the server-side application code. For this chat tutorial, all we need to do is listen to message
events (the name of the event is arbitrary) and broadcast the received message to all connected clients.
Remember: we have an sio
object reprensenting the asynchronous socket.io server. So, we can define an event handler to handle message
events, print the received message for debugging, and emit a response
event to all clients with the message contents:
# app.py
...
@sio.on("message")
async def broadcast(sid, data: str):
print("message:", data)
await sio.emit("response", data)
...
This code should be self-explanatory. If you're feeling unsure, be sure to check out the python-socketio
documentation on event handlers.
We're basically done with the socket.io server! Let's setup the code to connect to it on the client-side.
Client: connecting to the socket.io server
We need to add a new script to the HTML page that will connect to the socket.io server using the socket.io-client
library. This is how it should look like:
<!-- ... --->
<body>
<!-- ... --->
<script>
const socket = io({ path: "/sio/socket.io" });
socket.on("connect", () => {
console.log("Connected!");
});
socket.on("disconnect", () => {
console.log("Lost connection to the server.");
});
</script>
</body>
This code:
- Specifies that the socket.io client should connect to the server at the
/sio/socket.io
path on the same host (herelocalhost:8000
). This path corresponds to the prefix under which we mounted the socket.io server (/sio
) and the default path under whichpython-socketio
expects to receive connection requests (/socket.io
). - Adds two event handlers to show when socket.io manages to connect, or when it loses connection to the server.
At this point, if you fire up the application using uvicorn app:app
and connect to http://localhost:8000
, you should see a "Connected!"
message popping up in the browser console.
Client: handling messages
As a final step, we'll write the client-side code to a) send events when submitting the input form, and b) add new items to the list of messages when receiving messages from the server.
Remember that the body of the index.html
page contains the following snippet:
<ul id="messages"></ul>
<form id="form" action="">
<input id="message" autocomplete="off" /><button>Send</button>
</form>
So, to send a message to the other people in the chat, we need to hook onto the submission of the form
, and send a message
event with the contents of the input
element.
Then, to display messages received from other people, we'll need to add an event handler for the response
event, and dynamically append <li>
elements to the list of messages.
You can add the following code to the JavaScript script we previously added:
const formEl = document.getElementById("form");
const messageEl = document.getElementById("message");
const messageList = document.getElementById("messages");
formEl.onsubmit = event => {
event.preventDefault();
socket.emit("message", messageEl.value);
messageEl.value = "";
return false;
};
socket.on("response", message => {
console.log("response:", message);
const li = document.createElement("li");
li.innerText = message;
messageList.appendChild(li);
});
Wrapping up
If you've made it so far, congrats! You've just built a real-time chat application with Bocadillo, python-socketio and socket.io-client.
You can find all the code for this guide in the socketio-example repository. The full code contains useful comments, context and links, so be sure to check out the repo!