Customization with WASI

Compiled WASM modules, specifically compiled with WASI, can be used to extend proxysaur. Currently, bindings exist only for Rust. The API exposed by these bindings is not stable and subject to change.

A good reference point is the http-forward-proxy, which is the proxy that is documented for intercepting and rewriting requests via the YAML configuration file.

Writing a Rust proxysaur module

Cargo.toml

Add the following dependency:

proxysaur-bindings = { git = "https://github.com/pmalmgren/proxysaur" }

Configuration

The configuration parameter in the toml file called proxy_configuration_path will be read in and the raw bytes are accessible with the config bindings:

use proxysaur_bindings::config;

fn main() {
    let config_data: Vec<u8> = proxysaur_config::get_config_data();
    ...
}

If the data is invalid, you can use the set_invalid_data function which allows you to set a String error message:

use proxysaur_bindings::config;

fn main() {
    let config_data: Vec<u8> = proxysaur_config::get_config_data();
    let config: Config = serde::from_bytes(&config_data).map_err(|err| {
        let msg = format!("Error serializing data: {}", err);
        config::set_invalid_data(msg);
        err
    }).unwrap();
}

HTTP pre-requests

Before proxysaur proxies a request, it checks to see if it should intercept the request or just forward the data between the client and the server. It does this by running the module, and checking to see what the module set the proxy mode to. A value of Intercept means that it will proceed to call methods to manipulate the request and the response.

For example, this WASI module will proxy requests only to localhost:9234:

use proxysaur_bindings::http::pre_request::{self, HttpPreRequest, ProxyMode};

fn main() {
    let request: HttpPreRequest = pre_request::http_request_get();

    if request.authority == "localhost:9234" {
        pre_request::http_set_proxy_mode(ProxyMode::Intercept);
    }
}

HTTP Requests

After proxysaur received a request from the client, it runs a WASI module and checks the data set by the module. In the request module, you can set anything you want on the request. For example, this one changes the request body to some random data for test.com:

use proxysaur_bindings::http::request::{self, HttpRequest};

fn main() {
    let request: HttpRequest = request::http_request_get().expect("should get the request");

    if request.host == "test.com" {
        request::http_request_set_body("haha!".as_bytes()).expect("should set the body");
    }
}

HTTP Responses

After proxysaur receives a response from the server, it runs a WASI module to check the response data set by the module.

use proxysaur_bindings::http::response::{self, HttpResponse};

fn main() {
    let response: HttpResponse = response::http_response_get().expect("should get the response");

    if response.status == 200 {
        response::http_response_set_status(500).expect("should set the status");
        response::http_response_set_body("broken!".as_bytes()).expect("should set the body");
    }
}