Vert.x(四): Vert.x 實現REST
- 歡迎關注http://quanke.name/
- 交流群:
231419585
- 轉載請註明出處,謝謝
回顧
在第一篇文章中開發了一個非常簡單的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。你可能對這個符號不太熟悉。它表示引用一個方法(這裡是引用router
的accept
方法)。換句話說,當接收到一個請求的時候,告訴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
,然後你將會看到下面這個頁面。
很好奇,想看一下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,這一次,需要讀取request
的body
。因為效能的原因,它應該被顯式地啟用。不要怕,這也僅僅是一個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/0
將id
對映為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,如何訪問靜態資源。比以前的文章複雜些,但仍然還是很簡單。
全科龍婷▼升職加薪