HTTP
In this tutorial we will create a simple HTTP chat server.
Prerequisites
We assume that you have set up your development environment as described in the IDE tutorial.
We also assume that you have flashed your device with Jaguar and that you are familiar with running Toit programs on it. If not, have a look at the Hello world tutorial.
Note that you can do this tutorial without a device. In that case,
you need to use the -d host
option whenever you invoke
jag run
. The program will then run on your computer instead of on
a device.
Packages
The HTTP functionality is not part of the core libraries and must be imported as a package. See the packages tutorial for details.
We are using the http package. To install it, run the following command:
You can probably just write jag pkg install http
, but the full ID together
with the version is more explicit, and will make sure you get the right package.
Feel free to use a newer version of the package if one is available. You might need to update the code samples below if you do.
Architecture
The server will be a simple chat server. It will listen for incoming connections and accept messages from clients. It will then broadcast the messages to all connected clients.
We will implement two endpoints:
/
will serve a simple HTML page with a chat window./ws
will be the WebSocket endpoint for the chat.
For simplicity we will use chat.openai.com to create web page for us. The HTML/JavaScript below was created by asking chat.openai.com with the following prompt.
Create a simple, but stylish web-page for a chat-server with the following end-points: - `/` serves the page you provide. - `/ws`is the Websocket endpoint. Each message sent to through the Websocket should be a simple text message. Each message received from the Websocket is also text and should be displayed. The server broadcasts all messages to all connected clients (including the sender). Messages should be sent when the user presses Enter.
The following HTML and JavaScript was generated by chat.openai.com. It is not vetted by us. Use at your own risk.
<!DOCTYPE html> <html> <head> <title>Chat Server</title> <style> body { font-family: Arial, sans-serif; background-color: #F5F5F5; color: #333; padding: 10px; } #messages { height: 70vh; border: 1px solid #ddd; padding: 10px; border-radius: 5px; overflow-y: auto; margin-bottom: 10px; background-color: #fff; box-shadow: 0 0 10px rgba(0,0,0,0.1); } #input { width: 100%; height: 30px; padding: 5px; border: 1px solid #ddd; border-radius: 5px; } </style> </head> <body> <h1>Chat Server</h1> <div id="messages"></div> <input id="input" type="text" placeholder="Type your message and hit Enter..."> <script> var ws = new WebSocket('ws://' + window.location.host + '/ws'); var messages = document.getElementById('messages'); var input = document.getElementById('input'); ws.onmessage = function(event) { var message = document.createElement('p'); message.textContent = event.data; messages.appendChild(message); messages.scrollTop = messages.scrollHeight; }; input.addEventListener('keydown', function(event) { if (event.key === 'Enter') { ws.send(input.value); input.value = ''; } }); </script> </body> </html>
Store the content of this page in index.toit
as a string constant:
If you change the HTML make sure to escape the $
character in the string
which would be interpreted as a variable otherwise. See Toit's
string interpolation.
Code
We can now implement the actual server.
Create a new file called server.toit
and watch it with Jaguar.
Insert the following code:
import http import net import .index main: network := net.open server-socket := network.tcp-listen 0 port := server-socket.local-address.port print "Listening on http://$network.address:$port/" clients := [] server := http.Server --max-tasks=5 server.listen server-socket:: | request/http.RequestIncoming response-writer/http.ResponseWriter | if request.path == "/" or request.path == "/index.html": response-writer.headers.add "Content-Type" "text/html" response-writer.out.write INDEX-HTML else if request.path == "/ws": web-socket := server.web-socket request response-writer clients.add web-socket while data := web-socket.receive: clients.do: it.send data clients.remove web-socket else: response-writer.write-headers http.STATUS-NOT-FOUND --message="Not Found"
We start by creating a new server on an ephemeral port. We then print the port number so that we can connect to it.
After initializing the client
list, we create a new http.Server
instance. We set the max-tasks
to 5. This means that the server will
handle at most 5 requests concurrently. If more requests arrive, they
will be queued until a task becomes available. On a normal desktop
computer, this number can be much higher, but on a microcontroller, we
need to be more careful.
The listen
callback simply dispatches on the path:
/
or/index.html
for the index page that we created above./ws
for the WebSocket endpoint.
The websocket branch takes the existing request and upgrades it to a WebSocket. After that, we can use the WebSocket to send and receive messages.
Conclusion
We have now created a simple chat server that can be accessed from a web browser. We also demonstrated how to upgrade a regular HTTP connection to a WebSocket connection, and showed how to send and receive messages.