-
Download the FULL Vert.x distribution from http://vertx.io/ and ensure that the vertx command is in your path (vertx/bin/vertx)
-
Create a new file Exercise1.java with the following contents:
link:Exercise1/Exercise1.java[role=include]
-
Run the verticle with the command
vertx run Exercise1.java
-
You should see a message like:
Succeeded in deploying verticle
-
Open a browser and point it at: http://localhost:8080/
Next Steps: (see HttpServerResponse)
-
Modify the example above to add a
Content-Type
response header -
Modify the example above to add an HTTP response code of 201 to the response
-
Modify the example above to add an HTTP reason phrase of ‘IDUNNO’ to the response
Vert.x APIs are written to be fluent. This means that you can chain method calls together so that they form a sort of domain specific language which CAN be easier to read. We will modify our first example to use the fluent API in Vert.x to perform the same operations.
link:Exercise2/Exercise2.java[role=include]
You’ll see that we chained the createHttpServer() method, which returns an HttpServer object, to the requestHandler() method. We then chained the requestHandler() method to the listen() method. Each of these chained methods returns the original HttpServer object so that we can make subsequent calls in a fluent manner.
A handler in Vert.x is a form of Callback). Handlers are passed as arguments to some Vert.x methods so that the callback can be executed once a particular asynchronous operation has been completed. Handlers for Vert.x can be written in Java in several ways:
The basic Handler in Vert.x is any class which implements the Handler interface. For example:
link:Exercise3/Exercise3_1.java[role=include]
As you can see, we pass an instance of the RequestHandler class to the requestHandler() method on the HttpServer object and that instance will handle the HTTP requests.
Another way to implement handlers removes some of the boiler-plate of having a separate hanlder class for each Callback we want to register. It’s called a Method Reference. A method reference is a way of assigning a method to behave as a callback without having to implement a Handler interface on a new class.
link:Exercise3/Exercise3_2.java[role=include]
Finally, in Java we can use Lambdas. Lambdas are a way of writing a bit of code which can be passed as a value . . . in-line…
link:Exercise3/Exercise3_3.java[role=include]
An alternate way of declaring that closure would be to assign the closure to a variable and then pass the variable into the requestHandler() method as shown below:
link:Exercise3/Exercise3_3_1.java[role=include]
Next Steps: (see HttpServerRequest)
-
Modify the example above to include the requested path as an item in the JSON response body
-
Modify the example above to include the request headers as a nested JSON object within the response body
So far, we have seen that we can add a requestHandler() to an HTTP server, but what if we want to have a number of different paths which do different things in our web application? This is where the Vert.x Web module comes in. It gives us a new features like Router and RoutingContext.
link:Exercise4/Exercise4.java[role=include]
-
You see that we added 2 different routes to the Router instance
-
Each route has a separate handler set via a method reference
-
Finally, we pass the Router’s accept method via a method reference as a handler for the HttpServer’s requestHandler() method.
In the previous example, we saw that we could specify different paths with different handlers, but what about if we want to capture information FROM the path in a programmatic manner?
link:Exercise5/Exercise5.java[role=include]
Next Steps: (see Router, Routing With Regular Expressions, Routing Based On MIME Types, Request Body Handling)
-
Modify the example above to have a new route which had multiple path parameters
-
Modify the example above to use a route with regular expressions
-
Modify the example to add a new HTTP POST endpoint which consumes JSON and produces the POSTed JSON
So far, our exercised have done all of their work in a single Verticle (HelloWorld). This is fine for simple applications, but it does not scale well for larger and more complex applications. Each Verticle is single-threaded; so in order to utilize our CPU cores effectively, we need to distribute workloads across multiple Verticles.
link:Exercise6/EventVerticle.java[role=include]
link:Exercise6/Exercise6.java[role=include]
(NOTE: When using vertx run <VerticleName>
to launch Vert.x applications, the files should be in the current
working directory or a child directory referenced by it’s relative path)
Several new concepts have been introduced in this example:
-
The EventBus - Used to communicate between Verticles in a thread-safe manner
-
Deploying Verticles Programmatically
-
Handling AsyncResults via Callback
-
Using Message objects - Message objects consist of JsonObject or String contents and can be replied to
Often, the application will need to ensure that certain Verticles are already up and running before proceeding to do other actions. To allow for this, Vert.x provides a way of deploying Verticles with a callback once the deployment is complete.
link:Exercise7/Exercise7.java[role=include]
link:Exercise7/EventVerticle.java[role=include]
Next Steps:
-
Modify the example above to attempt to redeploy EventVerticle in case of a failure (Use maximum of 3 retries)
-
Modify the example above to deploy more than one Verticle and call the new Verticle
AnotherVerticle.java
It is useful to coordinate several asynchronous operations in a single handler for certain situations. To facilitate this, Vert.x provides a CompositeFuture
link:Exercise8/Exercise8.java[role=include]
Next Steps: (see CompositeFuture and Async Coordination) * Modify the example above to use a List of futures instead of specifying each future as a parameter. * Remove the CompositeFuture and use composed Futures to load one verticle after another
While you can coordinate between verticles very well using String and JsonObject instances over the EventBus, it is sometimes better to share certain objects across multiple verticles. Vert.x makes this possible via 2 facilities.
The Vertx.sharedData() method allows us to get an instance of LocalMap which can store most Immutable data types as well as custom types which implement the Shareable interface. Storing data in a these LocalMap instances makes those objects available to other Verticles without having to use the EventBus to send those objects. The Shared Local Map has no concurrency controls, so the last writer is always the winner. If assurance of ordered writes is required, then the user must implement their own concurrency controls or only use data structures which ensure thread safety.
link:Exercise9/Exercise9_1.java[role=include]
link:Exercise9/AnotherVerticle.java[role=include]
When running in a clustered configuration, sharing objects across Vert.x nodes requires a special feature known as
AsyncMap. The AsyncMap is handled by the Vert.x
ClusterManager, which is responsible for
ensuring that access to the AsyncMap data is handled in a cluster/thread-safe way. In order to use the AsyncMap, Vert.x
MUST be started in a clustered mode using vertx run -cluster <Verticle>
link:Exercise9/Exercise9_2.java[role=include]
link:Exercise9/ClusteredVerticle.java[role=include]
This cluster-wide data coordination is complex, so it is always advisable to send shared data via the EventBus where possible. The ClusterManager and the AsyncMap implementations ensure that access to and writing of clustered resources are synchronized properly across the entire cluster and thust prevents race conditions. The negative impact being that access to read/write clustered data is much slower.
As a quick introduction to the network server capabilities of Vert.x, Let’s implement a TCP Echo Server. An echo server is a network socket server which accepts incoming data and sends the same data back as a response.
link:Exercise10/Exercise10.java[role=include]
There are some new things to learn in this example. For one, there is the introduction of NetServer and it’s associated options; but that it mostly self-explanitory. The other thing to make note of is the use of the Buffer object. From the Vert.x API documentation:
Most data is shuffled around inside Vert.x using buffers. A buffer is a sequence of zero or more bytes that can read from or written to and which expands automatically as necessary to accommodate any bytes written to it. You can perhaps think of a buffer as smart byte array.
You can think of *Buffer*s as a way of pushing around streams of bytes. Buffers also have some convenience methods
like toString()
, toJsonObject()
, and toJsonArray()
. You can append to a Buffer using one of the provided append
methods which can handle input types like Int/Float/Short/Unsigned/String/Byte/Long/Double. There are also append
methods for storing data in the buffer in little-endian byte order.
Next Steps:
-
Modify the EchoServer above to take in some text (Latin characters, numbers, spaces, newlines ONLY), ignore non-text, and send back
Hello <text>
.
Vert.x also has the ability to create UDP servers. Let’s see what a UDP echo server would look like in Vert.x:
link:Exercise11/Exercise11.java[role=include]
Next Steps:
-
Modify the EchoServer above to take in some text (Latin characters, numbers, spaces, newlines ONLY), ignore non-text, and send back
Hello <text>
.
HTTP is a mainstay of software these days, so being able to make and handle HTTP requests is vital. Let’s see how Vert.x make HTTP requests in an asynchronous manner:
link:Exercise12/Exercise12.java[role=include]
Next Steps
-
Make an HTTP GET request which uses HTTP Basic Authentication
-
Make an HTTP POST request which sends a JSON body
We’ve covered a number of individual features of Vert.x, Async, and non-blocking APIs in Vert.x, but in this exercise we will try to put a few different ones together. Here’s the scenario:
-
An HTTP server listening on port 8080
-
A web browser will make a request to the '/merged/' endpoint
-
The Vert.x application will execute several operations in parallel
-
Request the
www.google.com
index page -
Read a file (Your choice, but make it a simple short file) from the filesystem
-
Perform a DNS lookup on
www.google.com
-
Once all of the parallel operations are complete, insert the file contents and the dns results into a
<pre>
block before the ending</body>
tag in the html retrieved from Google -
Return the modified Google index page to the browser
-
If ANY one of the async operations fails, return a 500 HTTP response with the exception’s
localizedMessage
value.
There is only one component here which you are not already familiar with, and that is the DNSClient. The DNS client relatively simple, and it will be left up to you to read the documentation and use it.
Next Steps:
Let’s jump back into vertx-web
again a little deeper… One interesting aspect of the Router
and RoutingContext
is
that routes can be chained. What this means is that if you have a path which starts with /rest
, and all routes
under that path will all do some of the same tasks, you can extract those operations into an earlier route which then
calls RoutingContext.next()
and the request will be processed by other routes which might match. Here’s an example:
link:Exercise14/Exercise14.java[role=include]
In this, admittedly contrived, example; we see that any request which matches '/rest/customer/:id' will match all of the
previous routes as well. Since the handlers for each of those routes are calling RoutingContext.next()
on the
RoutingContext object, ALL of these handlers will be applied in order!
Next Steps: * Do some research in the Vert.x documentation, and determine how to add a catch-all route which will handle any previously unhandled requests by sending a custom JSON 404 response * Create a catch-all route which will instead serve up static filesystem resources
Now that we have a basic understanding of event based programming in Vert.x, let’s learn how we can test our code when operating in an asynchronous world. In this example we will have a single Verticle which sets itself up to listen on the event bus. And using Spock Framework we will add a behavior test in order to make sure that our Verticle functions as expected. In order to facilitate running the tests, we have a Maven project POM to describe the build environment and handle dependencies. The Maven POM is already configured with the required dependencies for running tests using Spock Framework and the required Groovy libraries to make it all work.
link:Exercise15/src/main/java/com/redhat/labs/vertx/exercise15/Main.java[role=include]
link:Exercise15/src/test/groovy/com/redhat/labs/vertx/exercise15/MainSpec.groovy[role=include]
-
Spock uses Gherkin style syntax in the form of given, when, then
-
When testing asynchronous code, Spock gives you an AsyncConditions class which can be used to coordinate. The number passed indicates the number of async evaluation blocks which will need to be processed
-
This is an async evaluation block. In this case it checks to ensure that the Verticle is successfully deployed. Any assertions in this block must succeed or the test will fail
-
The when block is where we place the code to be tested
-
Another async evaluation block, this time checking to ensure that the reply message is correct
-
The then block is where we tell the system to check our work
-
The
async.await(10)
call tells Spock to wait for 10 seconds for the 2 async conditions to complete, and if it times out the test fails.