Gopher Server in Rust

Surely you won't have enough traffic to justify async
Reading time: about 3 minutes

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.

Gopher, like HTTP, is a network protocol for retrieving information over the internet. One crucial difference is, it hasn’t been commercialized by adtech companies. This is probably because it doesn’t provide many opportunities for tracking, and it doesn’t have a significantly large user base.

But recently it’s been gaining traction; so we should provide a decent landscape for new gophers, full of oxidised servers. Since I started using Gopher more often, it’s beneficial for me if there’s more content out 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.

Before we jump into the details of the protocol, let’s set up a server that responds with “Hello world”. This will provide a skeleton that we can fill with Gopher-related code later.

Handling connections

fn handle_client(stream: TcpStream) -> io::Result<()> {
	write!(stream, "Hello world!")?;
    Ok(())
}

fn main() -> io::Result<()> {
    let listener = TcpListener::bind(format!("0.0.0.0:70")?;

    for stream in listener.incoming() {
        thread::spawn(move || handle_client(stream?));
    }

    Ok(())
}

In this skeleton, pretty much all of our code is going to be in the handle_client function. 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.

Gopher protocol

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

At this point, a traditional server would check the filesystem for the selector and a fancy web framework would go through the registered routes and possibly check some regular expressions. But for our toy server, a simple match statement will be more than enough.

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

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

In the code above, GopherMenu comes from a crate called gophermap. It’s a crate that can parse and generate Gopher menus.

For relative links, we need to know the server address. Let’s put that in a constant and write a small helper.

const HOST: &str = "gkbrk.com";

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

match line {
    "/" | "" => {
        menu.info("Hi!")?;
        menu.info("Try going to page 1")?;
        menu_link("Page One", "/page1")?;
        menu_link("Go to unknown link", "/test")?;
    }
    "/page1" => {
        menu.info("Yay! You found the secret page")?;
        menu_link("Home page", "/")?;
    }
    x => {
        menu.info(&format!("Unknown link: {}", x))?;
        menu_link("Home page", "/")?;
    }
};
menu.end()?;

Now we can link between our pages and start building proper pages. Hopefully this was a good start. If anyone reading this sets up their own Gopherspace, please let me know by leaving a comment or sending me an email.

The following pages link here

Citation

If you find this work useful, please cite it as:
@article{yaltirakli201906gopherserver,
  title   = "Gopher Server in Rust",
  author  = "Yaltirakli, Gokberk",
  journal = "gkbrk.com",
  year    = "2019",
  url     = "https://www.gkbrk.com/2019/06/gopher-server/"
}
Not using BibTeX? Click here for more citation styles.
IEEE Citation
Gokberk Yaltirakli, "Gopher Server in Rust", June, 2019. [Online]. Available: https://www.gkbrk.com/2019/06/gopher-server/. [Accessed Oct. 19, 2024].
APA Style
Yaltirakli, G. (2019, June 02). Gopher Server in Rust. https://www.gkbrk.com/2019/06/gopher-server/
Bluebook Style
Gokberk Yaltirakli, Gopher Server in Rust, GKBRK.COM (Jun. 02, 2019), https://www.gkbrk.com/2019/06/gopher-server/

Comments

Comment by admin
2021-02-17 at 10:51
Spam probability: 1.424%

Hey @zwykle. You can find the repo on my GitHub [1]. [1]: https://github.com/gkbrk/rust-gophermap

Comment by WellMakeItSomehow
2019-07-02 at 20:10
Spam probability: 0.418%

There's not much content, but I'm surprised that people still use Gopher.

Comment by zwykle
2019-06-02 at 19:56
Spam probability: 3.591%

where are repo?

Comment by admin
2019-06-02 at 18:08
Spam probability: 0.076%

Hey! The connection handler needed an Ok(()) at the end. It's fixed now. This is what I get for editing code without compiling I guess. :P

Comment by Guest
2019-06-02 at 17:15
Spam probability: 0.128%

``` error[E0308]: mismatched types --> src/main.rs:6:40 | 6 | fn handle_client(stream: TcpStream) -> io::Result<()> { | ------------- ^^^^^^^^^^^^^^ expected enum `std::result::Result`, found () | | | this function's body doesn't return | = note: expected type `std::result::Result<(), std::io::Error>` found type `()` error: aborting due to previous error ```

© 2024 Gokberk Yaltirakli