Gopher Server
Thursday, October 27, 2016
A few days ago, I noticed a post about building a Gopher Server in Perl 6. I had already implemented a Gopher Client in Factor, and thought it might be fun to show a simple Gopher Server in Factor in around 50 lines of code.
Using the io.servers vocabulary, we will define a new multi-threaded server that has a directory to serve content from and hostname that it can be accessed at:
TUPLE: gopher-server < threaded-server
{ serving-hostname string }
{ serving-directory string } ;
When a file is requested, it can be streamed back to clients:
: send-file ( path -- )
binary [ [ write ] each-block ] with-file-reader ;
The Gopher protocol is defined in RFC 1436 and lists a few differentiated file types. We use the mime.types vocabulary to return the correct one.
: gopher-type ( entry -- type )
dup directory? [
drop "1"
] [
name>> mime-type {
{ [ dup "text/" head? ] [ drop "0" ] }
{ [ dup "image/gif" = ] [ drop "g" ] }
{ [ dup "image/" head? ] [ drop "I" ] }
[ drop "9" ]
} cond
] if ;
When a directory is requested, we can send a listing of all the sub-directories and files it contains, sending their relative path to the root directory being served so they can be requested properly by the client:
:: send-directory ( server path -- )
path [
[
[ gopher-type ] [ name>> ] bi
dup path prepend-path
server serving-directory>> ?head drop
server serving-hostname>
server insecure>
"%s%s\t%s\t%s\t%d\r\n" sprintf utf8 encode write
] each
] with-directory-entries ;
To know which path was requested, we read the line, split on the first tab, carriage return, or newline character we see:
: read-gopher-path ( -- path )
readln [ "\t\r\n" member? ] split1-when drop ;
With all of that built, we can now implement a word to handle a client request:
M: gopher-server handle-client*
dup serving-directory>> read-gopher-path append-path
dup file-info directory? [
send-directory
] [
send-file drop
] if flush ;
Initializing a gopher-server
instance and providing a convenience word
to start one:
: <gopher-server> ( directory port -- server )
utf8 gopher-server new-threaded-server
"gopher.server" >>name
swap >>insecure
binary >>encoding
"localhost" >>serving-hostname
swap resolve-symlinks >>serving-directory ;
: start-gopher-server ( directory port -- server )
<gopher-server> start-server ;
This is available in the gopher.server vocabulary with a few improvements such as:
- Support for
.gophermap
files for alternate results when content is requested. - Support for
.gopherhead
files to print headers above directory listings. - Navigation to parent directories using
..
links. - Display file modified timestamp and file sizes.
- Improved error handling.