After using building a tool to populate a Gopher server as an excuse to learn the Go programming language I’ve recently been wanting to try my hand at Rust.
The best way to learn a new programming language is to use it to actually solve a problem, rather than just copying exercises out of a tutorial. So this time I thought I’d try and build my own Gopher server.
Specification
The initial version of the Gopher specification is laid down in RFC1436. It’s pretty simple. The client basically sends a string that represents the path to the document it wants followed by \r\n
. In the case of the root document the client sends just the line ending chars.
The server responds with either the raw content of the document or if the path points to a directory then it sends the content of a file called gophermap
if found in that directory.
The gophermap
file holds a list of links a bit like a index.html
for a HTTP server.
Ben's Place - Gopher
Just a place to make notes about things I've
been playing with
0CV cv.txt
1Blog /blog
1Brad & Will Made a Tech Pod /podcast
The lines that start with 0
are direct links to a file and have the label and then the file name. Where as 1
are links to another directory. The fields are delimited with tabs.
You can also link to files/directories on other servers by including the server and port after the filename/dir path again separated by tabs.
1Blog /blog gopher.hardill.me.uk 70
There is also something called Gopher+ which is an extended version that looks to never have been formally adopted as a standard but both my gopher client and PyGopherd look to support. A copy of the draft is here.
Rust
Similar to things like NodeJS, Rust has a package manager called cargo that can be used to create a new project, running cargo new rust_gopher
will create a Cargo.toml
file that looks a bit like this:
[package]
name = "rust_gopher"
version = "0.1.0"
authors = ["Ben Hardill <hardillb@gmail.com"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
It also create a src
directory with a main.rs
and calls git init
to start a new repository.
The src/main.rs
is pre-populated with “Hello World”
fn main() {
println!("Hello, world!");
}
I’ve replaced the main()
function with one that uses the clap
create to parse some command line arguments.
let matches = App::new("rust_gopher")
.version("0.1.0")
.author("Ben Hardill")
.about("Basic Gopher Server")
.arg(Arg::with_name("hostname")
.short("h")
.long("hostname")
.takes_value(true)
.help("The hostname of this server"))
.arg(Arg::with_name("port")
.short("p")
.long("port")
.takes_value(true)
.help("Port number to listen on"))
.arg(Arg::with_name("dir")
.short("d")
.long("dir")
.takes_value(true)
.help("path to gopher content"))
.get_matches();
let hostname = matches.value_of("hostname").unwrap_or("localhost");
let port :i16 = matches.value_of("port").unwrap_or("70").parse().unwrap();
let dir = matches.value_of("dir").unwrap_or("root");
Which gives really nicely formatted help output:
$ rust_gopher --help
rust_gopher 0.1.0
Ben Hardill
Basic Gopher Server
USAGE:
rust_gopher [OPTIONS]
FLAGS:
--help Prints help information
-V, --version Prints version information
OPTIONS:
-d, --dir <dir> path to gopher content
-h, --hostname <hostname> The hostname of this server
-p, --port <port> Port number to listen on
Reading the gophermap
To add the required bits to a basic gophermap
file into what actually gets sent, the files get parsed into the following structure.
struct Gophermap {
row_type: char,
label: String,
path: String,
server: String,
port: i16
}
fn read_gophermap(path: &Path, config: &Config) -> Vec<Gophermap>{
let mut entries: Vec<Gophermap> = Vec::new();
let file = File::open(path).unwrap();
let reader = BufReader::new(file);
for line in reader.lines() {
let mut l = line.unwrap();
...
let entry = Gophermap {
row_type: t,
label: label,
path: p.to_string(),
server: s.to_string(),
port: port
};
entries.push(entry);
}
entries;
}
What’s next?
It still needs a bunch of work, mainly around adding a bunch of error handling. I’ll probably keep poking at it over the holidays.
As usual the code is up on github here.