#fn #serverless #faas #http-request

fdk

Experimental Function Development Kit for the Fn Project serverless platform

3 releases

Uses old Rust 2015

0.1.2 Oct 23, 2017
0.1.1 Oct 20, 2017
0.1.0 Oct 18, 2017

#2 in #faas

Apache-2.0

55KB
846 lines

FDK: Fn Function Development Kit

This crate implements an experimental Function Development Kit for the Fn Project serverless platform.

The API provided hides the implementation details of the Fn platform contract and allows a user to focus on the code and easily implement function-as-a-service programs.

Usage

The Fn platform offers a command line tool to initialize, build and deploy function projects. Follow the fn tool quickstart to learn the basics of the Fn platform. Then start a Rust function project with:

fn init --runtime=rust <other options to fn command>

The initializer will actually use cargo and generate a cargo binary project for the function. It is then possible to specify a dependency as usual.

[dependencies]
fdk = "0.1"

Examples

Stateless function with input

This is a simple function which greets the name provided as input.

extern crate fdk;
use std::process;

fn main() {
    let exit_code = fdk::Function::new(fdk::STATELESS)
    .run(|_, i: String| {
        Ok(format!("Hello, {}!\n",
            if i.is_empty() { "world".to_string() } else { i }))
    });
    process::exit(exit_code);
}

Function with a full testable implementation

This function takes advantage of features of the FDK such as configuration and state management, error handling, and the testbench which provides a wrapper to test the function code as if it was running on the Fn platform.

extern crate fdk;
use std::process;

struct MyState {
    greeting: String
}
impl MyState {
    pub fn greeting(&self) -> &str { &self.greeting }
}

fn init(context: &fdk::RuntimeContext) -> Result<MyState, fdk::FunctionError> {
    match context.config().get("GREETING") {
        Some(s) => Ok(MyState {
            greeting: s.clone()
        }),
        None => Err(fdk::FunctionError::initialization(
            "Missing greeting in configuration\n",
        )),
    }
}

fn code(state: &mut MyState, i: String) -> Result<String, fdk::FunctionError> {
    if !i.is_empty() {
        Ok(format!("{}, {}!\n", state.greeting(), i).into())
    } else {
        Err(fdk::FunctionError::invalid_input(
            "Requires a non-empty UTF-8 string\n",
        ))
    }
}

fn main() {
    let exit_code = fdk::Function::new(init).run(code);
    process::exit(exit_code);
}

#[cfg(test)]
mod tests {
    use fdk;

    use init;
    use code;

    #[test]
    fn test_normal_functionality() {
        let mut testbench =
            fdk::FunctionTestbench::new(init).with_config("GREETING", "Salutations");
        let exit_code = testbench.enqueue_simple("Blah").run(code);
        assert_eq!(exit_code, 0);
        let mut responses = testbench.drain_responses();
        assert_eq!(responses.len(), 1);
        let rb = fdk::body_as_bytes(responses.pop().unwrap().body()).unwrap();
        assert_eq!(String::from_utf8_lossy(&rb), "Salutations, Blah!\n");
    }
}

Function handling http requests and responses directly

While input and output coercions can be performed so that your function can just handle your own types, it is sometimes useful to manipulate requests and responses directly.

The FDK uses the hyper::Request and hyper::Response types from the well-known hyper crate to this effect, and your function can therefore receive a hyper::Request as input and produce a hyper::Response as output.

Note that this allows you to set custom headers and status code on the response directly rather than relying on the helper implementations in the FDK which associate errors with certain http statuses.

extern crate hyper;
extern crate fdk;
use std::process;

struct MyState {
    greeting: String
}
impl MyState {
    pub fn greeting(&self) -> &str { &self.greeting }
}

fn init(context: &fdk::RuntimeContext) -> Result<MyState, fdk::FunctionError> {
    match context.config().get("GREETING") {
        Some(s) => Ok(MyState {
            greeting: s.clone()
        }),
        None => Err(fdk::FunctionError::initialization(
            "Missing greeting in configuration\n",
        )),
    }
}

fn main() {
    let exit_code = fdk::Function::new(init)
    .run(|state, req: hyper::Request| {
        // Since we have raw access to the request we can inspect the
        // headers and extract some data.
        let host = match req.headers().get::<hyper::header::Host>() {
            Some(h) => h.hostname().to_string(),
            None => "NO HOST".to_string(),
        };
        let i = match fdk::body_as_bytes(req.body()) {
            Ok(b) => {
                match String::from_utf8(b) {
                    Ok(s) => s,
                    Err(_) => String::new()
                }
            }
            Err(e) => {
                return Err(e);
            }
        };
        if i.is_empty() {
            // We can produce an "error response" of our own instead of an
            // Err value which would be converted to a 400 response with no
            // headers.
            let message = "Requires a non-empty UTF-8 string!";
            let message_length = message.as_bytes().len() as u64;
            Ok(
                hyper::Response::new()
                    .with_status(hyper::StatusCode::BadRequest)
                    .with_header(hyper::header::Host::new(host.clone(), None))
                    .with_header(hyper::header::ContentLength(message_length))
                    .with_body(message)
            )
        } else {
            let message = format!("{}, {}!\n", state.greeting(), i);
            let message_length = message.as_bytes().len() as u64;
            Ok(
                hyper::Response::new()
                    .with_status(hyper::StatusCode::Ok)
                    .with_header(hyper::header::Host::new(host.clone(), None))
                    .with_header(hyper::header::ContentLength(message_length))
                    .with_body(message)
            )
        }
    });
    process::exit(exit_code);
}

Dependencies

~10MB
~179K SLoC