May 2, 20244 minutes
Exploring a typical software stack for a Klipper 3D Printer.
The software stack for a typical Klipper setup encompasses several key components. First is Klipper itself, serving as the 3D printer firmware. Then there’s Moonraker, functioning as the web API layer. Additionally, there’s a client application, such as Mainsail or Fluidd. Finally, there’s typically a camera service that provides the video stream.
Klipper operates locally and exposes its interface using a Unix Domain Socket. To extend this interface over the network, Moonraker connects to it and facilitates communication through a defined HTTP(S)/Websocket protocol. This setup enables any HTTP server to host a WebUI for printer control, including options like nginx, lighttpd, apache, or even python’s built-in HTTP server module. Since camera services usually operate their own servers, their streams are readily accessible over the network. With these combined services, users gain a modern and powerful means to manage their printers locally.
Let’s show this as a sequence diagram:
When Klipper starts, the Klippy service creates a Unix Domain Socket in /tmp/klippy_uds
for communication. Moonraker connects and communicates with Klippy over this socket.
class KlippyConnection:
def __init__(self, config: ConfigHelper) -> None:
self.server = config.get_server()
self.uds_address = config.getpath(
"klippy_uds_address", pathlib.Path("/tmp/klippy_uds")
)
...
Moonraker also enables various controls over its WebAPI, e.g. managing the host services, reboot, and etc.
If webcam stream is needed, then running ustreamer will expose a stream on port 8080
. With Moonraker, Klipper, and ustreamer running, serving Mainsail asssets with a HTTP server like nginx will complete the asks. Taking this a step futher, the above diagram has nginx acting as a reverse proxy (on top of serving the Mainsail assets). As a reverse proxy, nginx acts as a middleware and relays communcations between Mainsail and the proxied services (Moonraker/ustreamer).
The nginx configuration would look something like this:
1http {
2 # enable websocket proxying
3 map $http_upgrade $connection_upgrade {
4 default upgrade;
5 '' close;
6 }
7
8 server {
9 listen 80 default_server;
10 ...
11
12 # mainsail static assets
13 root /home/pi/mainsail;
14
15 index index.html;
16 server_name _;
17
18 # disable proxy request buffering
19 proxy_request_buffering off;
20
21 # root serves index.html
22 location / {
23 try_files $uri $uri/ /index.html;
24 }
25
26 # proxy /websocket to moonraker (apiserver)
27 location /websocket {
28 proxy_pass http://apiserver/websocket;
29 proxy_http_version 1.1;
30 proxy_set_header Upgrade $http_upgrade;
31 proxy_set_header Connection $connection_upgrade;
32 proxy_set_header Host $http_host;
33 proxy_set_header X-Real-IP $remote_addr;
34 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
35 proxy_read_timeout 86400;
36 }
37
38 # proxy http requests to moonraker (apiserver)
39 location ~ ^/(printer|api|access|machine|server)/ {
40 proxy_pass http://apiserver$request_uri;
41 proxy_http_version 1.1;
42 proxy_set_header Upgrade $http_upgrade;
43 proxy_set_header Host $http_host;
44 proxy_set_header X-Real-IP $remote_addr;
45 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
46 proxy_set_header X-Scheme $scheme;
47 }
48
49 # proxy http request to ustreamer (ustreamer)
50 location /webcam/ {
51 postpone_output 0;
52 proxy_buffering off;
53 proxy_ignore_headers X-Accel-Buffering;
54 access_log off;
55 error_log off;
56 proxy_pass http://ustreamer1/;
57 }
58 }
59
60 # referenced by the location /websocket (moonraker running on 7125)
61 upstream apiserver {
62 ip_hash;
63 server 127.0.0.1:7125;
64 }
65
66 # referneced by location /webcam/ (ustreamer running on port 8080)
67 upstream ustreamer1 {
68 ip_hash;
69 server 127.0.0.1:8080;
70 }
71}
Find a full nginx.conf
for such reverse proxy setup in MainsailOS
.
Let’s breakdown and explain some of the sections.
Lines 3-6
, this condition nginx to send a Connection: Upgrade
header when a Upgrade
header is present. This enables Moonraker and its clients to switch from HTTP to Websocket.
Line 9
, reverse proxy server will listen on port 80
.
Line 13
, nginx will look for Mainsail assets in this path and serve them over port 80
.
Lines 27-36
, proxies the request to :80/websocket
from clients. Make sure appropriate HTTP headers are passed, e.g. Connection: Upgrade
to upgrade protocols, X-Real-IP
for Moonraker’s truste_cllients
checks.
Lines 39-47
, proxies other moonraker requests. These are typically HTTP and not Websocket.
Lines 50-57
, proxies :80/webcam/
to :8080/
(ustreamer). The various settings prevents stream delays by disabling buffering.
Lines 61-64
, defines upstream apiserver
(moonraker) for referencing in this config, e.g. line 28, 40
.
Lines 67-70
, defines upstream ustreamer
(ustreamer) for referencing in this config, e.g. line 56
.
You can proxy more webcams by duplicating and renaming the location /webcam/
(line 50-57
) and upstream ustreamer
(lines 67-70
) sections.
Now you should have a high level understanding of how all these services connect and interact with each other. You should also be able to configure a simple reverse proxy using nginx.