Source code

Revision control

Copy as Markdown

Other Tools

Part 3 - Deploy to the web
==========================
.. currentmodule:: websockets
.. admonition:: This is the third part of the tutorial.
* In the :doc:`first part <tutorial1>`, you created a server and
connected one browser; you could play if you shared the same browser.
* In this :doc:`second part <tutorial2>`, you connected a second browser;
you could play from different browsers on a local network.
* In this :doc:`third part <tutorial3>`, you will deploy the game to the
web; you can play from any browser connected to the Internet.
In the first and second parts of the tutorial, for local development, you ran
an HTTP server on ``http://localhost:8000/`` with:
.. code-block:: console
$ python -m http.server
and a WebSocket server on ``ws://localhost:8001/`` with:
.. code-block:: console
$ python app.py
Now you want to deploy these servers on the Internet. There's a vast range of
hosting providers to choose from. For the sake of simplicity, we'll rely on:
* GitHub Pages for the HTTP server;
* Heroku for the WebSocket server.
Commit project to git
---------------------
Perhaps you committed your work to git while you were progressing through the
tutorial. If you didn't, now is a good time, because GitHub and Heroku offer
git-based deployment workflows.
Initialize a git repository:
.. code-block:: console
$ git init -b main
Initialized empty Git repository in websockets-tutorial/.git/
$ git commit --allow-empty -m "Initial commit."
[main (root-commit) ...] Initial commit.
Add all files and commit:
.. code-block:: console
$ git add .
$ git commit -m "Initial implementation of Connect Four game."
[main ...] Initial implementation of Connect Four game.
6 files changed, 500 insertions(+)
create mode 100644 app.py
create mode 100644 connect4.css
create mode 100644 connect4.js
create mode 100644 connect4.py
create mode 100644 index.html
create mode 100644 main.js
Prepare the WebSocket server
----------------------------
Before you deploy the server, you must adapt it to meet requirements of
Heroku's runtime. This involves two small changes:
1. Heroku expects the server to `listen on a specific port`_, provided in the
``$PORT`` environment variable.
2. Heroku sends a ``SIGTERM`` signal when `shutting down a dyno`_, which
should trigger a clean exit.
Adapt the ``main()`` coroutine accordingly:
.. code-block:: python
import os
import signal
.. literalinclude:: ../../example/tutorial/step3/app.py
:pyobject: main
To catch the ``SIGTERM`` signal, ``main()`` creates a :class:`~asyncio.Future`
called ``stop`` and registers a signal handler that sets the result of this
future. The value of the future doesn't matter; it's only for waiting for
``SIGTERM``.
Then, by using :func:`~server.serve` as a context manager and exiting the
context when ``stop`` has a result, ``main()`` ensures that the server closes
connections cleanly and exits on ``SIGTERM``.
The app is now fully compatible with Heroku.
Deploy the WebSocket server
---------------------------
Create a ``requirements.txt`` file with this content to install ``websockets``
when building the image:
.. literalinclude:: ../../example/tutorial/step3/requirements.txt
:language: text
.. admonition:: Heroku treats ``requirements.txt`` as a signal to `detect a Python app`_.
:class: tip
That's why you don't need to declare that you need a Python runtime.
Create a ``Procfile`` file with this content to configure the command for
running the server:
.. literalinclude:: ../../example/tutorial/step3/Procfile
:language: text
Commit your changes:
.. code-block:: console
$ git add .
$ git commit -m "Deploy to Heroku."
[main ...] Deploy to Heroku.
3 files changed, 12 insertions(+), 2 deletions(-)
create mode 100644 Procfile
create mode 100644 requirements.txt
Follow the `set-up instructions`_ to install the Heroku CLI and to log in, if
you haven't done that yet.
Create a Heroku app. You must choose a unique name and replace
``websockets-tutorial`` by this name in the following command:
.. code-block:: console
$ heroku create websockets-tutorial
Creating ⬢ websockets-tutorial... done
If you reuse a name that someone else already uses, you will receive this
error; if this happens, try another name:
.. code-block:: console
$ heroku create websockets-tutorial
Creating ⬢ websockets-tutorial... !
▸ Name websockets-tutorial is already taken
Deploy by pushing the code to Heroku:
.. code-block:: console
$ git push heroku
... lots of output...
remote: Released v1
remote: https://websockets-tutorial.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
* [new branch] main -> main
You can test the WebSocket server with the interactive client exactly like you
did in the first part of the tutorial. Replace ``websockets-tutorial`` by the
name of your app in the following command:
.. code-block:: console
> {"type": "init"}
< {"type": "init", "join": "54ICxFae_Ip7TJE2", "watch": "634w44TblL5Dbd9a"}
Connection closed: 1000 (OK).
It works!
Prepare the web application
---------------------------
Before you deploy the web application, perhaps you're wondering how it will
locate the WebSocket server? Indeed, at this point, its address is hard-coded
in ``main.js``:
.. code-block:: javascript
const websocket = new WebSocket("ws://localhost:8001/");
You can take this strategy one step further by checking the address of the
HTTP server and determining the address of the WebSocket server accordingly.
Add this function to ``main.js``; replace ``python-websockets`` by your GitHub
username and ``websockets-tutorial`` by the name of your app on Heroku:
.. literalinclude:: ../../example/tutorial/step3/main.js
:language: js
:start-at: function getWebSocketServer
:end-before: function initGame
Then, update the initialization to connect to this address instead:
.. code-block:: javascript
const websocket = new WebSocket(getWebSocketServer());
Commit your changes:
.. code-block:: console
$ git add .
$ git commit -m "Configure WebSocket server address."
[main ...] Configure WebSocket server address.
1 file changed, 11 insertions(+), 1 deletion(-)
Deploy the web application
--------------------------
Go to GitHub and create a new repository called ``websockets-tutorial``.
Push your code to this repository. You must replace ``python-websockets`` by
your GitHub username in the following command:
.. code-block:: console
$ git remote add origin git@github.com:python-websockets/websockets-tutorial.git
$ git push -u origin main
Enumerating objects: 11, done.
Counting objects: 100% (11/11), done.
Delta compression using up to 8 threads
Compressing objects: 100% (10/10), done.
Writing objects: 100% (11/11), 5.90 KiB | 2.95 MiB/s, done.
Total 11 (delta 0), reused 0 (delta 0), pack-reused 0
To github.com:<username>/websockets-tutorial.git
* [new branch] main -> main
Branch 'main' set up to track remote branch 'main' from 'origin'.
Go back to GitHub, open the Settings tab of the repository and select Pages in
the menu. Select the main branch as source and click Save. GitHub tells you
that your site is published.
Follow the link and start a game!
Summary
-------
In this third part of the tutorial, you learned how to deploy a WebSocket
application with Heroku.
You can start a Connect Four game, send the JOIN link to a friend, and play
over the Internet!
Congratulations for completing the tutorial. Enjoy building real-time web
applications with websockets!
Solution
--------
.. literalinclude:: ../../example/tutorial/step3/app.py
:caption: app.py
:language: python
:linenos:
.. literalinclude:: ../../example/tutorial/step3/index.html
:caption: index.html
:language: html
:linenos:
.. literalinclude:: ../../example/tutorial/step3/main.js
:caption: main.js
:language: js
:linenos:
.. literalinclude:: ../../example/tutorial/step3/Procfile
:caption: Procfile
:language: text
:linenos:
.. literalinclude:: ../../example/tutorial/step3/requirements.txt
:caption: requirements.txt
:language: text
:linenos: