An open API service indexing awesome lists of open source software.

https://github.com/steadylearner/chat

Learn how to build chat app with Rust ws-rs.
https://github.com/steadylearner/chat

Last synced: 4 months ago
JSON representation

Learn how to build chat app with Rust ws-rs.

Awesome Lists containing this project

README

          

[Steadylearner]: https://www.steadylearner.com
[Steadylearner Github Repository]: https://github.com/steadylearner/Steadylearner
[Steadylearner Chat]: https://github.com/steadylearner/Chat
[How to deploy Rust Web App]: https://medium.com/@steadylearner/how-to-deploy-rust-web-application-8c0e81394bd5?source=---------9------------------
[Steadylearner Post]: https://github.com/steadylearner/Steadylearner/tree/master/post
[LinkedIn]: https://www.linkedin.com/in/steady-learner-3151b7164/
[Twitter]: https://twitter.com/steadylearner_p

[prop-passer]: https://github.com/steadylearner/prop-passer

[How to install Rust]: https://www.rust-lang.org/learn/get-started
[Rust Documentation]: https://doc.rust-lang.org/std/
[cargo-edit]: https://github.com/killercup/cargo-edit
[Rocket]: https://rocket.rs/
[Concurrency in Rust]: https://blog.rust-lang.org/2015/04/10/Fearless-Concurrency.html

[Websocket API]: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API
[ws-rs]: https://ws-rs.org/
[ws-rs-html-example]: https://github.com/housleyjk/ws-rs/blob/master/examples/html_chat.rs
[socket-io]: https://socket.io/
[socket-io-and-ws-rs-comparision]: https://github.com/steadylearner/Chat/tree/master/socket_io

[browserify]: https://github.com/browserify/browserify
[node-emoji]: https://www.npmjs.com/package/node-emoji
[has-emoji]: https://www.npmjs.com/package/has-emoji

# How to start Rust Chat App

You can also find post for this at [Steadylearner Post for this](https://www.steadylearner.com/blog/read/How-to-start-Rust-Chat-App).

[Prerequisite]

1. [How to install Rust]
2. [Websocket API]
3. [ws-rs]
4. [ws-rs-html-example]
5. [Steadylearner Chat]
6. [browserify]
7. [node-emoji]
8. [Thread in Rust](https://doc.rust-lang.org/std/thread/)

---

You will use **Rust** mainly to build socket server to handle messages. So you need to install it following the instruction from [its official website][How to install Rust].

You also need to understand what is [Websocket API] to understand what you need to connect, send, receive and close the websocket for your chat app.

(You may also visit [socket-io] JavaScript framework if you are more familar with JavaScript to understand the topic better.)

We will use ws-rs Rust crate for this post. So I want you to visit [its website][ws-rs] and read its API and documenation first.

The chat app we will build here is just the improved version of [ws-rs-html-example]. Therefore, I want you to visit it and read its source code.

I also prepared the examples to compare [ws-rs] to [socket-io] at [socket-io-and-ws-rs-comparision] GitHub page. You may refer to it if you are already knew API of [socket-io].

Whenever you have doubt about **Rust**, please visit [Rust Documentation] page and seach on your own. It will be better for you to read [Concurrency in Rust] and search other articles if you want to understand why Rust is fast.

(If you were completely new at Rust, but familar with JavaScript, you may read [TypeScript](https://www.typescriptlang.org/docs/index.html) documentation first because the purpose of both langauges is to have strong type system. It will help you to learn **Rust** if you haven't any experience in Rust.)


Table of Contents

1. Setup [Rocket] to serve files
2. ws-rs for socket server
3. HTML and CSS for layout
4. JavaScript for chat app users
5. Conclusion

---


## 1. Setup Rocket to serve files

(You can skip this part if you already downloaded [GitHub Respoitory][Steadylearner Chat] and understand how to structure Rust application.)

If you have tested code at [ws-rs-html-example] before you read on, you should have found that a single Rust(.rs) file does everything to render html, serve file, and exchange messages.

It may be a decent single file example but it will be difficult to build more complicated app later. Therefore, we will pass its role to serve files to [Rocket] web framework.

(I prefer [it][Rocket] for it has many examples but you may use whatever framework.)

We can start this from our **Cargo.toml** file like the code snippet below or use **$cargo add ws-rs rocket**.(You can visit [cargo-edit] page for this command)

```toml
[package]
name = "chat_app_with_rust_by_steadylearner"
version = "0.1.0"
edition = "2018"

[dependencies]
ws = "0.8.1"
rocket = "0.4.1"
```

Nothing complicated for our Cargo.toml, What we need is to notify **Rust** that we need [Rocket] to serve our files such as **HTML** file to work as socket client, CSS, JavaScript, images etc at webpage.

Then, [ws-rs] crate to work as socket server to handle chat messages.

(If you are not familar with [.toml files](https://doc.rust-lang.org/cargo/reference/manifest.html), you may think it is similar to **package.json** in JavaScript)

Then we will build **main.rs** file first to be starting point for our Rust app.(Please, refer to the repository I gave before whenever you have doubts.)

```rust
#![feature(
proc_macro_hygiene,
decl_macro,
custom_attribute,
rustc_private,
type_ascription
)]
#[macro_use]
extern crate rocket;

extern crate ws;

use std::thread;

mod route;
use crate::route::{get, static_files};

mod chat;
use crate::chat::ws_rs;

fn rocket() -> rocket::Rocket {
let rocket_routes = routes![
static_files::file,
//
get::index,
get::chat,
];

rocket::ignite()
.mount("/", rocket_routes)
}

fn main() {
// 1.
thread::Builder::new()
.name("Thread for Rust Chat with ws-rs".into())
// 2.
.spawn(|| {
ws_rs::websocket();
})
.unwrap();

rocket().launch();
}
```

The major part of the [Rocket] relevant code will be the boilerplate if you want to write minimum webpage with it. So you don't have to understand all at once.

But the important points here are

1. We use [thread](https://doc.rust-lang.org/std/thread/) to separate chat server and not to affect(break) the main server that manages your website(It is Rocket here)

2. You can assign **.stack_size(83886 * 1024)** here if you want to be serious with your chat app later.(You can search "How many resources chat app need" at your search engine.)

The part of the code snippet above includes

```rust
mod route;
use crate::route::{get, static_files};

mod chat;
use crate::chat::ws_rs;

fn rocket() -> rocket::Rocket {
let rocket_routes = routes![
static_files::file,
//
get::index,
get::chat,
];

rocket::ignite()
.mount("/", rocket_routes)
}
```

They are to serve routes and init your app with them.

The important part here will be **static_files** and **ws_rs**.

When you see the source code for static_routes first,

```rust
use std::path::{Path, PathBuf};
use rocket::response::NamedFile;

#[get("/static/")]
pub fn file(file: PathBuf) -> Option {
NamedFile::open(Path::new("static/").join(file)).ok()
}
```

It is just to serve every files in static folder. You can just copy and paste it when you need to serve every files in static folder.

(You can use simplified API in newer version of [Rocket] if you want.)

for **ws_rs** route, important part is

```rust
#[get("/chat")]
pub fn chat() -> io::Result {
NamedFile::open("static/chat/index.html")
}
```

and it is to serve **HTML** files for chat app.

For example, https://www.yourwebsite.com/chat

The file will help users to connect socket and have their separtate data with JavaScript and brwoser API later. We will learn how to do that in the last part of this post.

You can refer to [Steadylearner Chat] and [Rocket] Documentation for more information.


## 2. ws-rs for socket server

In this part, we will learn how to build server socket with Rust.

If you see the code snippet for [ws-rs](https://github.com/steadylearner/Chat/blob/master/ws_rs_with_rocket/src/chat/ws_rs.rs), it will be similar to this and not so different to [ws-rs-html-example] from its official website.

```rust
use ws::{
listen,
CloseCode,
Error,
Handler,
Handshake,
Message,
Request,
Response,
Result,
Sender,
};

use std::cell::Cell;
use std::rc::Rc;

// Server web application handler
struct Server {
out: Sender,
count: Rc>,
}

impl Handler for Server {
// 1.
fn on_request(&mut self, req: &Request) -> Result<(Response)> {
match req.resource() {
"/ws" => {
// 2.
println!("Browser Request from {:?}", req.origin().unwrap().unwrap());
// Uncomment this and find what you can do with them when you develope
// println!("Client found is {:?}", req.client_addr().unwrap());
// println!("{:?} \n", &resp);
let resp = Response::from_request(req);
resp
}

_ => Ok(Response::new(404, "Not Found", b"404 - Not Found".to_vec())),
}
}

fn on_open(&mut self, handshake: Handshake) -> Result<()> {
// 3.
self.count.set(self.count.get() + 1);
let number_of_connection = self.count.get();

if number_of_connection > 5 {
// panic!("There are more user connection than expected.");
}

// 4.
let open_message = format!("{} entered and the number of live connections is {}", &handshake.peer_addr.unwrap(), &number_of_connection);
// println!("{}", &handshake.local_addr.unwrap());

println!("{}", &open_message);
self.out.broadcast(open_message);

Ok(())
}

fn on_message(&mut self, message: Message) -> Result<()> {
let raw_message = message.into_text()?;
println!("The message from the client is {:#?}", &raw_message);

// 5.
let message = if raw_message.contains("!warn") {
let warn_message = "One of the clients sent warning to the server.";
println!("{}", &warn_message);
Message::Text("There was warning from another user.".to_string())
} else {
Message::Text(raw_message)
};

// 6.
// Broadcast to all connections
self.out.broadcast(message)
}

fn on_close(&mut self, code: CloseCode, reason: &str) {
match code {
CloseCode::Normal => println!("The client is done with the connection."),
CloseCode::Away => println!("The client is leaving the site."),
CloseCode::Abnormal => {
println!("Closing handshake failed! Unable to obtain closing status from client.")
},
_ => println!("The client encountered an error: {}", reason),
}

// 7.
self.count.set(self.count.get() - 1)
}

fn on_error(&mut self, err: Error) {
println!("The server encountered an error: {:?}", err);
}
}

pub fn websocket() -> () {
println!("Web Socket Server is ready at ws://127.0.0.1:7777/ws");
println!("Server is ready at http://127.0.0.1:7777/");

// Rc is a reference-counted box for sharing the count between handlers
// since each handler needs to own its contents.
// Cell gives us interior mutability so we can increment
// or decrement the count between handlers.

// Listen on an address and call the closure for each connection
let count = Rc::new(Cell::new(0));
listen("127.0.0.1:7777", |out| { Server { out: out, count: count.clone(), } }).unwrap()
}
```

The code snippet is a little bit long but the important parts will be

1. [You need on_request part just once and you don't have to reconnect later](https://blog.stanko.io/do-you-really-need-websockets-343aed40aa9b).

2. Use them to verify what you can do when the first socket connection between server and client happen and read [the documenation for them](https://ws-rs.org/api_docs/ws/struct.Request.html)

3. We need to count how many connections there are because it affects connection quality. You may use the `number_of_connection` variable with conditional statement.(We will write code for that in client side later. You may use your own code.)

4. **This is the most important part.** Even though we use localhost first and not real users, there should be some ways to differenciate the users from one another. So We will use return value of `&handshake.peer_addr.unwrap()` for the purpose and also `number of connection` inside `fn on_open`. (If you open various windows for http://localhost:8000/chat later, You can see that it always return different values in your CLI.)

5. This is where you can do various things with messages from users. You can use database to save messages from users here. You may write experimental code, for example, to send **warning** received from other users to everyone connected to the server socket.(You may test it with **!warn** in socket client later.)

6. `self.out.broadcast(message)` used to send messages to all users. It is the last API used before the messages from the server arrive to clients connected to socket.

7. `self.count.set(self.count.get() - 1)` is used to recalculate the total number of user when some client close the connection.

I hope it helped you to understand this code snippet. You will need to find what are the uses of [Sender](https://doc.rust-lang.org/std/sync/mpsc/struct.Sender.html), [Rc](https://doc.rust-lang.org/std/rc/index.html) and [Cell](https://doc.rust-lang.org/std/cell/index.html) on your own if you want to understand it fully.

It is also important for you to understand that the most of the code used above are relevant to the JavaScript client code we will use later.

(It is important to think that Frontend and Backend code all in one.)


## 3. HTML and CSS for layout

![Rust Chat App](https://www.steadylearner.com/static/images/post/chat/rust_chat.png)

We briefly learnt the Rust serverside code to build our chat app. It is time to build Frotnend part to help users.

If you see the [index.html](https://github.com/steadylearner/Chat/blob/master/ws_rs_with_rocket/static/chat/index.html) file, The important part will be

```html







Rust Chat App



Exit








Send

Clear