Part 1: Hello
Ahoy, world!
As programmers, we're all sorta obsessed with Hello World, aren't we? So why don't we dive in now and do just that!
Hello, world!
Type in your terminal the following command:
pipy -e "console.log('Hello, world!')"
The '-e' option tells Pipy to evaluate the expression coming after. The expression we're evaluating here is:
console.log('Hello, world!')
It says in PipyJS, invoke function console.log() with a string parameter "Hello, world!".
PipyJS is the dialect of ECMAScript that Pipy speaks. Refer to PipyJS Reference for more information.
Pipy will now start but quit almost immediately, leaving the following output:
[INF] Hello, world!undefined[ERR] [pjs] Script did not result in a Configuration[INF] [shutdown] Shutting down...[INF] [shutdown] Stopped.Done.
Let's take a look at the output:
On the first line, we see "Hello, world!". This is what the function call to console.log() ended up with: a log output to the console.
On the second line, we see "undefined". This is the result from evaluating our expression. Since function console.log() has no return values, the evaluated result was undefined.
On the third line, we see an error message saying "Script did not result in a Configuration". Here, Configuration is the main API provided by Pipy for pipeline setups, which we'll see being used a lot later on. For now, the only thing you need to know is, Pipy requires a Configuration to continue as a long-lived server. However, since the expression evaluation gave us undefined, which is not a Configuration, so there's nothing else Pipy could do but quit. That's what we see in the output after: Pipy shut down everything and stopped running.
As you can see from above, Pipy is fundamentally an "expression evaluator". If you say "pipy -e 1+1", it tells you "2", nothing fancy indeed. Surely that isn't what we created Pipy for. To make Pipy do something more interesting, we should write an expression that evaluates to a Configuration.
Hello, world in HTTP!
Now let's use Pipy the way it usually is: run an HTTP server.
pipy -e "pipy().listen(8080).serveHTTP(new Message('Hi, there!'))"
This time, we're evaluating a slightly more complex expression. The same expression can be rewritten in multiple lines for better clarity:
pipy().listen(8080).serveHTTP(new Message('Hi, there!'))
Code dissection
Take a bit of time to examine the code. Here's what's going on:
At the very first line, the built-in function pipy() is called. It returns a Configuration object. That's what Pipy expects for running a server.
At line 2, by calling method listen() on the Configuration object that we just got from pipy(), we add a port pipeline layout to the configuration that listens on port 8080. Method listen() itself returns the same Configuration object, so we can continually call other methods on it to add more pipeline layouts and filters. That's exactly what we are about to do next.
At line 3, we add a serveHTTP() filter to the pipeline layout that we just added in line 2. The filter takes a Message object as its construction parameter. At runtime, it'll be outputting that same message in response to any input messages.
To recap, the expression we've written above defined one pipeline layout containing only one filter. To picture it all would be like this:
Test in action
Input the above command in your terminal, you'll see output like this:
[INF] [config][INF] [config] Module[INF] [config] =======[INF] [config][INF] [config] [Listen on 8080 at 0.0.0.0][INF] [config] ----->|[INF] [config] |[INF] [config] serveHTTP -->|[INF] [config] |[INF] [config] <-----------------|[INF] [config][INF] [listener] Listening on TCP port 8080 at 0.0.0.0
This time, Pipy didn't quit. Instead, it started listening on port 8080 for HTTP requests.
Now open up a second terminal window and send a request with curl:
curl localhost:8080
You should see the result:
Hi, there!
Summary
In this first part of our tutorial, you've learned how a Pipy program is organized by creating and running a simple one-liner HTTP server.
Takeaways
A Pipy program is made of definitions of pipeline layouts and filters. It always starts off with a call to pipy(), which gives us a Configuration object, which we call various methods on to add pipeline layouts and filters.
A port pipeline reads from a network port and writes back to that port whatever its last filter outputs.
A serveHTTP() filter takes in HTTP messages as requests and outputs HTTP messages as responses.
What's next?
Now you've made a simple Hello World server, but it's barely useful because it always says the same thing. Next, we'll look at how we can respond to different requests with different responses.