Hi Ho! Its been a long time without writing here. As you might know, I’ve just launched a new web/mobile (Guiato) platform to help retailers reach their customers with their existing brochures/pamphlets/flyers but now, electronically.
It was as a 4 months adventure, including 20 days in Germany to plan and bring all the platform to Brazil. But now it is time to clojure a bit more. And we are just beginning. My team and I have a simple google docs with amazing funny statements - or facts - we say. But it is too much sophisticated, so I decided to write our own system (Clacts) to register and read this facts using Clojure, Aleph as TCP server, Gloss for encoding/decoding messages and SQLite for storing facts. Amazing! Isn’t it? :) Well at last it was funny.
I started creating a very simple protocol to allow clients to connect via telnet. So it is:
We have two main commands,
PUT, author is the guy speaking, via is who noted it, and the fact is the statement itself. And for
LSA command, you can pass the author’s name and the system will return all the facts spoken by the author.
* means you want to read all the facts.
Any other command will be handled as error. Enter Gloss, a lib that allows you to draw how sequence of bytes will be converted to clojure data structures, and how clojure data will be converted to byte sequences. Here is the definition of Clacts the protocol:
Gloss uses the concept of frames and codecs to model your bytes. As a shortcut, i’m using
lp to identify parameters ended in
" " and parameters ended in
\r\n. That is,
lp are frames that will be converted to strings, with UTF-8 encoding.
Given the building block frames
lp we can start to form the commands. We have
PUTC, a codec that is composed by the word
PUT plus two
p frames and one
lp frame. So this:
PUT Agustinho Brisola Estou acostumado a criar my propria cloud, will be converted to:
["PUT" "Agustinho" "Brisolla" "Estou acostumado a criar minha propria cloud"]. Bang! We have bytes straight to a clojure vector. And testing it is pretty straight forward. Look:
There are actually more codecs (
LSRC) to handle generic responses and
LSA responses respectively. But once you understand the commands, the answers are natural consequences.
Hell yeah! Neat and handy. But clients can actually use different commands, how to understand which command to decode an handle appropriately?
For these cases (and others for sure) Gloss allows you to define a
header, which is some part of the frame that behaves as an indicative for the rest of the frames. In this case, look to the codec
CMDS. It is composed by a header that, depending on its content, indicates the other commands.
head function is a bit strange at first, but once you get it, you can go really far.
head takes 3 args, (i) its own frame, (ii) a function that given the header it points to the right codec for the rest of the message, and (iii) another function that given the body of a frame, extracts the value of the header. Easy?
PUT Agustinho Brisolla Teste Fact\r\n command as an example.
PUT is extracted by this
frist on it. This is the function that maps the body to header. And given the header, that is a
p (the first string separated by space), I check its value and return the appropriate codec:
Note the default value for
ERRC. This is for the cases where some smart user types an unknown command.
Great, but we have to handle the requests coming from telnet clients. Now it is Aleph time:
When you start the tcp server without defining the frame to handle, Aleph delivers to the
handler a series of
ByteBuffers, what is perfect for this case. The handler function decodes the frames against the
CMDS codec and calls the correspondent function passing as argument the channel to respond to.
Not that there is a default function -
handle-err being called in case of unknown commands. It will respond to clients random error messages.
The functions to list and put facts into the database use the same
CMDS codec to encode reply messages. Look:
REP is a command encoded by
REPC codec as defined above. The codec is defined by the header of the message (
REP). What is pretty useful and saves your from using
if to do that.
You may argue: “Why not use HTTP/Restful thing?” And I say: because this is more fun :)
You can find the full project on my github: https://github.com/paulosuzart/clact. There you can see more details regarding interacting with SQLite, that wasn’t covered here.
The great brain @ztellman, the creator of gloss, gave me a hand pulling some changes in the code. What he suggested was to specify the frame for the server. So Aleph takes care of the protocol being encoded/decoded and you interact solely with clojure data structures. The change was:
This is great because the best thing on every clojure lib is use pure clojure data structures to keep things uniform.
Thanks to Zach for the precious tips.