diff options
-rw-r--r-- | content/clopher/01-Intro.md | 13 | ||||
-rw-r--r-- | content/clopher/02-Protocol.md | 129 | ||||
-rw-r--r-- | content/clopher/clopher01.md | 15 |
3 files changed, 108 insertions, 49 deletions
diff --git a/content/clopher/01-Intro.md b/content/clopher/01-Intro.md index 2a2e90d..ce07f5d 100644 --- a/content/clopher/01-Intro.md +++ b/content/clopher/01-Intro.md @@ -54,7 +54,7 @@ know– it's also about remembering that libraries can't be software you just import: they should be read, analysed and, often, thrown away in favor of an ad-hoc solution. Many times ad-hoc solutions reduce the codebase size and they solve the problem more accurately, as they are specifically design to solve -*our* problem. +*our* problem.[^1] Also, it's good to tell yourself you can code everything from scratch and try to prove it true. @@ -83,6 +83,17 @@ I'm just sharing my experiences. I'm looking at the abyss and telling you what I see from this view, pointing the interesting things I spot. +[^1]: As a note, while I was writing this, I experienced some issues with + nested dependencies in a different piece of software I was using. + Dependencies can be understood as a tree, with your project at the root. More + deep the tree is, longer time for changes to arrive the root of the tree from + the leaves, because changes must be accepted in all the nodes of the affected + branch and developers are busy. This can be a problem like in the case I + experienced where a bug in a leave of the tree was solved but the root was + broken and was unable to solve the issue because they needed an intermediate + node to update the version of the leave. This *hurts*. + *(They should've never added the change in the first place but when + dependencies go deep it's more difficult to detect bugs)* [gopher]: https://en.wikipedia.org/wiki/Gopher_%28protocol%29 [gopher-rfc]: https://tools.ietf.org/html/rfc1436 diff --git a/content/clopher/02-Protocol.md b/content/clopher/02-Protocol.md index 32361c7..91a8161 100644 --- a/content/clopher/02-Protocol.md +++ b/content/clopher/02-Protocol.md @@ -19,14 +19,15 @@ did the difficult part for you. Gopher is a really simple protocol (this doesn't mean I implemented it correctly anyway). It's assumed to work on top of TCP (default port is 70) and -it's as simple as creating a socket, sending the *selector string* to it, and -reading everything from it until it closes. That's in most of the cases how it -works. +it's as simple as creating a socket, sending the *selector string* to it +followed by a line terminator[^1], and reading everything from it until it +closes. That's in most of the cases how it works. It has two major ways to access the data: 1. **Text mode**, which is used in most of the queries, needs the client to - read from the socket until a line with a single dot (`.`) appears[^1]. + read from the socket until a line with a single dot (`.`) appears. Then + the connection is closed. 2. **Binary mode**, expects the client to read from the socket until the server closes it. @@ -42,7 +43,7 @@ menu, and it's called, unsurprisingly, *Menu*. Menus have a description of the content, the address or hostname, the port, the selector string, and a number that indicates the type of each of its elements -separated by a TAB (aka "\t" character). Each element in one line[^1]. +separated by a TAB (aka `\t` character). Each element in one line[^1]. Pay attention to the fact that each menu entry contains an address and a port, that means it can be pointing to a different server! @@ -67,9 +68,41 @@ There are some points more but this is more than enough for the moment. Let's make something work. -### Make Gopher queries work +### Make Gopher queries -A simple text request can be understood like this piece of Clojure code here: +Before jumping to Clojure, lets make sure that we understood how this works +with some simple text queries. In a UNIX-like terminal you can do the following +to navigate the *Gopherverse*: + + ::bash + exec 5<>/dev/tcp/tilde.team/70 + echo -e "~giacomo\r\n" >&5 + cat <&5 + +This code opens a TCP socket to `tilde.team` at port `70` sends the selector +string `~giacomo` followed by the line terminator (`\r\n`) and prints the +answer. Simple. + +You can do some telnet magic instead, which is easier but not as cool as the +other[^3]: + + telnet tilde.team 70 + ~giacomo + +If you run the code you'll see you can understand the response with your bare +eyes with no parser involved. Isn't that great? + +Notice that in our examples our selector string is `~giacomo`. Gopher supports +empty strings as selector strings that, in most cases, return a Menu where we +can see which selector strings are valid. Why don't you try it yourself? + +### Move to Clojure + +Now we understand what it's happening under the hood, it's time to move to +Clojure. + +A simple text request can be understood like this piece of Clojure code here +(which involves more Java than I'd like to): ::clojure ; Define the function to make the queries @@ -92,6 +125,8 @@ doing any kind of parsing, error checking or timeout handling, but it works. This a minimal (and ugly, clean the namespaces!) implementation for you to be able to run it in the REPL. +### Binary or not? + The binary is almost the same but the output must be handled in a different way. As Clopher is a terminal based application I made it store the answer in a file. @@ -101,42 +136,61 @@ can access from Clojure. As I wasn't a Java user before I didn't know this: ::clojure (defn- ->tempfile - "Creates a temporary file with the provided extension. If extension is nil - it adds `.tmp`." - [extension] - (doto - (. java.io.File createTempFile "clopher" extension) - .deleteOnExit)) + "Creates a temporary file with the provided extension. If extension is + nil it adds `.tmp`." + [extension] + (doto + (. java.io.File createTempFile "clopher" extension) + .deleteOnExit)) With this function is really simple to create a temporary file and copy the download there. It's also easy to ask the user if they want to store the file -as a temporary file or in a specific path. +as a temporary file or in a specific path. With the code below, calling to +`download-file-to` works like we described. If `destpath` is `nil` a temporary +file is created. Cool.[^4] + + ::clojure + (defn download-file-to + [host port srcpath destpath] + (with-open [sock (->socket host port) + writer (io/writer sock) + reader (io/reader sock)] + (.append writer (str srcpath defs/CR-LF)) + (.flush writer) + (io/copy reader + (io/output-stream + (or (io/file destpath) + (->tempfile (get-extension srcpath))))))) + +### <code>doto</code>, make Java interop less painful You probably know what `doto` does but it's interesting enough to talk about it here. It returns the result of the first form with all the rest of the forms -applied (inserting the result of the first form as first argument), discarding -their return values. This sounds weird at the beginning but in cases like this -one where you are working with mutation it's really handy. We are creating a -`File` instance and returning it after calling `.deleteOnExit` on it. Take in -consideration that `.deleteOnExit` returns nothing, so it's really cool for us -to have a `doto` macro. +applied inserting the first form's result as first argument and discarding the +result of the operations. This sounds weird at the beginning but in cases like +this one where you are working with mutation it's really handy: -Once we now how to deal with `doto` we can improve the caller to with this -function that creates sockets with some timeout applied and automatically -connects: +We are creating a `File` instance and returning it after calling +`.deleteOnExit` on it. Take in consideration that `.deleteOnExit` returns +nothing, so discarding its return value is great. We want to return the `File`, +not the result of the `.deleteOnExit` operation. + +Once we now how to deal with `doto` we can improve the caller with this +function that creates sockets with some timeout applied that connect +automatically: ::clojure (defn- ->socket ([host port] (->socket host port 10000)) ([host port timeout] - (doto (java.net.Socket.) - (.setSoTimeout timeout) - (.connect (InetSocketAddress. host port) timeout)))) + (doto (java.net.Socket.) + (.setSoTimeout timeout) + (.connect (java.net.InetSocketAddress. host port) timeout)))) Replacing `java.net.Socket` from the example above with a call to this function -will make the call handle timeouts. +will make the call handle timeouts, configuring the socket on its creation. Whatever, right? Better check the code for that. Beware that it may change as I keep going with the development. Maybe not, it depends on the time I spend on @@ -145,20 +199,29 @@ this. Here's the link to the code. Relevant part can be found in `src/clojure/clopher` in a file called `net` or similar: -https://gitlab.com/ekaitz-zarraga/clopher +[Link to the repository](https://gitlab.com/ekaitz-zarraga/clopher) + +It's time to move on because this is taking longer than it should. We are just +warming up, let's leave it simple at the beginning, there will be chance to +make this complex in the near future. + +Hope you enjoyed this post. -Time to move on. ### Hey! But what about the Menus? Menus are just queried like any other text document so they can be queried with this little code. The parsing, processing and so on is only needed for user -interaction so we'll deal with that later. Don't worry, we'll arrive soonish. +interaction so we'll deal with that later. Don't worry. We all have to learn to +be patient. See you in the next step. -[^1]: Line terminator is CRLF (carriage-return + line-feed), aka `"\r\n"`. -[^2]: Don't be afraid of the types because they are just a few of them. -[gopher]: https://en.wikipedia.org/wiki/Gopher_%28protocol%29 +[^1]: Line terminator is CRLF (carriage-return + line-feed), aka `\r\n`. +[^2]: Don't be afraid of the types because they are just a few of them. +[^3]: Remember to jump line after you enter the selector string. +[^4]: You have to implement `get-extension` yourself but you know how to do it. + +[gopher]: https://en.wikipedia.org/wiki/Gopher_%28protocol%29 diff --git a/content/clopher/clopher01.md b/content/clopher/clopher01.md deleted file mode 100644 index de4972e..0000000 --- a/content/clopher/clopher01.md +++ /dev/null @@ -1,15 +0,0 @@ -Title: Clopher01- Introducing Clopher -Date: 2019-03-23 -Categories: Series -Tags: clopher -Slug: clopher01 -Lang: en -Summary: The process, the reasons and the conclusions. - -> Read all the posts in [Clopher series here]({tag}clopher). - -When you do a hack (or even a dirty hack) you do it for some reason. You do it -because you can understand the complexity of the problem and you see it's a -complex problem to solve that needs a good enough solution for your case. - -You are facing the complexity. You are seeing it. |