We’ve just explored how to create socket servers that listen for incoming connections in Node. So far, our example programs have sent plain-text messages that are meant to be read by a human. In this section we’ll design and implement a better protocol.
A protocol is a set of rules that defines how endpoints in a system communicate. Any time you develop a networked application in Node.js, you’re working with one or more protocols. Here we’ll create a protocol based on passing JSON messages over TCP.
JSON is incredibly prevalent in Node.js. We’ll use it extensively for data serialization and configuration throughout the book. It is significantly easier to program clients against than plain text, and it’s still human-readable.
We’ll implement client and server endpoints that use our new JSON-based protocol. This will give us opportunities to develop test cases and refactor our code into reusable modules.
Let’s develop the message-passing protocol that uses JSON to serialize messages. Each message is a JSON-serialized object, which is a hash of key-value pairs. Here’s an example JSON object with two key-value pairs:
| | {"key":"value","anotherKey":"anotherValue"} |
The net-watcher service we’ve been developing in this chapter sends two kinds of messages that we need to convert to JSON:
When the connection is first established, the client receives the string Now watching "target.txt" for changes....
Whenever the target file changes, the client receives a string like this: File changed: Fri Dec 18 2015 05:44:00 GMT-0500 (EST).
We’ll encode the first kind of message this way:
| | {"type":"watching","file":"target.txt"} |
The type field indicates that this is a watching message—the specified file is now being watched.
The second type of message is encoded this way:
| | {"type":"changed","timestamp":1358175733785} |
Here the type field announces that the target file has changed. The timestamp field contains an integer value representing the number of milliseconds since midnight, January 1, 1970. This happens to be an easy time format to work with in JavaScript. For example, you can get the current time in this format with Date.now.
Notice that there are no line breaks in our JSON messages. Although JSON is whitespace agnostic—it ignores whitespace outside of string values—our protocol will use newlines only to separate messages. We’ll refer to this protocol as line-delimited JSON (LDJ).
Now that we’ve defined an improved, computer-accessible protocol, let’s modify the net-watcher service to use it. Then we’ll create client programs that receive and interpret these messages.
Our task is to use JSON.stringify to encode message objects and send them out through connection.write. JSON.stringify takes a JavaScript object, and returns a string containing a serialized representation of that object in JSON form.
Open your editor to the net-watcher.js program. Find the following line:
| | connection.write(`Now watching "${filename}" for changes...\n`); |
And replace it with this:
| | connection.write(JSON.stringify({type: 'watching', file: filename}) + '\n'); |
Next, find the call to connection.write inside the watcher:
| | const watcher = |
| | fs.watch(filename, () => connection.write(`File changed: ${new Date()}\n`)); |
And replace it with this:
| | const watcher = fs.watch(filename, () => connection.write( |
| | JSON.stringify({type: 'changed', timestamp: Date.now()}) + '\n')); |
Save this updated file as net-watcher-json-service.js. Run the new program as always, remembering to specify a target file:
| | $ node net-watcher-json-service.js target.txt |
| | Listening for subscribers... |
Then connect using netcat from a second terminal:
| | $ nc localhost 60300 |
| | {"type":"watching","file":"target.txt"} |
When you touch the target.txt file, you’ll see output like this from your client:
| | {"type":"changed","timestamp":1450437616760} |
Now we’re ready to write a client program that processes these messages.