I have to say, I'm really happy with my tiny gopher multithreading
#'make-load-form de/serializing gopher server. Check it out in
common-lisp/i2pher.lisp
after reading this post, probably. (i2pher::test3) makes a gophermap/server
serving two item type 0s on 127.0.0.1 port 54321 for example.
I (turn out not to have) have a serious problem with lynx, which relates to a 
key feature that complies with rfc1436.
I am using READable lisp lists (with *read-eval* locally set to nil, thank you)
as my item specifiers SO THAT my items are in general date ordered feeds of
textual orgmode compatible (or markdown, basically) text documents. I purport
that this conforms to rfc1436 (and it works just like I wanted).
```totally erroneous, I forgot my external gopher port was different.
BUT
As you may know by hitting backslash, lynx clumsily converts plain text to html
format. And in the case of (:item-specifier) you end up with something like
%28%aitem-specifier%29
Bizarre get-request-eze aside, this clashes with a compatibility feature I added
preemptively.
```
Traditionally on some gopher browsers, such as lynx, unrelated to rfc1436,
requests are arbitrarily prefixed with their item-type character, which is
discarded.
Now, using (let ((*read-eval* nil)) (read stream)) is important to me. Lisp has
some dispensation - the poorly named #'peek-char,
(peek-char #\( stream) #|) [closing that bracket in a block comment]|#
which eats chars until it reads a #\(, which it then unreads. BUT lynx's
gophermap no longer has any #\(s in it. It just has %28 etc. So peek-char will
always discard all the characters that lynx is erroneously passing as an item-
specifier [or so I quickly and wrongly assumed].
So my granfalloon (slash just netcat) works (on (i2pher::test3)) ;
printf "(:map)\n" | nc localhost 54321
Oh, hang on, am I making a dumb mistake. Oh yeah I was. In my debugging I was
using a local port different to the advertised port (for a level of indirection
/ especially with i2pd). Lynx in fact understands its htmleze %28 as meaning to
send a #\(. Disregard me.
ALL IS WELL AND GOOD THEN with the following:
In one term: (using rlwrap, which could be left out)
rlwrap ecl --load i2pher.lisp --eval '(i2pher::test3)'
and in another term:
lynx gopher://localhost:54321/1\(:map\)
(to get to the gophermap(s) specified by including the keyword :map)
And lynx's browsing works normally. Note the escaped brackets etc in ksh.
Now I know there are lots of small gopher servers, many of which did their
connect(2)s themselves rather than farming them out to openbsd netcat, even
though they probably have less dab multiprocessing. (The kyoto lisp tradition
calls threads processes, clashing with the unix notion, which is the process
being run in the process).
My real killer feature is this ; YOU DON'T NEED TO HAVE A DEEP UNDERSTANDING
:                              ; OF THIS METHOD BUT IT IS OBVIOUSLY USEFUL
```ecl
(load #p"i2pher.lisp")
(in-package i2pher)
;;
(setq *goma*
 (make-instance 'gophermap :item-specifier '(:map :toplevel)
                           :item-description "The toplevel gophermap"
                           :filename #p"my-gophermap.lisp"))
;;And lets add a serving process.
(add-thread *goma*)
```
which we can look at with any/all of these:
```ksh ## somewhere else
lynx gopher://1\(:map\)
lynx 'gopher://1(:map  :toplevel)'
lynx gopher://F\(:toplevel\ :map\)
```
etc. Note that the item-type character is ignored (by lynx and/or me).
Back in the lisp terminal:
```ecl
;;;Just cleaning up that serving process
;; I've commented this out, since it would only be necessary if you are
;; experiencing certain race conditions.
;; Oh, actually, taking (lock *goma*) would make this safe; it's safe now anyway
;; (mapcar 'mp:process-kill *threads*)
;; (mp:process-join (pop *threads*))
;;
;;;Let's spawn some new gophers.
;;;Tbh this is pretty ugly, but it works for now.
(spawn *goma* 0 "First spawn description" '(:child :first :item-type-0)
      "      All the text that is going to go in here
      should probably be read from a file. But I'm not going to re-explain my
      fantastic sam-d.lisp reader macro here. Focus.")
;;
(spawn *goma* 0 "Second spawn" '(:child :second :item-type-0) "shorter.")
```
let's have a quick look that lynx was, in fact, working..
(or you could just refresh lynx and have a look around)
```ksh
lynx 'gopher://localhost:54321/0(:map)'
## Navigate to either, but now for something lynx isn't expecting
## (in lynx:)
## g gopher://localhost:54321/0(:child%20:item-type-0)
#similarly
printf "(:first :child)\n" | nc localhost 54321
```
Haha, there we go. We have to write %20 instead of hitting #\space .
: That gets a reverse-chronological list of everything hitting its keywords.
        ;Lisp aside: The comparison is (remove-if-not 
        ;                          like (lambda (x) (subsetp keywords
        ;                                            (item-specifier x)
        ;                                            :test 'equalp)) 
        ;                               gopher-list)
        ; So :keywords isn't important. The test is an 'equalp 'subsetp.
But this level of interaction would suck if common lisp didn't have a canonical
way for us to get code that would instantiate the objects we have created in it.
```ecl
(make-load-form *goma*)
```
Yep, that's a lot of lisp-formatter idiomatic capital letter lisp code.
My #'save just puts it in (filename *goma*) we set to my-gophermap.lisp earlier.
```ecl
(save *goma*)
(si:quit)
```
Take a look at
```ksh
cat my-gophermap.lisp
#Start a new lisp repl
rlwrap ecl
```
```ecl
(load #p"i2pher.lisp")
(in-package i2pher)
(setq *goma-is-back*
 (with-open-file (in #p"my-gophermap.lisp") (eval (read in))))
;;;And we're in business!
(princ (in-memory *goma-is-back*))
(add-thread *goma-is-back*)
```
and refresh lynx, say. I'm pretty sure nested gophermaps work as well.
(It's recursive).
Pretty
cool