Vert.x 實現REST

回顧

在第一篇文章中開發了一個很是簡單的Vert.x 3應用程序,還包括怎麼測試、打包和執行。在第二篇文章中對端口進行了可變配置。html

這篇文章中,開發一個CRUD(增刪改查)應用,發佈一個HTML頁面,經過REST API與後臺進行交互。RESTfull形式的API不簡單,這篇文章中就不涉及了。java

接下來,能看到:git

  • Vert.x Web - 使用Vert.x建立Web應用的框架
  • 怎麼發佈靜態資源
  • 怎麼開發REST API

這篇文章開發的代碼放在GitHub上,是從第二篇文章的代碼基礎上進行的。github

開始Vert.x Web

若是你看了前面的文章,使用Vert.x Core來處理複雜的HTTP應用仍是很麻煩的,因此就有了Vert.x Web,它可使Vert.x開發一個web應用更加簡單,並且不會改變Vert.x的思想。web

更新pom.xml文件,添加下面的依賴:數據庫

<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-web</artifactId>
  <version>3.0.0</version>
</dependency>

這就是使用Vert.x Web的惟一前提。json

還記得在上一篇文章中,當請求http://localhost:8080時,返回一個Hello World消息,使用Vert.x Web完成一樣的事情,打開name.quanke.study.vertx.first.MyFirstVerticle.java 類,修改start方法:後端

@Override
public void start(Future<Void> 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("<h1>Hello from my first Vert.x 3 application</h1>");
 });

 // 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());
           }
         }
     );
}

在開始start方法裏建立了一個Router對象。router是Vert.x Web的基礎,負責分發HTTP請求到handler(處理器),在Vert.x Web中還有兩個很重要的概念。api

  • Routes-定義請求的分發
  • Handlers-這是實際處理請求而且返回結果的地方。Handlers能夠被連接起來使用。

若是明白了這3個概念(Router、Routes、Handlers),就明白了Vert.x Web的全部了。數組

仔細看看下面這段代碼:

router.route("/").handler(routingContext -> {
  HttpServerResponse response = routingContext.response();
  response
      .putHeader("content-type", "text/html")
      .end("<h1>Hello from my first Vert.x 3 application</h1>");
});

將訪問"/"(http://localhost:8080/)的請求「路由」到指定的handler。Handlers接收RoutingContext對象。這個handler的方法和咱們以前的代碼很像,他們操做的是同一個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());
          }
        }
    );
}

除了改變了request handler,基本和以前的代碼同樣。傳router::accept給handler。你可能對這個符號不太熟悉。它表示引用一個方法(這裏是引用routeraccept方法)。換句話說,當接收到一個請求的時候,告訴vert.x從router裏調用accept方法。

讓咱們來看下它是怎麼工做的:

mvn clean package
java -jar target/my-first-app-1.0-SNAPSHOT-fat.jar

瀏覽器打開http://localhost:8080,你會看到Hello的消息。

發佈靜態資源

如今有了第一個使用Vert.x Web開發的應用。先在寫增長一個index.html頁面(靜態資源)。

這個HTML頁面將會是咱們應用的入口。在src/main/resources/assets目錄下,index.html文件在github上。此文不涉及這個文件的細節。

基本上,就是一個簡單的CRUD的UI界面,actions是由經過AJAX調用的REST API執行的。

建立完了頁面後,編輯name.quanke.study.vertx.first.MyFirstVerticle類,並修改start方法:

@Override
public void start(Future<Void> fut) {
 Router router = Router.router(vertx);
 router.route("/").handler(routingContext -> {
   HttpServerResponse response = routingContext.response();
   response
       .putHeader("content-type", "text/html")
       .end("<h1>Hello from my first Vert.x 3 application</h1>");
 });

 // Serve static resources from the /assets directory
 // 將訪問「/assets/*」的請求route到「assets」目錄下的資源
 
 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"));

這一行是什麼意思?挺簡單的。將訪問「/assets/*」的請求route到「assets」目錄下的資源。如今能夠經過http://localhost:8080/assets/index.html來訪問index.html了。

測試以前,咱們花一些時間來看一下handler的建立。全部的處理請求動做在Vert.x Web裏都實現成handler。而建立一個handler須要調用create方法。

編譯、運行:

mvn clean package
java -jar target/my-first-app-1.0-SNAPSHOT-fat.jar

瀏覽器,輸入http://localhost:8080/assets/index.html

如今這個table是空的。那是由於咱們尚未實現REST的API。如今讓咱們來開始吧。

使用Vert.x Web實現REST API

Vert.x Web實現REST API很簡單。看下面:

  • GET /api/whiskies => 獲取全部的威士忌(getAll)
  • GET /api/whiskies/:id => 獲取指定id的威士忌(getOne)
  • POST /api/whiskies =>添加一瓶威士忌(addOne)
  • PUT /api/whiskies/:id => 編輯一瓶威士忌(updateOne)
  • DELETE /api/whiskies/id => 刪除一瓶威士忌(deleteOne)
咱們須要一些數據。。。

在實現REST API以前,須要建立Whisky的數據模型。使用下面的內容建立src/main/java/quanke/name/study/vertx/first/Whisky.java

package name.quanke.study.vertx.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;
  }
}

這是一個很簡單的bean類。由於Vert.x依賴Jackson來處理JSON格式,Jackson可以自動序列化和反序列化bean類,讓代碼變得更簡單,因此選擇這樣的格式。

如今,建立幾瓶威士忌。在MyFirstVerticle類中,添加下面的代碼:

// Store our product
// 存儲產品
private Map<Integer, Whisky> 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方法裏,調用createSomeData方法:

@Override
public void start(Future<Void> fut) {

  createSomeData();

  // Create a router object.
  Router router = Router.router(vertx);

  // Rest of the method
}

在這裏並無一個後臺數據庫。僅使用一個map,將數據存儲在內存中。添加後端數據庫的介紹我準備放在另外一篇文章中講。

得到產品(威士忌)

GET /api/whiskies,JSON數組中返回產品列表。

start方法裏,添加下面這行(static handler):

router.get("/api/whiskies").handler(this::getAll);

告訴router調用getAll方法來處理"/api/whiskies"的GET請求。代碼能夠寫在handler裏,可是爲了讓代碼更加清晰,另外建立一個方法:

private void getAll(RoutingContext routingContext) {
  routingContext.response()
      .putHeader("content-type", "application/json; charset=utf-8")
      .end(Json.encodePrettily(products.values()));
}

每個handler(好比:請看上面的代碼)都會接受一個RoutingContext參數。經過設置content-type和一些內容來填充response。由於內容可能會碰到特殊的字符,因此強制使用UTF-8的格式。建立內容的時候,並不須要本身去處理JSON格式的字符串。Vert.x有處理Json的API。使用Json.encodePrettily(products.values())處理JSON字符串。本應使用Json.encodePrettily(products),可是爲了讓JavaScript代碼更簡單,咱們僅返回威士忌(產品)的數據集合,並無返回包含Id=>Bottle的鍵值對。

打包運行:

mvn clean package
java -jar target/my-first-app-1.0-SNAPSHOT-fat.jar

瀏覽器訪問http://localhost:8080/assets/index.html,而後你將會看到下面這個頁面。

image

很好奇,想看一下REST API到底返回了什麼。打開瀏覽器,訪問http://localhost:8080/api/whiskies。你會看到下面這樣的信息:

[ {
  "id" : 0,
  "name" : "Bowmore 15 Years Laimrig",
  "origin" : "Scotland, Islay"
}, {
  "id" : 1,
  "name" : "Talisker 57° North",
  "origin" : "Scotland, Island"
} ]

建立一個產品

能獲取到威士忌(產品)了,如今須要建立一個產品。不像以前的REST API,這一次,須要讀取requestbody。由於性能的緣由,它應該被顯式地啓用。不要怕,這也僅僅是一個handler而已。

start方法中,添加下面的內容到getAll的後面:

router.route("/api/whiskies*").handler(BodyHandler.create());
router.post("/api/whiskies").handler(this::addOne);

第一行容許"/api/whiskies"下的全部route讀取請求的body。經過使用router.route().handler(BodyHandler.create()),能讓它在全局生效。

第二行將對/api/whiskies的POST請求映射到addOne方法。讓咱們來建立這個方法:

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));
}

開始從請求的body中取出Whisky對象。只是將body讀成一個字符串並將它傳入到Json.decodeValue方法裏。Whisky這個對象一旦建立好,將被添加到後臺的map中,並以JSON的格式返回。

從新編譯而且運行:

mvn clean package
java -jar target/my-first-app-1.0-SNAPSHOT-fat.jar

刷新HTML頁面,點擊Add a new bottle按鈕。輸入數據,如:「quanke」 做爲名字, 「quanke.name」 做爲產地 ,就OK了。

狀態碼 201 ? CREATED和在REST API中建立一個entity時,response的狀態碼爲201。。默認的vert.x web設置一個200的狀態碼錶明OK。

刪除一個產品

start方法裏,添加:

router.delete("/api/whiskies/:id").handler(this::deleteOne);

URL裏,參數爲::id。在處理一個相匹配的請求的時候,Vert.x提取路徑中與這個參數對應的一段,可以在handler中得到。例如,/api/whiskies/0id映射爲0

看一下在handler方法中這個參數是怎樣被使用的。建立一個deleteOne方法。

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();
}

狀態碼 204 ? 狀態碼爲204 - NO CONTENT。HTTP delete動做一般都是無返回內容的。

其餘方法

實現getOne和updateOne很簡單,和上面的差很少,此文再也不詳細介紹。源碼在github

總結

此文介紹瞭如何用Vert.x web輕鬆的實現一個REST API,如何訪問靜態資源。比之前的文章複雜些,但仍然仍是很簡單。

全科龍婷

相關文章
相關標籤/搜索