Skip to content

Rust HTTP Server Libary built Around Hyper.rs with Macros Similar to Actix

Notifications You must be signed in to change notification settings

GalactechsLLC/portfu

Repository files navigation

CI

PortFu

An HTTP Library built to simplify Web App Development.

  • Macros for All standard HTTP Methods GET, POST, DELETE ect...
  • Data Extractors to easily access and process request data
  • Websocket Macro
  • Background Task and Interval Macros

Macro Examples

GET Request using a Path Variable

#[get("/echo/{path_variable}")]
pub async fn example_fn(
    path_variable: Path,
) -> Result<String, Error> {
    Ok(path_variable.inner())
}

StaticFiles from a path (Built into the binary at compile time)

#[static_files("relative/path/to/files/")]
pub struct StaticFiles;
//By default, / is not mapped to index.html, to fix this add the below
//to use a file other than index.html take the path and apply the below function
//path.replace(['/','.',')','(','-',' ','+'], "_").replace("__", "_");
//ie. relative/path/to/files/some_sub_dir/index.json becomes STATIC_FILE_some_sub_dir_index_json
#[get("/")]
pub async fn index() -> Result<Vec<u8>, Error>{
    Ok(STATIC_FILE_index_html.to_vec())
}

POST Request with Shared State

#[post("/counter")]
pub async fn example_fn(
    get_counter: State<AtomicUsize>,
    path_variable: Path,
) -> Result<String, Error> {
    let val = get_counter
        .inner()
        .fetch_add(1, Ordering::Relaxed) + 1;
    Ok(val.to_string())
}

Websockets are bound to a path but can share peers if both Websockets are created with the same peers object, see main function below

#[websocket("/echo_websocket")]
pub async fn example_websocket(websocket: WebSocket) -> Result<(), Error> {
    while let Ok(msg) = websocket.next_message().await {
        match msg {
            Some(v) => {
                websocket.send(v).await?;
            }
            None => {
                tokio::time::sleep(Duration::from_millis(10)).await;
            }
        }
    }
    Ok(())
}

Interval running in the background

#[interval(500u64)] //Will run every 500ms
pub async fn example_interval(state: State<AtomicUsize>) -> Result<(), Error> {
    state.inner().fetch_add(1, Ordering::Relaxed);
    info!("Tick");
    Ok(())
}

Task that will run when server is started

#[task("")]
pub async fn example_task(state: State<AtomicUsize>) -> Result<(), Error> {
    loop {
        state.inner().fetch_add(1, Ordering::Relaxed);
        tokio::time::sleep(Duration::from_secs(1)).await;
    }
}

Custom services can be created with a struct that implements ServiceRegister + Into<Service> When a request is sent to the server it will search for the first registered Service where the below are true:

  • The Path string of the service matches the requests URI path
  • The Filters attached to the service all return FilterResult::Allow

ServiceGroups can even have sub_groups to have even finer control over services

Here is the main function that would be used for all the services above, including some example filters and wrappers.

#[tokio::main]
async fn main() -> Result<(), Error> {
    SimpleLogger::default(); //Init your logger of choice
    let server = ServerBuilder::default() //Start building the Server
        .shared_state(RwLock::new(AtomicUsize::new(0))) //Shared State Data is auto wrapped in an Arc
        .shared_state("This value gets Overridden") //Only one version of a type can exist in the Shared data, to get around this use a wrapper struct/enum
        .shared_state("By this value")
        //Filters applied at the server level apply to all services regardless of when they were registered
        .filter(any("Method Filters".to_string(), &[GET.clone(), POST.clone(), PUT.clone(), DELETE.clone()]))
        .register(StaticFiles) //Register Each Service directly with the server
        .register( //Sub Groups are also services
            ServiceGroup::default() //Start the Subgroup
               //Filters at the ServiceGroup level apply to service defined below them only, this is the same with any wrappers
               .service(example_get) //This service is defined above the filter and will not have the filter applied
               .filter(has_header(HeaderName::from_static("content-length")))
               .service(example_post)//This service is defined below the filter and will have the filter applied
               .wrap(Arc::new(SessionWrapper::default())) //The session wrapper will create a session using cookies for each connection
               //All Requests below this will only work for connections that have a session and send the cookie with requests
               .sub_group( //Add another group to this group
                   ServiceGroup::default()
                       .service(example_websocket { //Peers Need to be defined for a websocket, to share peers pass the same map to multiple websockets
                           peers: Default::default(),
                       })
               ),
        )
        .task(example_task) //Add a background task to start when the server is started
        .task(example_interval) //Intervals are also tasks
        .build();
    info!("{server:#?}"); //Servers impl debug so you can see the structure
    server.run().await //Run the server and wait for a termination signal
}

About

Rust HTTP Server Libary built Around Hyper.rs with Macros Similar to Actix

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages