Cowsay Server - Part 2

Table of Contents

(This article is part 2 of 3 of my Cowsay Series of articles.)

This is the second post in a series of articles about writing my first application that uses sockets. For more information about why I'm doing this or how, please see my first article.

More Functional Requirements

I have a working server, but there are two things that bug me about it:

  1. I have to test it using netcat, which is good for simple stuff but things would be much easier with an actual client.
  2. Right now, the server just process a "raw" string of commands. I would rather have the server interpret parameters.

I figure that I'm going to need some type of "message format" to make requirement #2 work, so I first try to define that.

My Message Format

Since I'm familar with HTTP, I decided to use a message format that is very similar. Right now, I simply want to be able to pass a message and cow body format to the cowsay server. I therefore decided to send messages that look something like this:

MESSAGE This SUCKS!
BODY beavis.zen

That's it. Just plain old text (unicode?) over the wire with two properties. In the future, I'll probably want to use return codes and more header options.

The Client

Here's my first stab at a very simple client:

This is really a very simple socket client. I have one real method called say which understands two keys, message and body. I then take those values, drop them in a heredoc, and then send that to the server.

Of course, now that I'm using a new message format, I'm going to need to make some changes on the server too.

The Server, Part Two

Here's my stab at creating a server that can read the new message format:

There's a few things that I added to this code:

  • Before sending the message to the process method, I now have to parse it.
  • The parse method simply grabs the MESSAGE and BODY values with some help from the find_value_for_key method and then performs some very simple validation.
  • The process method now does some very rudimentary parameterization. Eventually I would like some more safeguards in place to ensure that bad input cannot be passed to the cowsay executable, but for now this will do.

Testing

First, let's take a look at some "happy path" testing. In your first window, execute the following command:

$ ruby server.rb Listening on port 4481

Great. Now in another window, execute the following command:

$ ruby client.rb
 _______________
< this is cool! >
 ---------------
        \   ^__^
         \  (oo)\_______
            (__)\       )\/\
                ||----w |
                ||     ||
 _____________
< This SUCKS! >
 -------------
   \         __------~~-,
    \      ,'            ,
          /               \
         /                :
        |                  '
         _| =-.     .-.   ||
         o|/o/       _.   |
         /  ~          \ |
       (____@)  ___~    |
          |_===~~~.`    |
       _______.--~     |
       \________       |
                \      |
              __/-___-- -__
             /            _ \
 ______________
< Moshi moshi! >
 --------------
  \
   \
      /\_)o<
     |      \
     | O . O|
      \_____/

Nice. Let's also try a quick test using netcat:

$ echo "MESSAGE Oh YEAH\nBODY milk" | nc localhost 4481
 _________
< Oh YEAH >
 ---------
 \     ____________ 
  \    |__________|
      /           /\
     /           /  \
    /___________/___/|
    |          |     |
    |  ==\ /== |     |
    |   O   O  | \ \ |
    |     <    |  \ \|
   /|          |   \ \
  / |  \_____/ |   / /
 / /|          |  / /|
/||\|          | /||\/
    -------------|   
       |  |  |  |
      <__/    \__>

And now for the unhappy path. What happens if I pass a "body type" that the cowsay server doesn't recognize?

$ echo "MESSAGE Boom goes the dynamite\nBODY bogus" | nc localhost 4481

The client exits normally, but I see the following error message in the console window in which the server is running:

cowsay: Could not find bogus cowfile!

It looks like the STDERR from the cowsay process is only being written to the console. In the future, I'll need to capture that and make the server appropriately.

What if I don't pass a message?

$ echo "BODY default" | nc localhost 4481

In this case, the client freezes. I then see the following error in the server console window:

ERROR: Empty message

The server then becomes unresponsive. This is definitely the first bug that I will need to fix in my next revision.

Conclusion

I'm happy with the progress of my little socket server and client. In my next revision I am going to focus on the following:

  • Having the server handle bad input gracefully
  • Making sure that the server is able to respond in a predictable, informative way when it experiences issues
  • Finally ditching the backticks and executing the cowsay process in a more robust way.

Last Updated 2013-11-27 21:00.