Skip to content

Commit acb3659

Browse files
committed
feat: Rust Server Implementation
1 parent 459ee5e commit acb3659

13 files changed

+258
-2
lines changed

Cargo.toml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[workspace]
2+
members = ["demo/server/rust-example"]

demo/server/README.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Server Demos
2+
3+
These servers are built to reflect the various languages to setup an edge service.
4+
5+
An Edge service's idea is to simply and solely connect a player's connection to a game server via kafka.

demo/server/rust-example/.gitignore

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# BEGIN: Rust
2+
# Generated by Cargo
3+
# https://git.io/JvCJW
4+
5+
# Compiled files
6+
/target/
7+
8+
# Dependency files
9+
Cargo.lock
10+
11+
# Generated files
12+
Cargo.toml.orig
13+
Cargo.toml.bkp
14+
15+
# Output files
16+
*.rs.bk
17+
18+
# END: Rust

demo/server/rust-example/Cargo.toml

+14
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,19 @@
22
name = "rust-example"
33
version = "0.1.0"
44
edition = "2021"
5+
publish = false
56

67
[dependencies]
8+
axum = { version = "0.7", features = ["ws", "tokio"] }
9+
# axum-server = { version = "0.7", features = ["tls-rustls"] }
10+
axum-extra = { version = "0.9.3", features = ["typed-header"] }
11+
futures = "0.3"
12+
futures-util = { version = "0.3", default-features = false, features = ["sink", "std"] }
13+
headers = "0.4"
14+
tokio = { version = "1.4", features = ["full"] }
15+
tokio-tungstenite = "0.23"
16+
tower = { version = "0.5", features = ["util"] }
17+
tower-http = { version = "0.5", features = ["fs", "trace"] }
18+
tracing = "0.1"
19+
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
20+
rdkafka = { version = "0.36.2", features = ["cmake-build"] }
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
pub struct Client
2+
{
3+
id: i64,
4+
}
5+
6+
impl Client
7+
{
8+
pub fn new(id: i64) -> Client
9+
{
10+
Client
11+
{
12+
id,
13+
}
14+
}
15+
16+
pub fn get_id(&self) -> i64
17+
{
18+
self.id
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub trait LoadBalancer {
2+
fn next(&mut self) -> &mut super::server::Server;
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
use crate::api::{loadbalancer::LoadBalancer, server::Server};
2+
3+
pub struct LeastConnections {
4+
servers: Vec<Server>
5+
}
6+
7+
impl LoadBalancer for LeastConnections {
8+
fn next(&mut self) -> &mut Server {
9+
let mut min = usize::MAX;
10+
let mut index = 0;
11+
for (i, server) in self.servers.iter().enumerate() {
12+
if server.get_clients().len() < min {
13+
min = server.get_clients().len();
14+
index = i;
15+
}
16+
}
17+
&mut self.servers[index]
18+
}
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Least Connection Rounded Robin Load Balancer
2+
// This load balancer selects the server with the least number of active connections.
3+
// This is done by iterating through the servers, finding the average number of clients and round robin selecting the servers below the average.
4+
5+
use crate::api::{loadbalancer::LoadBalancer, server::Server};
6+
7+
pub struct LeastConnectionsRoundRobin {
8+
servers: Vec<Server>,
9+
current: i32,
10+
}
11+
12+
impl LoadBalancer for LeastConnectionsRoundRobin {
13+
fn next(&mut self) -> &mut Server {
14+
let mut average: i32 = 0;
15+
{
16+
let mut total_clients: i32 = 0;
17+
for server in self.servers.iter() {
18+
let clients = server.get_clients().len() as i32;
19+
total_clients += clients;
20+
average += clients;
21+
}
22+
23+
average /= total_clients;
24+
}
25+
26+
let mut index = 0;
27+
for _ in 0..self.servers.len() {
28+
self.current = (self.current + 1) % self.servers.len() as i32;
29+
let clients = self.servers[self.current as usize].get_clients().len() as i32;
30+
if clients < average {
31+
index = self.current;
32+
break;
33+
}
34+
}
35+
36+
&mut self.servers[index as usize]
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
pub mod roundrobin;
2+
pub mod leastconnections;
3+
pub mod leastconnectionsroundrobin;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use crate::api::loadbalancer::LoadBalancer;
2+
3+
pub struct RoundRobin {
4+
servers: Vec<super::super::server::Server>,
5+
current: i32,
6+
}
7+
8+
impl LoadBalancer for RoundRobin {
9+
fn next(&mut self) -> &mut crate::api::server::Server {
10+
self.current = (self.current + 1) % self.servers.len() as i32;
11+
&mut self.servers[self.current as usize]
12+
}
13+
}
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
pub mod loadbalancer;
2+
pub mod loadbalancers;
3+
pub mod server;
4+
pub mod client;
+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
pub struct Server
2+
{
3+
id: i64,
4+
clients: Vec<super::client::Client>,
5+
}
6+
7+
impl Server
8+
{
9+
pub fn new(id: i64) -> Server
10+
{
11+
Server
12+
{
13+
id,
14+
clients: Vec::new(),
15+
}
16+
}
17+
18+
pub fn get_id(&self) -> i64
19+
{
20+
self.id
21+
}
22+
23+
pub fn add_client(&mut self, client: super::client::Client)
24+
{
25+
self.clients.push(client);
26+
}
27+
28+
pub fn get_clients(&self) -> &Vec<super::client::Client>
29+
{
30+
&self.clients
31+
}
32+
pub fn remove_client(&mut self, client_id: i64)
33+
{
34+
self.clients.retain(|client| client.get_id() != client_id);
35+
}
36+
37+
pub fn get_client(&self, client_id: i64) -> Option<&super::client::Client>
38+
{
39+
self.clients.iter().find(|client| client.get_id() == client_id)
40+
}
41+
}

demo/server/rust-example/src/main.rs

+78-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,79 @@
1-
fn main() {
2-
println!("Hello, world!");
1+
//! Run with
2+
//!
3+
//! ```not_rust
4+
//! cargo run -p rust-example
5+
//! ```
6+
7+
use std::net::SocketAddr;
8+
9+
use axum::{
10+
extract::{connect_info::ConnectInfo, ws::WebSocketUpgrade},
11+
response::{Html, IntoResponse},
12+
routing::get,
13+
Router
14+
};
15+
use axum_extra::TypedHeader;
16+
use headers::UserAgent;
17+
use tower_http::trace::TraceLayer;
18+
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
19+
20+
mod api;
21+
22+
#[tokio::main]
23+
async fn main() {
24+
tracing_subscriber::registry()
25+
.with(
26+
tracing_subscriber::EnvFilter::try_from_default_env()
27+
.unwrap_or_else(|_| format!("{}=debug,tower_http=debug,axum::rejection=trace", env!("CARGO_CRATE_NAME")).into()),
28+
)
29+
.with(tracing_subscriber::fmt::layer())
30+
.init();
31+
32+
tracing::info!("Starting server...");
33+
34+
// build our application with a route
35+
let app = Router::new()
36+
.route("/", get(handler))
37+
.route("/ws", get(ws_handler))
38+
.layer(TraceLayer::new_for_http());
39+
40+
// run it
41+
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000")
42+
.await
43+
.unwrap();
44+
45+
tracing::info!("listening on {} - http://localhost:{}/", listener.local_addr().unwrap(), listener.local_addr().unwrap().port());
46+
axum::serve(
47+
listener,
48+
app.into_make_service_with_connect_info::<SocketAddr>()
49+
).await.unwrap();
350
}
51+
52+
async fn ws_handler(
53+
ws: WebSocketUpgrade,
54+
user_agent: Option<TypedHeader<UserAgent>>,
55+
ConnectInfo(addr): ConnectInfo<SocketAddr>
56+
) -> impl IntoResponse {
57+
let user_agent = if let Some(TypedHeader(user_agent)) = user_agent {
58+
user_agent.to_string()
59+
} else {
60+
"unknown".to_string()
61+
};
62+
63+
tracing::debug!("WebSocket connection from {} with user agent: {}", addr, user_agent);
64+
65+
ws.on_upgrade(move |socket| async move {
66+
tracing::debug!("WebSocket connection established, from: {}", addr);
67+
// handle the WebSocket connection
68+
69+
// Wait a second.
70+
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
71+
72+
// Close the socket, we don't need it!
73+
socket.close().await.unwrap();
74+
})
75+
}
76+
77+
async fn handler() -> Html<&'static str> {
78+
Html("<h1>Hello, World!</h1>")
79+
}

0 commit comments

Comments
 (0)