Creating a Plugin

One of Humphrey's main strengths is its extensibility through its plugin system. In this section, you'll learn how to write a basic plugin using Rust and load it into the server.

This section requires knowledge of the Rust programming language.

Setting Up the Project

To begin, you'll need to create a new Rust library with the following command:

$ cargo new my_plugin --lib

Then, in the Cargo.toml file, you'll need to specify the humphrey and humphrey_server dependencies, as well as the plugins feature of the latter. You must also specify the crate type as cdylib so it can by dynamically linked into the server. The file should look like this:

[package]
name = "my_plugin"
version = "0.1.0"
edition = "2021"

[dependencies]
humphrey = "*"
humphrey_server = { version = "*", features = ["plugins"] }

[lib]
crate-type = ["cdylib", "rlib"]

Initialising the Plugin

Every Humphrey plugin is a crate which defines a type which implements the Plugin trait. The type must be declared with the declare_plugin! macro. In your lib.rs file, add the following code:

use humphrey_server::declare_plugin;
use humphrey_server::plugins::plugin::Plugin;

#[derive(Default, Debug)]
pub struct MyPlugin;

impl Plugin for MyPlugin {
    fn name(&self) -> &'static str {
        "My Plugin"
    }
}

declare_plugin!(MyPlugin, MyPlugin::default);

The only required method for the trait to be implemented is name, which returns the name of the plugin. The declaration macro takes in the type of the plugin, and a constructor to initialise the plugin, which we've automatically generated by deriving the Default trait.

Intercepting Requests

The on_request method of the plugin trait is passed every request, along with the app's state and the configuration of the route which matched it. It returns an Option<Response>, which is None if the plugin doesn't want to handle the request, or Some(response) if it does.

Let's add some code which will intercept all requests to the /example route, and return a response with a body of "Hello, world!".

// --snip--

use humphrey::http::headers::HeaderType;
use humphrey::http::{Request, Response, StatusCode};
use humphrey_server::config::RouteConfig;
use humphrey_server::AppState;

use std::sync::Arc;

impl Plugin for MyPlugin {
    // --snip--

    fn on_request(
        &self,
        request: &mut Request,
        state: Arc<AppState>,
        _: &RouteConfig,
    ) -> Option<Response> {
        state.logger.info(&format!(
            "Example plugin read a request from {}",
            request.address
        ));

        // If the requested resource is "/example" then override the response
        if &request.uri == "/example" {
            state.logger.info("Example plugin overrode a response");

            return Some(
                Response::empty(StatusCode::OK)
                    .with_bytes("Hello, world!")
                    .with_header(HeaderType::ContentType, "text/plain"),
            );
        }

        None
    }
}

This code simply logs that the plugin intercepted each request, and if the URI is equal to /example, it overrides the response with a the message "Hello, world!".

Intercepting Responses

Humphrey plugins can also intercept responses, and can modify them before they are sent to the client. The on_response method takes in a mutable reference to the response, which can be modified if necessary. It also takes in the app's state.

Now, we're going to add some code which adds the X-Example-Plugin header to every response with a value of true.

impl Plugin for MyPlugin {
    // --snip--

    fn on_response(&self, response: &mut Response, state: Arc<AppState>) {
        // Insert a header to the response
        response.headers.add("X-Example-Plugin", "true");

        state
            .logger
            .info("Example plugin added the X-Example-Plugin header to a response");
    }
}

Conclusion

As you can see, Humphrey's plugin system allows for complex additions to be made to the Humphrey server. If you want to see a more in-depth example of a plugin, check out the source code for the PHP plugin here.