Rust Gopher Server

I find Gopher really cool. I think it’s a really nice way to organize information into trees and hierarchies, and as we all know programmers can’t resist trees. So recently I took an interest in Gopher and started writing my own server.

And since I started using it more often, it’s beneficial for me if there’s more content there. So I’m writing this blog post to walk you through how to write your own server. We’ll be doing this in Rust.

We could do this asynchronously using a library like Tokio or Runtime, but I’m going to keep it straightforward and use the thread-per-connection model as this is meant to be a short blog post.

The way we’re going to implement this is by writing a connection handler function that will answer each connection in a new thread. If we look at the RFC for Gopher; we can see that after establishing a connection, the client sends the selector for the resource they are interested in. Like /ProgrammingLanguages/Python. Let’s read one line from the socket and look at which selector they want.

let mut line = String::new();
BufReader::new(stream.try_clone()?).read_line(&mut line)?;
let line = line.trim();

For a static file server you would want to look these up on the filesystem, and for a full application you would want a real router that can map parameters; but for our purposes a match statement will be more than enough. Let’s make our bare-bones router and handle some pages.

let mut menu = GopherMenu::with_write(&stream);

match line {
    "/" | "" => {
        menu.info("Amazing home page of amazingness")?;
    }
    x => {
        menu.info("Page not found")?;
    }
};

Let’s make a tiny helper function in order to have relative links.

const HOST: &str = "localhost";
const PORT: u16 = 1234;

let menu_link = |text: &str, selector: &str|
    menu.write_entry(ItemType::Directory, text, selector, HOST, PORT);

match line {
    "/" | "" => {
        menu.info("Hi!")?;
        menu.info("Welcome to my Gopher server!")?;
        menu_link("Tomatoes", "/tomato")?;
        menu.info("Opinion piece about tomatoes")?;
        menu_link("Potatoes", "/potato")?;
        menu.info("Opinion piece about potatoes")?;
        menu_link("Go to unknown link", "/lel")?;
    }
    "/tomato" => {
        menu.info("Tomatoes are not good")?;
        menu_link("Home page", "/")?;
    }
    "/potato" => {
        menu.info("Potatoes are the best")?;
        menu_link("Home page", "/")?;
    }
    x => {
        menu.info(&format!("Unknown link: {}", x))?;
        menu_link("Home page", "/")?;
    }
};
menu.end()?;

Comments BETA





Page built on Sun 19 May 2019 01:15:16 AM BST