This project contains a client-server application for sending messages and files. See individual crates
- common (lib),
- client (bin),
- server (bin),
for details.
On the high-level, clients send messages and files to server using a custom protocol on one port (default 11111
)
that can run over mTLS.
Server exposes an unsecured (bring your own!) web interface on another port (default 8080
).
The web server also serves autogenerated OpenAPI documentation at http://localhost:8080/_docs/redoc
.
This repo contains self-signed certs (that are not used anywhere beside here) to get up and
running quickly. They're in directory ./ssl
.
ca.crt
- CA certificateca.key
- CA private keyclient1.crt
- Client certificateclient1.csr
- Client signing requestclient1.key
- Client private keyserver-localhost.crt
- Server certificateserver-localhost.csr
- Server signing requestserver-localhost.key
- Server private keyserver-localhost.bundle.crt
- Server certificate bundle (cat server-localhost.crt ca.crt > server-localhost.bundle.crt
)
To run without mTLS, disable default features using flag --no-default-features
as mTLS is enabled by default via feature
"mtls" in both client
and server
. Crate common has a feature named tls
.
Note
I had problems switching between non-TLS and TLS at runtime using Box<dyn _>
.
If you know how to do it without enum dispatch and implementing the required traits,
please let me know.
In directory ./server
, run command make setup
to
- Start Postgres, Prometheus and Grafana using docker-compose.
- Install Diesel CLI via Cargo.
- Run DB migrations.
For more info regarding DB, see here.
Web server exposes text-encoded metrics at http://localhost:8080/metrics
.
They use the default registry provided by crate prometheus
.
http_requests_total
: Total number of HTTP requests over server's lifetime. Excludes metrics and docs requests.messages_total
: Total number of messages handled by server. File streaming messages are counted as one.messages_received_bytes
: Total number of bytes received when handling messages.messages_sent_bytes
: Total number of bytes sent when handling messages.active_connections
: Number of active connections to the server.
Defines protocol for sending messages and files and how to write/read to/from network, CLI arguments
and other utilities used by both client
and server
. See ./common/src/proto.rs for details.
Use cargo run -- --help
to see usage:
Command-line arguments for the client
Usage: client [OPTIONS] --nick <NICKNAME> [SERVER_ADDRESS]
Arguments:
[SERVER_ADDRESS] Server address to bind to or connect to [default: 127.0.0.1:11111]
Options:
-n, --nick <NICKNAME>
--cert-domain <CERT_DOMAIN> Domain to require from the server [default: localhost]
--cert <CERT> Path to the client's certificate [default: ../ssl/client1.crt]
--key <KEY> Path to the client's private key [default: ../ssl/client1.key]
--ca-cert <CA_CERT> Path to the CA certificate [default: ../ssl/ca.crt]
-h, --help Print help
Commands are read from stdin and sent to the server. They have the following syntax:
.file <file-path> # send file
.image <file-path> # send image
.nick <new-nickname> # announce nickname to the server
<anything else> # send text message
Use cargo run -- --help
to see usage:
Command-line arguments for the server
Usage: server [OPTIONS] [SERVER_ADDRESS]
Arguments:
[SERVER_ADDRESS] Server address to bind to or connect to [default: 127.0.0.1:11111]
Options:
-r, --root <ROOT>
[default: .]
--cert <CERT>
Path to the server's certificate [default: ../ssl/server-localhost.bundle.crt]
--key <KEY>
Path to the server's private key [default: ../ssl/server-localhost.key]
--ca-cert <CA_CERT>
Path to the CA certificate used for authenticating clients [default: ../ssl/ca.crt]
--web_address <WEB_ADDRESS>
[default: 0.0.0.0:8080]
--actix_num_workers <ACTIX_NUM_WORKERS>
[default: 2]
--disable-docs
-h, --help
Print help
Besides the arguments, env var DATABASE_URL
with Postgres connection URL must be set.
Files are saved in <root-dir>/files
and images are saved in <root-dir>/images
.
Directories <root-dir>/files
and <root-dir>/images
are created if they don't exist.
Server handles connection on the main thread and spawns a new thread for each client.
diesel
and diesel_async
are used for user-data persistence on the server. To work with it, see their guide.
Regarding schema.rs
:
- Each message variant has its own table to simplify new variants in future. Common fields are in one common table each variant table references.
- File data is not saved to DB as FS is the natural choice here. DB remembers the file's SHA256 and path.
- I had trouble convincing Diesel to reference
message_text(message_id) -> message(id)
but it would generatemessage_text(message_id) -> message(message_id)
so I changed column names accordingly.
See tags with prefix "hw-".
OS | GNU/Linux |
Rust Installation | rustup from package manager |
Editor | VSCode |
Rust Extensions | rust-analyzer1, crates2, CodeLLDB3 |
Other Extensions | Vim4, EditorConfig5, Error Lens6, GH Copilot7 |
Footnotes
-
https://marketplace.visualstudio.com/items?itemName=rust-lang.rust-analyzer ↩
-
https://marketplace.visualstudio.com/items?itemName=serayuzgur.crates ↩
-
https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb ↩
-
https://marketplace.visualstudio.com/items?itemName=vscodevim.vim ↩
-
https://marketplace.visualstudio.com/items?itemName=EditorConfig.EditorConfig ↩
-
https://marketplace.visualstudio.com/items?itemName=usernamehw.errorlens ↩
-
https://marketplace.visualstudio.com/items?itemName=GitHub.copilot ↩