#web-server #flask #server #http #server-framework #request-response

orangutan

Flask-like super simple and lightweight web server framework for rust

5 releases

0.1.4 Jul 7, 2024
0.1.3 Jul 3, 2024
0.1.2 Jul 3, 2024
0.1.1 Jul 3, 2024
0.1.0 Jul 3, 2024

#1169 in HTTP server

MIT license

45KB
857 lines

orangutan

orangutan is a lightweight and SUPER simple rust web server library inspired by Python's Flask.

Installation (cargo)

Unfortunately, you must add more than one crate to use orangutan because of the macro. This will be fixed in the future but for now I am sorry for the inconvenience.

In the Cargo.toml add:

[dependencies]
orangutan = "0.1.0"
lib_shared = "0.1.0"
ctor = "0.2.8"

Usage

This is what a minimal orangutan application looks like:

use orangutan::*;

// Use the 'route'-macro to define the path, method(s) and a handler
#[route(path="/hello", method="[POST, GET]")] 
fn hello_handler(request: &Request) -> Response {

    // Handler has to return a Response           
    let mut res = Response::new();

    // Use the 'insert'-method to insert text/json into the Response     
    res.insert("Hello!"); 

    res 

}

fn main() {    
    // Create an new Orangutan
    let mut app = Orangutan::new("127.0.0.1:8080"); 

    // This automatically makes the Orangutan run with the handler and routes assigned to it.      
    app.run();

    // Now you have a web server listening on http://127.0.0.1:8080/hello 
    // Simple, right
} 

Output of the program:

* orangutan being served!
    *  orangutan running on http://127.0.0.1:8080/hello (Press CTRL+C to quit)

I recommend using software like ngrok to make the web server public, as there is no way of doing it with orangutan.

Examples

orangutan is quite a powerful and useful tool. Here are some of the things that orangutan can do!

Variables in path

use orangutan::*;

// Same as defining a normal route but with the path containing the type and variable name.
#[route(path="/<str:username>", method="[GET, POST]")]
fn username_handler(request: &Request) -> Response {
    // This handler is responsible for every request in the path: "/something"
    // The name of the variable (username) is defined after the variables type 

    let mut res = Response::new();          

    // We use the get_var() method to get the value of the variable in the request
    let username: String = request.get_var("username");

    // We can do whatever we want with the username
    res.insert(format!("Hello, {}", username));    

    res

}

// Same thing with the username path but now the value of the variable is i32
#[route(path="/<int:number>", method="[POST, GET]")]
fn number_handler(request: &Request) -> Response {
    // This handler is responsible for every request in the path: "/something"
    // Same as username handler except the value of the variable is i32.  

    let mut res = Response::new();          

    let number: i32 = request.get_var("number");

    res.insert(format!("{} * 2 = {}", number, number*2));    

    res

}

fn main() {    
    let mut app = Orangutan::new("127.0.0.1:8080");   

    app.run();     
}

JSON Requests

In this example I will show you how to handle Requests that contain JSON data

use orangutan::*;

#[route(path="/order", method="[GET, POST]")]
fn order_handler(request: &Request) -> Response {

    let mut res = Response::new();          

    // .json() returns an Option<Value>, where Some represents the JSON Value and None means that there is no JSON Value in the request
    let order_data = request.json();

    // Let us use match to see if the request contains JSON Value or not
    match order_data {
        // Great. The reqeust contains JSON Value
        Some(data) => { 
            let order: Option<&Value> = data.get("order");

            println!("The order: {}", order.unwrap());
        }
        None => { println!("This means that the request does not contain any JSON data");}
    }    

    res
}

fn main() {    
    let mut a = Orangutan::new("127.0.0.1:8080");   

    a.run();     
}

A great way to test how this works is by utilizing tools like curl on Linux. Lets see how our program behaves.

curl -i -H "Content-Type: application/json" -X POST -d '{"order":"Computer", "OS": "Linux"}' http://127.0.0.1:8080/order

Lets see how out program responses to this:

The output of the program:

The order: "Computer"

What if the request does not contain any JSON Values?

Lets use this command:

curl -X GET -H "Content-Type: text/plain" -d "This is not Json Value" http://127.0.0.1:8080/order

And again the output is this:

This means that the request does not contain any JSON data

If the request does contain JSON Value but the Value does not contain the "order" field then our program will 'panic' BUT that does not crash the server thankfully.

Aborting requests

Sometimes you need to simply return an error. This is how:

use orangutan::*;

#[route(path="/only_vip", method="[GET, POST]")]
fn user_satus_handler(request: &Request) -> Response {

    let mut res = Response::new();          

    let user_data = request.json();
    
    match user_data {        
        Some(data) => { 
            let user_status: Option<&Value> = data.get("user_status");

            if user_status.unwrap() != "VIP" {
                // Use the abort method to create an error for Response. Only 403, 404 and 500 are valid errors for now.
                res.abort(request, 403);
            } else {
                res.insert("Welcome to the club!");
            }            
        }

        None => { res.abort(request, 404) }
    }    

    res
}

Writing Responses

There are many ways of making Responses. Here are some different ways of doing the same thing.

use orangutan::*;

#[route(path="/", method="[GET, POST]")]
fn handler(request: &Request) -> Response {

    let mut res = Response::new();          

    // Here are 3 methods for doing the same thing
    res.set_content_type(ContentType::TextHtml);
    res.set_status(200);
    res.append("Hello!");

    res.insert("<p>Hello!</p>");

    res.insert("Hello!");
    res.set_content_type(ContentType::TextHtml);

    res
}

Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

License

MIT

Dependencies

~7–10MB
~178K SLoC