pom.xml
file to add the following dependency:<dependency> <groupId>io.vertxgroupId> <artifactId>vertx-webartifactId> <version>3.0.0version> dependency>
io.vertx.blog.first.MyFirstVerticle
class and change the start
method to be:@Override public void start(Future fut) { // Create a router object. Router router = Router.router(vertx); // Bind "/" to our hello message - so we are still compatible. router.route("/").handler(routingContext -> { HttpServerResponse response = routingContext.response(); response .putHeader("content-type", "text/html") .end("Hello from my first Vert.x 3 application"); }); // Create the HTTP server and pass the "accept" method to the request handler. vertx .createHttpServer() .requestHandler(router::accept) .listen( // Retrieve the port from the configuration, // default to 8080. config().getInteger("http.port", 8080), result -> { if (result.succeeded()) { fut.complete(); } else { fut.fail(result.cause()); } } ); }
Router
object. The router is the cornerstone of Vert.x Web. This object is responsible for dispatching the HTTP requests to the right handler. Two other concepts are very important in Vert.x Web:router.route("/").handler(routingContext -> { HttpServerResponse response = routingContext.response(); response .putHeader("content-type", "text/html") .end("Hello from my first Vert.x 3 application"); });
RoutingContext
object. This handler is quite similar to the code we had before, and it’s quite normal as it manipulates the same type of object: HttpServerResponse
.vertx .createHttpServer() .requestHandler(router::accept) .listen( // Retrieve the port from the configuration, // default to 8080. config().getInteger("http.port", 8080), result -> { if (result.succeeded()) { fut.complete(); } else { fut.fail(result.cause()); } } ); }
router::accept
to the handler. You may not be familiar with this notation. It’s a reference to a method (here the method accept
from the router
object). In other worlds, it instructs vert.x to call the accept
method of the router
when it receives a request.mvn clean package java -jar target/my-first-app-1.0-SNAPSHOT-fat.jar
http://localhost:8080
in your browser you should see the Hello message. As we didn’t change the behavior of the application, our tests are still valid.index.html
page. Before we go further, I should start with a disclaimer: “the HTML page we are going to see here is ugly like hell : I’m not a UI guy”. I should also add that there are probably plenty of better ways to implement this and a myriad of frameworks I should try, but that’s not the point. I tried to keep things simple and just relying on JQuery and Twitter Bootstrap, so if you know a bit of JavaScript you can understand and edit the page.index.html
page in src/main/resources/assets
with the content from here. As it’s just a HTML page with a bit of JavaScript, we won’t detail the file here. If you have questions, just post comments.io.vertx.blog.first.MyFirstVerticle
class and change the start
method to be:@Override public void start(Future fut) { Router router = Router.router(vertx); router.route("/").handler(routingContext -> { HttpServerResponse response = routingContext.response(); response .putHeader("content-type", "text/html") .end("Hello from my first Vert.x 3 application"); }); // Serve static resources from the /assets directory router.route("/assets/*").handler(StaticHandler.create("assets")); vertx .createHttpServer() .requestHandler(router::accept) .listen( // Retrieve the port from the configuration, // default to 8080. config().getInteger("http.port", 8080), result -> { if (result.succeeded()) { fut.complete(); } else { fut.fail(result.cause()); } } ); }
router.route("/assets/*").handler(StaticHandler.create("assets"));
line. So, what does this line mean? It’s actually quite simple. It routes requests on “/assets/*” to resources stored in the “assets” directory. So our index.html
page is going to be served using http://localhost:8080/assets/index.html
.create
method.mvn clean package java -jar target/my-first-app-1.0-SNAPSHOT-fat.jar
http://localhost:8080/assets/index.html
. Here it is… Ugly right? I told you.GET /api/whiskies
=> get all bottles (getAll
)GET /api/whiskies/:id
=> get the bottle with the corresponding id (getOne
)POST /api/whiskies
=> add a new bottle (addOne
)PUT /api/whiskies/:id
=> update a bottle (updateOne
)DELETE /api/whiskies/id
=> delete a bottle (deleteOne
)src/main/java/io/vertx/blog/first/Whisky.java
with the following content:package io.vertx.blog.first; import java.util.concurrent.atomic.AtomicInteger; public class Whisky { private static final AtomicInteger COUNTER = new AtomicInteger(); private final int id; private String name; private String origin; public Whisky(String name, String origin) { this.id = COUNTER.getAndIncrement(); this.name = name; this.origin = origin; } public Whisky() { this.id = COUNTER.getAndIncrement(); } public String getName() { return name; } public String getOrigin() { return origin; } public int getId() { return id; } public void setName(String name) { this.name = name; } public void setOrigin(String origin) { this.origin = origin; } }
MyFirstVerticle
class, add the following code:// Store our product private Map products = new LinkedHashMap<>(); // Create some product private void createSomeData() { Whisky bowmore = new Whisky("Bowmore 15 Years Laimrig", "Scotland, Islay"); products.put(bowmore.getId(), bowmore); Whisky talisker = new Whisky("Talisker 57° North", "Scotland, Island"); products.put(talisker.getId(), talisker); }
start
method, call the createSomeData
method:@Override public void start(Future fut) { createSomeData(); // Create a router object. Router router = Router.router(vertx); // Rest of the method }
GET /api/whiskies
. It returns the list of bottles in a JSON Array.start
method, add this line just below the static handler line:router.get("/api/whiskies").handler(this::getAll);
router
to handle the GET
requests on “/api/whiskies” by calling the getAll
method. We could have inlined the handler code, but for clarity reasons let’s create another method:private void getAll(RoutingContext routingContext) { routingContext.response() .putHeader("content-type", "application/json; charset=utf-8") .end(Json.encodePrettily(products.values())); }
RoutingContext
. It populates the response
by setting the content-type
and the actual content. Because our content may contain weird characters, we force the charset to UTF-8. To create the actual content, no need to compute the JSON string ourself. Vert.x lets us use the Json
API. So Json.encodePrettily(products.values())
computes the JSON string representing the set of bottles.Json.encodePrettily(products)
, but to make the JavaScript code simpler, we just return the set of bottles and not an object containing ID => Bottle
entries.mvn clean package java -jar target/my-first-app-1.0-SNAPSHOT-fat.jar
http://localhost:8080/assets/index.html
), and should should see:http://localhost:8080/api/whiskies
. You should get:[ { "id" : 0, "name" : "Bowmore 15 Years Laimrig", "origin" : "Scotland, Islay" }, { "id" : 1, "name" : "Talisker 57° North", "origin" : "Scotland, Island" } ]
Create a product start
method, add these lines just below the line ending by getAll
:router.route("/api/whiskies*").handler(BodyHandler.create()); router.post("/api/whiskies").handler(this::addOne);
router.route().handler(BodyHandler.create())
.POST
requests on /api/whiskies
to the addOne
method. Let’s create this method:private void addOne(RoutingContext routingContext) { final Whisky whisky = Json.decodeValue(routingContext.getBodyAsString(), Whisky.class); products.put(whisky.getId(), whisky); routingContext.response() .setStatusCode(201) .putHeader("content-type", "application/json; charset=utf-8") .end(Json.encodePrettily(whisky)); }
Whisky
object from the request body. It just reads the body into a String and passes it to the Json.decodeValue
method. Once created it adds it to the backend map and returns the created bottle as JSON.mvn clean package java -jar target/my-first-app-1.0-SNAPSHOT-fat.jar
Add a new bottle
button. Enter the data such as: “Jameson” as name and “Ireland” as origin (purists would have noticed that this is actually a Whiskey and not a Whisky). The bottle should be added to the table.Status 201 ? As you can see, we have set the response status to 201 . It means CREATED , and is the generally used in REST API that create an entity. By default vert.x web is setting the status to 200 meaning OK . |
start
method, add this line:router.delete("/api/whiskies/:id").handler(this::deleteOne);
:id
. So, when handling a matching request, Vert.x extracts the path segment corresponding to the parameter and let us access it in the handler method. For instance, /api/whiskies/0
maps id
to 0
.deleteOne
method as follows:private void deleteOne(RoutingContext routingContext) { String id = routingContext.request().getParam("id"); if (id == null) { routingContext.response().setStatusCode(400).end(); } else { Integer idAsInteger = Integer.valueOf(id); products.remove(idAsInteger); } routingContext.response().setStatusCode(204).end(); }
routingContext.request().getParam("id")
. It checks whether it’s null
(not set), and in this case returns a Bad Request
response (status code 400). Otherwise, it removes it from the backend map.Status 204 ? As you can see, we have set the response status to 204 - NO CONTENT . Response to the HTTP Verb delete have generally no content. |
getOne
and updateOne
as the implementations are straightforward and very similar. Their implementations are available on GitHub.