note
This article was last updated on April 27, 2023, 1 year ago. The content may be out of date.
Previously I have written a post about ttyd that describes how to authenticate requests for ttyd sessions. The problem is that vanilla ttyd only supports running a predefined command, and it’s nearly impossible to customize what command to run based on the authenticated user. To solve this problem we need to port ttyd to another language. But first we need to understand how ttyd works.
Request Waterfall
When a browser connects to a ttyd instance, below is the order in which resources are requested.
Downloading Frontend
The first request, whose url depends on ttyd configuration, is where ttyd frontend is downloaded.
Getting Auth Token
The second request is getting the auth token. It’s used later when establishing the websocket connection.
Connecting to Websocket
The third request is a websocket request. This is where most of the ttyd protocol is.
ttyd Websocket Protocol
We could reverse engineer ttyd protocol using devtools. But since ttyd is an open source tool, we can simply inspect its source code to find out how it’s working.
The definitions of message types are listed in server.h:
|
|
client message
is what clients send to the server and vice versa.
Client Message
protocol.c shows how ttyd servers are handling different types of client messages:
|
|
Command | Description |
---|---|
INPUT | If ttyd is configured as readonly, do nothing, else pass the data to the pty process |
RESIZE_TERMINAL | ttyp resizes the window of the pty process according to the size contained within the json data. |
PAUSE | ttyp pauses reading from the pty process. |
RESUME | ttyp resumes reading from the pty process. |
JSON_DATA | The opening brace is part of the data. The data contains the auth token and the window size of the client. ttyd checks if the auth token is valid and if so, spawns the predefined process with the window size. |
Server Message
From the frontend codes:
|
|
Command | Description |
---|---|
SET_WINDOW_TITLE | change the title of the browser tab in which the ttyd session is running to the specifiy value. |
SET_PREFERENCES | apply the preferences contained in the json data. |
OUTPUT | render the data using xterm.js |
Session Establishment
As can be seen from the first three messages of the websocket connection. A client first sends the JSON_DATA
message to the server. The server responds by sending two messages: SET_WINDOW_TITLE
and SET_PREFERENCES
.
Flow Control
When rendering the data using xterm.js, xterm.js can be overwhelmed with too much data. ttyd solves this problem by using flow control:
|
|
What it does is after having received data totalling a specific size, xterm.js renders the data and calls a callback function, increases the pending counter and reset the data counter. If the number of pending calls is above a certain threshold, frontend sends a RESUME
message. When xterm.js finishes rendering the data, it calls the callback function and if the number of pending calls is below another threshold, frontend sends a PAUSE
message.
info
RESUME
and PAUSE
should be swapped. It’s already fixed in 01f1ed5.
To Be Continued
The next part will deal with how to implement ttyd protocol in golang.