Waffle: Gopher Client

Screenshot of Waffle being used

I made a Gopher client called waffle. Basically, a web browser, but for the Gopher Protocol. I started it about 4 years ago now (2020?).

A truly big, complicated beast of a project for me.

I feel like this project touched upon some core areas:

  • Rapid acceleration of my Haskell proficiency
  • Ability to read and implement RFCs (namely RFC 1436)
  • Network programming
  • Concurrency
  • TUI (I used bricks, I think)
  • Large project, requiring careful architecture and separation of concerns

Some other highlights:

  • I got to talk to one of the people who worked on the Gopher Protocol (P. Lindner).
  • Caching system
  • Titlebar thing
  • Search
  • Profiling for efficiency, working with someone who made a tool I used to analyze profiler output
  • Bookmarks
  • Color
  • UTF-8 support, emojis
  • Just using Cabal, basically, not Stack
  • Build/run with nix!

Examples of Waffle

I believe Waffle has an INI file for customization:

Waffle style configuration

Playing a hacking game in Waffle on mozz.us:

Playing a hacking game with Waffle

Waffle playing solitaire:

Waffle playing solitaire

Waffle playing Zork:

Waffle playing Zork

An example of Waffle’s search and refresh abilities (also kind of showing off caching):

Search and refresh in Waffle

The development journey

I found it very satisfying to create a project based around reading an RFC and other research.

Gopher: High Level

The Gopher client, hell, even the Gopher Protocol in general, is very lines-based. As in everything is just composed by a serires of lines, it can feel.

I believe the Gopher Protocol was designed around being simple, easy to hack things together for.

You browse Gopherspace using menus. Menus are basically a bunch of lines that indicate some kind of option you can select to do something, like go to a new directory, download a file, or start a query.

The menus themselves are simple text files that are primarily tab-delimited, looking like this:

1About Gopher	/about	gopher.server.com	70
0What's New!	/whatsnew.txt	gopher.server.com	70
1Gopher Clients	/clients	gopher.server.com	70
1Search Gopher Space	/search	gopher.server.com	70
0Gopher Protocol Specification	/rfc1436.txt	gopher.server.com	70
iThis is an information line		fake	0
1Fun & Games	/fun	gopher.server.com	70
1University Information	/university	gopher.server.com	70
1Libraries around the World	/libraries	gopher.server.com	70
7Search a gopherhole	/search	gopher.example.org	70
.

As you can see these menus are pretty easy to parse. Each line represents something actionable. For example if the first line is selected in the client, we know the following information:

  • Represent the line as About Gopher to the user/client
  • The line intends to bring the user to another gopher menu (I also see menus called “directories”), because the first character on the line is 1, which is the directory (menu) type. Check out RFC1436, 3.8: Item Type Characters for a comprehensive list of such characters.
  • The “selector string” is /about (the path to request)
  • The server to make the request to is gopher.server.com on port 70

An i line is just an information line that’s just there for display. There are various kinds of downloadable files, like type 9 (binary file) and even a specific type for GIF (g).

Maybe the most interesting type is the 7 item type character. It indicates that the entry intends to perform a query if selected, where a query string is sent to the server and a response is (hopefully) given based off that query. Some gopher enthusiasts, myself included, really enjoy abusing this feature for everything from games (I’ve included some screenshots and even video of games in Waffle!) to, say, my gopher forum software.

Clients are also kind of expected to view type 0 in the client, I think. That’s text files.

Interesting bits

  • I found it interesting that the way Gopher Clients, Waffle included, tend to get the title bar text or the like, is from the label of the menu item which lead to it.
  • I decided to implement a loading screen (there’s a whole interesting Progress.hs module), which uses Brick.BChan, and does some fun networking/socket things
  • Caching and history
  • The TUI/BrickApp gets broken up into different states I call a ModeAction, so this could be things like bookmark, help, homepage, menu, open, progress, search, or stuff to do with the menu. The user interface is kind of thought of in relation to a state machine.
  • INI configuration and storage for bookmarks, styling, default associations, homepage
  • Can be piped with tor or whatever, so you can access hidden service Gopherholes

Profiling, benchmarking, optimizing

One of the first major issues I came across was Waffle was extremely slow. I wasn’t sure why, because it felt like I was using brick the way I was supposed to.

I used a terminal-based viewer called profold for GHC .prof files generated by profiling with +RTS -p, made by someone I had the pleasure of talking to (@garmelon). I believe it allowed to to suss out what Waffle was spending the most time doing and why, specifically, scrolling up and down felt so laggy and intensive on very long menus. I can’t remember what I was doing before, but the solution turned out to be to instead use BrickList to display menus, basically.