Internet Protocols and Rhino JavaScript
Originally published on: Fri, 04 Sep 2009 00:26:04 +0000
I plan to write a series of posts each of which will delve into some aspect of a common Internet protocol ( POP3, SMTP, HTTP, ...etc. ) It's easy enough to try and tinker with these protocols with a decent telnet client, but I had been wanting something that could provide both interactive and batch scripting.
I have tried many contemporary languages. Most are more than capable of what I want to accomplish. Ultimately, I decided to use the Mozilla Rhino JavaScript shell as the interface for our experimentation.
A version of Rhino is already packaged with Sun's Java SE 6.x, but does not exist for the Apple MacIntosh edition of Java 6.x. So, my examples will all be based on Rhino version 1.7_R2 which can be downloaded from http://www.mozilla.org/rhino/
I chose Rhino JavaScript because:
- It's easy to tap the power of existing Java classes when needed.
- A number of developers and designers have had at least some exposure to client-side JavaScript embedded in HTML.
- Rhino has a nice interactive shell or REPL ( Read Evaluate Print Loop ).
- I believe we can focus on just a few JavaScript programming constructs to experiment with Internet protocols.
After you've installed Rhino and have ensured that the js.jar is in your classpath, you can invoke the interactive Rhino shell by issuing the command
java -jar js.jar
At the js prompt, enter the string:
print("Hello, world!")
You should see the message Hello, world! and should be returned to the interactive Rhino prompt.
Rhino supports a common dialect of JavaScript for flow control and data manipulation. You can type help() at the prompt to see a list and description of extended shell functions. We won't be using many in this series of tutorials. We'll certainly use load() and print(), but we will rely heavily on the ability to interact with the running Java Virtual Machine (JVM) for most of our I/O needs.
For our first foray into TCP software, we're not even going to leave our own machines. We're going to write a server and client for a subset of the simple finger protocol in Rhino JS. Each of these will run locally on our machines ( although they could run anywhere Rhino is supported. )
We will begin to build a library of TCP JavaScript functions that will serve us in future posts as we delve into other Internet protocols.
The finger protocol is relatively simple. A standard finger daemon listens for traffic on port 79. It accepts a single string and may return multiple lines of text. Most computers have command-line finger clients, but a lot of Internet Service Providers block port 79 access. So, in order to learn a little about finger, we're going to have to build a server first.
Here's the first attempt:
fingerSrv1.js
// Simple finger server
//
// License: MIT / X11
// Copyright (c) 2009 by James K. Lawless
// jimbo@radiks.net http://www.radiks.net/~jimbo
// http://www.mailsend-online.com
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
importPackage(java.net);
importPackage(java.io);
serv=new ServerSocket(79,1);
for(;;) {
s=serv.accept();
br=new BufferedReader(
new InputStreamReader(
s.getInputStream()));
str=br.readLine();
print(str);
pw=new PrintWriter(s.getOutputStream(),true);
pw.println("Heya!");
pw.close();
}
The importPackage() function allows us to dynamically interoperate with all of the classes in the specified Java package. We will need the java.net and java.io packages.
The script first creates a ServerSocket object. The first parameter is the port that we want our program to listen to for traffic. The second parameter designates how many attempted connections should wait until we have time to service them. We will likely specify 1 in most of these examples.
In the main for-loop, we call the accept() method which waits ( or more correctly blocks the I/O ) until a connection is made from a client. Once made a construct called a socket is created and a Java Socket object is returned.
In order to perform I/O on the socket, we must utilize the socket's InputStream and OutputStream. A number of the Internet protocols are text-only, so we'll just use text samples for now.
The script creates a BufferedReader and a PrintWriter object using the socket's InputStream and OutputStream respectively. A call is then made to readLine() which reads a string passed to us by a client. The script displays this string and then writes the string "Heya!" back to the caller. The PrintWriter is then closed.
In order to test this script, you'll need a running Rhino JS shell prompt and a separate command prompt. Place the file fingerSrv1.js in the same directory as the js.jar file. From the js prompt type:
load("fingerSrv1.js");
If no syntax-errors were found, the script will then execute.
Enter your second command-prompt and type:
finger this_is_a_test@localhost
In the Rhino console, you should see the string this_is_a_test. The command-line finger client extracts the string to the left of the @ symbol and sends it to the server specified on the right side of the @ symbol.
The client command session should see:
your-machine-name-in-brackets
Heya!
So, version 1 of the server script listens, reads the string, displays it, and send back the string "Heya!".
For the remainder of the examples, we will move as much of the Java-specific code into a library of callable functions. Please refer to the file below:
tcplib.js
// TCP / Socket library functions for Rhino JavaScript
//
// License: MIT / X11
// Copyright (c) 2009 by James K. Lawless
// jimbo@radiks.net http://www.radiks.net/~jimbo
// http://www.mailsend-online.com
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
importPackage(java.net);
importPackage(java.io);
importPackage(java.lang);
function tcpGetLibraryVersion() {
return 1;
}
function tcpCreateServerSocket(port,numConnectionsToWait) {
return new ServerSocket(port,numConnectionsToWait);
}
function tcpAccept(serverSocket) {
var wrk;
wrk=new Object();
wrk.socket=serverSocket.accept();
wrk.inp=new BufferedReader(
new InputStreamReader(
wrk.socket.getInputStream()));
wrk.out=new PrintWriter(
wrk.socket.getOutputStream(),true);
return wrk;
}
function tcpConnect(server,port) {
var wrk;
wrk=new Object();
wrk.socket=new Socket(server,port);
wrk.inp=new BufferedReader(
new InputStreamReader(
wrk.socket.getInputStream()));
wrk.out=new PrintWriter(
wrk.socket.getOutputStream(),true);
return wrk;
}
function tcpSendString(tcpObj,str) {
tcpObj.out.print(str);
}
function tcpSendStringLine(tcpObj,str) {
tcpObj.out.println(str);
}
function tcpRecvString(tcpObj) {
var s,i;
// try 20 times with a1-second delay each.
for(i=0;i<20;i++) {
if(! tcpObj.inp.ready()) {
Thread.sleep(1000);
continue;
}
else {
s=tcpObj.inp.readLine();
return s;
}
}
return "";
}
function tcpClose(tcpObj) {
tcpObj.inp.close();
tcpObj.out.close();
tcpObj.socket.close();
}
I wanted to avoid having to create a BufferedReader object and a PrintWriter object in the JavaScript code. So, the TCP library uses a construct referred to as tcpObj which is actually a JavaScript map ( associative-array, if you will ) with three member objects: a Socket, a BufferedReader, and a PrintWriter. The Socket object is named socket. The BufferedReader object is named inp. The PrintWriter object is named out.
Now, let's take a look at a version of the finger server that only refers to the new tcplib.js file:
fingerSrv2.js
// Simple finger server, v2
//
// License: MIT / X11
// Copyright (c) 2009 by James K. Lawless
// jimbo@radiks.net http://www.radiks.net/~jimbo
// http://www.mailsend-online.com
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
load("tcplib.js");
serv=tcpCreateServerSocket(79,1);
for(;;) {
s=tcpAccept(serv);
str=tcpRecvString(s);
print(str);
tcpSendStringLine(s,"Heya!");
tcpClose(s);
}
Terminate the running Rhino JS shell and restart it. Now, load("fingerSrv2.js") and reissue the finger this_is_a_test@localhost command. You should see indentical results. Note how much shorter this script is and how we stick to JavaScript alone for every operation. ( Although most of these JavaScript functions are calling Java methods behind the scenes. )
Here's a third variant of the finger server that sends back a Heya! string along with a string representing the current date/time to the client.
fingerSrv3.js
// Simple finger server, v3
//
// License: MIT / X11
// Copyright (c) 2009 by James K. Lawless
// jimbo@radiks.net http://www.radiks.net/~jimbo
// http://www.mailsend-online.com
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
load("tcplib.js");
importPackage(java.util);
serv=tcpCreateServerSocket(79,1);
for(;;) {
s=tcpAccept(serv);
str=tcpRecvString(s);
print(str);
tcpSendStringLine(s,"Heya! The time is " +
new Date());
tcpClose(s);
}
The script references java.util so that a date string can be sent back.
We now have a few similar examples of a TCP server. Let's look at a finger client in Rhino JavaScript:
fingerClient1.js
// Simple finger client
//
// License: MIT / X11
// Copyright (c) 2009 by James K. Lawless
// jimbo@radiks.net http://www.radiks.net/~jimbo
// http://www.mailsend-online.com
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use,
// copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following
// conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
// OTHER DEALINGS IN THE SOFTWARE.
load("tcplib.js");
s=tcpConnect("localhost",79);
tcpSendStringLine(s,"Testing!");
str=tcpRecvString(s);
print(str);
With one of the three finger servers running, start a new Rhino JS session and issue the function call:
At the js prompt, enter the string:
load("fingerClient1.js")
You should see the returned Heya! message from the server. In the server's console output, you should see the string Testing!
Now that we have the basics down, we'll try to tackle a more elaborate protocol next time.
The JavaScript sources referenced above can be downloaded in a single archive at:
http://www.mailsend-online.com/wp/rhinofinger.zip
Unless otherwise noted, all code and text entries are Copyright ©2009 by James K. Lawless
Save to del.icio.us
Save to StumbleUpon
Digg it
Save to Reddit
Share on Facebook
Share on Twitter
More bookmarks