1. 程式人生 > >Vert.x(四): Vert.x 實現REST

Vert.x(四): Vert.x 實現REST

回顧

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

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

接下來,能看到:

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

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

開始Vert.x Web

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

更新pom.xml檔案,新增下面的依賴:

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

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

還記得在上一篇文章中,當請求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中還有兩個很重要的概念。

  • 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,如何訪問靜態資源。比以前的文章複雜些,但仍然還是很簡單。

全科龍婷▼升職加薪