#Http request load balancer in rust

25 messages · Page 1 of 1 (latest)

inland pine
#

Hello, I am .net developer, who decided to learn rust for a university project.
I need to write a gateway with a load balancer in rust for my other .net services

I am very new to rust and I feel like this task is pretty hard for me to make it in time.

I decided to keep things as simple as possible to try to understand how to do it with a default .net application that just returns the weather

Now I have instantiated 2 containers of the same service on different ports, I keep their addresses in a config.toml file

Now I want my load balancer to wrap my http handler and enrich it by passing to it the next url( Iterators seem like a good choice, but I need to recreate them every time they are consumed)

So the load balancer returns a closure with the correct parameter for the handler

I want a round robin distribution

#

Here is some code:

#[path="handlers/my_handlers.rs"]
mod my_handlers;
#[path="load_balancers/loadbalancer.rs"]
mod loadbalancer;
use std::fs::File;
use std::io::Read;
use axum::{Router, routing::get};
use my_handlers::weatherforecast_handler;
use serde_derive::Deserialize;
use std::net::SocketAddr;
#[tokio::main]
async fn main() {
    let mut config_file = File::open("config.toml").expect("Unable to open config file");
    let mut config_toml = String::new();
    config_file.read_to_string(&mut config_toml).expect("Unable to read config file");

    let config:AppConfig = toml::from_str(&config_toml).expect("Failed to parse TOML");
    let iterator = config.addresses.iter();
    for address in config.addresses.iter() {
        println!("Service Address: {}", address);
    }
    let app = Router::new().route("/", get(|| weatherforecast_handler("http://localhost:7071/weatherforecast")));

    let addr = SocketAddr::from(([127, 0, 0, 1], 8080));

    axum::Server::bind(&addr)
        .serve(app.into_make_service())
        .await
        .unwrap();
}

#[derive(Debug, Deserialize)]
struct AppConfig {
    addresses: Vec<String>,
}

use axum::http::StatusCode;
use reqwest;
pub async fn weatherforecast_handler(url:&str) -> (StatusCode,String) {
    match reqwest::get(url).await {
        Ok(res) => (
            StatusCode::OK,
            res.text().await.unwrap(),
        ),
        Err(err) => (
            StatusCode::INTERNAL_SERVER_ERROR,
            format!("Server failed with {}", err),
        )
    }
}
use std::{pin::Pin, future::Future};

use reqwest::StatusCode;
pub fn balance<'a, F>(handler: F) -> impl Fn(&'a str) -> Pin<Box<dyn Future<Output = (StatusCode,String)>>>
where
    F: Fn(&'a str) -> Box<dyn std::future::Future<Output = (StatusCode, String)>>,
{
    move |url:&'a str| Box::pin(handler(url))
}
#

Any feedback about the code and possible ways of doing what I am trying to do is appreciated 🙂

vapid flume
inland pine
#

sure,looks better, i presume you're talking about replacing this

 for address in config.addresses.iter() {
        println!("Service Address: {}", address);
    }

Glad to know this is possible,looks a lot like c#
but my main problem is how to make the program pass a different url each time "/" this route is called
so as an example
1st call 1st url
2nd call 2nd url
since there are only 2 they will wrap around
so on the third call the 1st url will be used and so on
but it should work the same for any number of urls

vapid flume
#

And as for your problem

#

Why don't you try Rocket?

#

I find it more ergonomic than Axum

inland pine
#

only heard of hyper and axum

will give it a look thanks)

vapid flume
#

Let me know what you think when you have a look

inland pine
# vapid flume Also, this should help https://github.com/mhallin/loadbalancer-rs

It seems like it needs a lot less boiler plate than axum
but I don't see the benefit in rewriting my server atm, since
if i solve my load balancing issue
i will just need to add more routes
the entire job of this project is just to basically route requests to other .net microservices
and it doesn't need to be production worthy since it's just a university project and we were told to keep things simple)

I am glad I found out about it though, if I get better at rust soon, will attempt to write a chess engine and use rocket

vapid flume
#

Rocket is a lot like Spring Boot

#

In Java

inland pine
#

Haven't worked with it since I am used to .net not Java
but btw
is it common in rust to go for a more functional style and return handlers
like what I am trying to achieve?
or usually for servers a more oop style is used?

so like in my case here

use std::{pin::Pin, future::Future};

use reqwest::StatusCode;
pub fn balance<'a, F>(handler: F) -> impl Fn(&'a str) -> Pin<Box<dyn Future<Output = (StatusCode,String)>>>
where
    F: Fn(&'a str) -> Box<dyn std::future::Future<Output = (StatusCode, String)>>,
{
    move |url:&'a str| Box::pin(handler(url))
}

it doesn't even compile btw))
but what I am trying to achieve is basically pass it a handler
and make the function decide which url to use
and return a closure that then will be called by the get method

let app = Router::new().route("/", get(|| balance(weatherforecast_handler)));

something like this

if i was writing this in c#

i would encapsulate this logic in a class and save the list of possible urls in a readonly field
make a variable for tracking how many times the handler was called to determine which url to use
and add a thread lock for incrementing this value and reading it

vapid flume
#

Imperative is mostly used for very low level stuff

#

Like OS development

#

And even then you can still use functions

#

Also why are you implementing a future like this

inland pine
#

Am not anymore)
But would be great to learn more about what was wrong)

vapid flume
#

async the keyword is made for that