Vert.x Web

https://vertx.io/docs/vertx-web/java/javascript

Vert.x-Web是一組用於使用Vert.x構建Web應用程序的構建塊。將其視爲瑞士軍刀,用於構建現代,可擴展的網絡應用程序。css

Vert.x核心爲處理HTTP提供了至關低級別的功能,對於某些應用程序來講已經足夠了。html

Vert.x-Web構建於Vert.x核心之上,能夠更輕鬆地爲構建真實Web應用程序提供更豐富的功能。java

它是Vert.x 2.x中Yoke的繼承者,並從Node.js世界中的ExpressRuby世界中的Sinatra等項目中得到靈感git

Vert.x-Web旨在實現強大,無需激活和徹底嵌入。您只需使用您想要的部件,僅此而已。Vert.x-Web不是容器。github

您可使用Vert.x-Web建立經典的服務器端Web應用程序,RESTful Web應用程序,「實時」(服務器推送)Web應用程序或您能想到的任何其餘類型的Web應用程序。Vert.x-Web並不關心。您能夠選擇本身喜歡的應用程序類型,而不是Vert.x-Web。web

Vert.x-Web很是適合編寫有效的HTTP微服務*,但咱們不強迫你編寫相似這樣的應用程序。ajax

Vert.x-Web的一些主要功能包括:正則表達式

  • 路由(基於方法,路徑等)算法

  • 路徑的正則表達式模式匹配

  • 從路徑中提取參數

  • 內容協商

  • 請求身體處理

  • 體型限制

  • Cookie解析和處理

  • 多部分表格

  • 多部分文件上傳

  • 子路由器

  • 會話支持 - 本地(針對粘性會話)和羣集(針對非粘性)

  • CORS(跨源資源共享)支持

  • 錯誤頁面處理程序

  • 基本認證

  • 基於重定向的身份驗證

  • 受權處理程序

  • 基於JWT的受權

  • 用戶/角色/權限受權

  • Favicon處理

  • 服務器端呈現的模板支持,包括對開箱即用的如下模板引擎的支持:

    • 把手

    • 玉,

    • MVEL

    • Thymeleaf

    • Apache FreeMarker

    • 卵石

    • 搖臂

  • 響應時間處理程序

  • 靜態文件服務,包括緩存邏輯和目錄列表。

  • 請求超時支持

  • SockJS支持

  • 事件總線橋

  • CSRF跨站請求僞造

  • 虛擬主機

Vert.x-Web中的大多數功能都是做爲處理程序實現的,所以您能夠隨時編寫本身的功能。咱們設想隨着時間的推移寫出更多。

咱們將在本手冊中討論全部這些功能。

使用Vert.x Web

要使用vert.x web,請將如下依賴項添加到構建描述符dependencies部分:

  • Maven(在你的pom.xml):

<dependency> <groupId>io.vertx</groupId> <artifactId>vertx-web</artifactId> <version>3.8.0</version> </dependency>
  • Gradle(在您的build.gradle文件中):

dependencies {
 compile 'io.vertx:vertx-web:3.8.0' }

發展模式

Vert.x Web默認在生產模式下運行。您能夠經過將dev分配給如下一項來切換開發模式

  • VERTXWEB_ENVIRONMENT環境變量中,或

  • vertxweb.environment系統屬性

在開發模式中:

  • 模板引擎緩存已禁用

  • ErrorHandler不顯示異常詳細信息

  • StaticHandler不處理緩存頭

  • GraphiQL開發工具已禁用

從新調整Vert.x核心HTTP服務器的上限

Vert.x-Web使用並公開來自Vert.x核心的API,因此若是你尚未熟悉使用Vert.x核心編寫HTTP服務器的基本概念,那麼這是很是值得的。

Vert.x核心HTTP文檔詳細介紹了這一點。

這是使用Vert.x核心編寫的hello world Web服務器。此時沒有涉及Vert.x-Web:

HttpServer server = vertx.createHttpServer(); server.requestHandler(request -> { // This handler gets called for each request that arrives on the server HttpServerResponse response = request.response(); response.putHeader("content-type", "text/plain"); // Write to the response and end it response.end("Hello World!"); }); server.listen(8080);

咱們建立一個HTTP服務器實例,並在其上設置請求處理程序。只要請求到達服務器,就會調用請求處理程序。

當發生這種狀況時,咱們只是將內容類型設置爲text/plain,並編寫Hello World!和結束響應。

而後咱們告訴服務器在端口監聽8080(默認主機是localhost)。

您能夠運行此命令,並將瀏覽器指向http://localhost:8080以驗證它是否按預期工做。

基本的Vert.x-Web概念

這是10000英尺的視圖:

Router是Vert.x-Web的核心概念之一。它是一個維持零或更多的對象 Routes

路由器接收HTTP請求並找到該請求的第一個匹配路由,並將請求傳遞給該路由。

路由能夠有一個與之關聯處理程序,而後接收請求。而後對請求執行某些操做,而後結束它或將其傳遞給下一個匹配的處理程序。

這是一個簡單的路由器示例:

HttpServer server = vertx.createHttpServer(); Router router = Router.router(vertx); router.route().handler(routingContext -> { // This handler will be called for every request HttpServerResponse response = routingContext.response(); response.putHeader("content-type", "text/plain"); // Write to the response and end it response.end("Hello World from Vert.x-Web!"); }); server.requestHandler(router).listen(8080);

它基本上與上一節中的Vert.x Core HTTP服務器hello world示例相同,但此次使用的是Vert.x-Web。

咱們像之前同樣建立HTTP服務器,而後建立路由器。完成後,咱們建立一個沒有匹配條件的簡單路由,以便匹配到達服務器的全部請求。

而後,咱們爲該路由指定處理程序。將爲全部到達服務器的請求調用該處理程序。

傳遞給處理程序的對象是RoutingContext- 它包含標準的Vert.x HttpServerRequest以及HttpServerResponse 其餘各類有用的東西,這使得使用Vert.x-Web變得更簡單。

對於路由的每一個請求,都有一個惟一的路由上下文實例,而且相同的實例將傳遞給該請求的全部處理程序。

一旦咱們設置了處理程序,咱們就設置HTTP服務器的請求處理程序以將全部傳入的請求傳遞給handle

因此,這是基礎知識。如今咱們將更詳細地研究一下:

處理請求並調用下一個處理程序

當Vert.x-Web決定將請求路由到匹配的路由時,它會調用在實例中傳遞的路由的處理程序RoutingContext路徑能夠有不一樣的處理程序,您可使用它們追加 handler

若是你沒有在你的處理程序中結束響應,你應該調用,next因此另外一個匹配的路由能夠處理請求(若是有的話)。

next在處理程序執行完以前,您沒必要調用若是你願意,你能夠在之後作一段時間:

Route route = router.route("/some/path/"); route.handler(routingContext -> { HttpServerResponse response = routingContext.response(); // enable chunked responses because we will be adding data as // we execute over other handlers. This is only required once and // only if several handlers do output. response.setChunked(true); response.write("route1\n"); // Call the next matching route after a 5 second delay routingContext.vertx().setTimer(5000, tid -> routingContext.next()); }); route.handler(routingContext -> { HttpServerResponse response = routingContext.response(); response.write("route2\n"); // Call the next matching route after a 5 second delay routingContext.vertx().setTimer(5000, tid -> routingContext.next()); }); route.handler(routingContext -> { HttpServerResponse response = routingContext.response(); response.write("route3"); // Now end the response routingContext.response().end(); });

在上面的例子route1寫入響應,而後5秒後route2寫入響應,而後5秒後route3寫入響應,響應結束。

注意,全部這些都沒有任何線程阻塞。

使用阻塞處理程序

有時,您可能必須在可能阻塞事件循環一段時間的處理程序中執行某些操做,例如調用傳統阻塞API或進行一些密集計算。

你不能在普通的處理程序中這樣作,因此咱們提供了在路由上設置阻塞處理程序的能力。

阻塞處理程序看起來就像一個普通的處理程序,但Vert.x使用來自工做池的線程調用它而不使用事件循環。

您在路由上設置阻止處理程序blockingHandler這是一個例子:

router.route().blockingHandler(routingContext -> {

  // Do something that might take some time synchronously service.doSomethingThatBlocks(); // Now call the next handler routingContext.next(); });

默認狀況下,在同一個上下文(例如同一個Verticle實例)上執行的任何阻塞處理程序都是有序的 - 這意味着下一個阻塞處理程序將在前一個完成以前執行。若是您不關心orderering而且不介意並行執行阻塞處理程序,則能夠將阻止處理程序設置ordered爲false using blockingHandler

注意,若是您須要處理阻塞處理程序中的多部分表單數據,則必須使用非阻塞處理程序FIRST才能調用setExpectMultipart(true)這是一個例子:

router.post("/some/endpoint").handler(ctx -> { ctx.request().setExpectMultipart(true); ctx.next(); }).blockingHandler(ctx -> { // ... Do some blocking operation });

按確切路徑路由

能夠設置路由以匹配來自請求URI的路徑。在這種狀況下,它將匹配任何具備與指定路徑相同的路徑的請求。

在如下示例中,將爲請求調用處理程序/some/path/咱們也忽略尾隨斜線因此它會被調用路徑/some/path/some/path//太:

Route route = router.route().path("/some/path/"); route.handler(routingContext -> { // This handler will be called for the following request paths: // `/some/path` // `/some/path/` // `/some/path//` // // but not: // `/some/path/subdir` });

經過以某事開頭的路徑進行路由

一般,您但願路由以特定路徑開頭的全部請求。您可使用正則表達式來執行此操做,但一種簡單的方法是*在聲明路徑路徑時在路徑末尾使用星號

在如下示例中,將爲具備以...開頭的URI路徑的任何請求調用處理程序 /some/path/

例如/some/path/foo.html/some/path/otherdir/blah.css二者都匹配。

Route route = router.route().path("/some/path/*"); route.handler(routingContext -> { // This handler will be called for any path that starts with // `/some/path/`, e.g. // `/some/path` // `/some/path/` // `/some/path/subdir` // `/some/path/subdir/blah.html` // // but not: // `/some/bath` });

使用任何路徑時,也能夠在建立路徑時指定:

Route route = router.route("/some/path/*"); route.handler(routingContext -> { // This handler will be called same as previous example });

捕獲路徑參數

可使用佔位符匹配路徑,以獲取請求中可用的參數 params

這是一個例子

Route route = router.route(HttpMethod.POST, "/catalogue/products/:producttype/:productid/"); route.handler(routingContext -> { String productType = routingContext.request().getParam("producttype"); String productID = routingContext.request().getParam("productid"); // Do something with them... });

佔位符:後跟參數名稱。參數名稱由任何字母字符,數字字符或下劃線組成。

在上面的示例中,若是對路徑發出POST請求:/catalogue/products/tools/drill123/那麼路由將匹配productType並將接收值tools,productID將接收該值drill123

使用正則表達式路由

正則表達式也可用於匹配路由中的URI路徑。

Route route = router.route().pathRegex(".*foo"); route.handler(routingContext -> { // This handler will be called for: // /some/path/foo // /foo // /foo/bar/wibble/foo // /bar/foo // But not: // /bar/wibble });

或者,能夠在建立路徑時指定正則表達式:

Route route = router.routeWithRegex(".*foo"); route.handler(routingContext -> { // This handler will be called same as previous example });

使用正則表達式捕獲路徑參數

您還能夠在使用正則表達式時捕獲路徑參數,這是一個示例:

Route route = router.routeWithRegex(".*foo"); // This regular expression matches paths that start with something like: // "/foo/bar" - where the "foo" is captured into param0 and the "bar" is captured into // param1 route.pathRegex("\\/([^\\/]+)\\/([^\\/]+)").handler(routingContext -> { String productType = routingContext.request().getParam("param0"); String productID = routingContext.request().getParam("param1"); // Do something with them... });

在上面的示例中,若是請求路徑:/tools/drill123/那麼路由將匹配productType並將接收值tools,productID將接收該值drill123

捕獲以帶有捕獲組的正則表達式表示(即用圓括號圍繞捕獲)

使用命名捕獲組

在某些狀況下,使用int index param名稱可能會很麻煩。能夠在正則表達式路徑中使用命名捕獲組。

Route route = router.routeWithRegex("\\/(?<productType>[^\\/]+)\\/(?<productId>[^\\/]+)").handler(routingContext -> { String productType = routingContext.request().getParam("productType"); String productID = routingContext.request().getParam("productId"); // Do something with them... });

在上面的示例中,命名捕獲組映射到與組同名的路徑參數。

此外,您仍然能夠像使用普通組同樣訪問組參數(即params0, params1…​

經過HTTP方法路由

默認狀況下,路由將匹配全部HTTP方法。

若是您但願路由僅匹配特定HTTP方法,則可使用 method

Route route = router.route().method(HttpMethod.POST); route.handler(routingContext -> { // This handler will be called for any POST request });

或者,您能夠在建立路徑時使用路徑指定:

Route route = router.route(HttpMethod.POST, "/some/path/"); route.handler(routingContext -> { // This handler will be called for any POST request to a URI path starting with /some/path/ });

若是要爲特定HTTP方法路由,還可使用諸如的方法get, postput以HTTP方法名稱命名。例如:

router.get().handler(routingContext -> {

  // Will be called for any GET request }); router.get("/some/path/").handler(routingContext -> { // Will be called for any GET request to a path // starting with /some/path }); router.getWithRegex(".*foo").handler(routingContext -> { // Will be called for any GET request to a path // ending with `foo` });

若是要指定的路由將匹配多於HTTP方法,則能夠method 屢次調用

Route route = router.route().method(HttpMethod.POST).method(HttpMethod.PUT); route.handler(routingContext -> { // This handler will be called for any POST or PUT request });

路線順序

默認狀況下,路由按照添加到路由器的順序進行匹配。

當請求到達時,路由器將逐步執行每一個路由並檢查它是否匹配,若是匹配則將調用該路由的處理程序。

若是處理程序隨後調用next處理程序以便調用下一個匹配的路由(若是有的話)。等等。

這是一個例子來講明這一點:

Route route1 = router.route("/some/path/").handler(routingContext -> { HttpServerResponse response = routingContext.response(); // enable chunked responses because we will be adding data as // we execute over other handlers. This is only required once and // only if several handlers do output. response.setChunked(true); response.write("route1\n"); // Now call the next matching route routingContext.next(); }); Route route2 = router.route("/some/path/").handler(routingContext -> { HttpServerResponse response = routingContext.response(); response.write("route2\n"); // Now call the next matching route routingContext.next(); }); Route route3 = router.route("/some/path/").handler(routingContext -> { HttpServerResponse response = routingContext.response(); response.write("route3"); // Now end the response routingContext.response().end(); });

在上面的示例中,響應將包含:

ROUTE1
路徑2
路徑3

由於路由已按此順序調用任何以/some/path開頭的請求

若是要覆蓋路由的默認排序,可使用order指定整數值。

在建立時爲路由分配一個順序,該順序對應於它們被添加到路由器的順序,第一個路由編號0,第二個路由編號1,依此類推。

經過指定路徑的順序,您能夠覆蓋默認順序。訂單也能夠是否認的,例如,若是您想確保在路線編號以前評估路線0

讓咱們改變route2的順序,使它在route1以前運行:

Route route1 = router.route("/some/path/").order(1).handler(routingContext -> { HttpServerResponse response = routingContext.response(); response.write("route1\n"); // Now call the next matching route routingContext.next(); }); Route route2 = router.route("/some/path/").order(0).handler(routingContext -> { HttpServerResponse response = routingContext.response(); // enable chunked responses because we will be adding data as // we execute over other handlers. This is only required once and // only if several handlers do output. response.setChunked(true); response.write("route2\n"); // Now call the next matching route routingContext.next(); }); Route route3 = router.route("/some/path/").order(2).handler(routingContext -> { HttpServerResponse response = routingContext.response(); response.write("route3"); // Now end the response routingContext.response().end(); });

那麼響應如今將包含:

路徑2
ROUTE1
路徑3

若是兩個匹配的路由具備相同的訂單值,則將按添加的順序調用它們。

您還能夠指定最後處理路由 last

注意:只能在配置處理程序以前指定路徑順序!

基於MIME類型的請求進行路由

您可使用指定路由將匹配匹配的請求MIME類型consumes

在這種狀況下,請求將包含content-type指定請求正文的MIME類型的標頭。這將與指定的值匹配consumes

基本上,consumes是描述處理程序能夠使用的 MIME類型

匹配能夠在確切的MIME類型匹配上完成:

router.route().consumes("text/html").handler(routingContext -> { // This handler will be called for any request with // content-type header set to `text/html` });

還能夠指定多個徹底匹配:

router.route().consumes("text/html").consumes("text/plain").handler(routingContext -> { // This handler will be called for any request with // content-type header set to `text/html` or `text/plain`. });

支持在子類型的通配符上匹配:

router.route().consumes("text/*").handler(routingContext -> { // This handler will be called for any request with top level type `text` // e.g. content-type header set to `text/html` or `text/plain` will both match });

您也能夠匹配頂級類型

router.route().consumes("*/json").handler(routingContext -> { // This handler will be called for any request with sub-type json // e.g. content-type header set to `text/json` or `application/json` will both match });

若是您沒有/在消費者中指定a ,則會假定您指的是子類型。

基於客戶端可接受的MIME類型的路由

HTTP accept標頭用於表示響應的哪些MIME類型是客戶端可接受的。

一個accept報頭可具備由分隔的多個MIME類型「」。

MIME類型也能夠q附加一個值*,表示若是有多個響應MIME類型與accept頭匹配,則應用加權。q值是介於0和1.0之間的數字。若是省略,則默認爲1.0。

例如,如下accept標頭表示客戶端將僅接受MIME類型text/plain

接受:text / plain

如下客戶將接受text/plaintext/html接受

接受:text / plain,text / html

使用如下內容,客戶端將接受text/plaintext/html更喜歡,text/html由於它具備更高的 q值(默認值爲q = 1.0)

接受:text / plain; q = 0.9,text / html

若是服務器能夠提供text / plain和text / html,則在這種狀況下應該提供text / html。

經過使用produces您定義路由生成的MIME類型,例如,如下處理程序生成MIME類型的響應application/json

router.route().produces("application/json").handler(routingContext -> { HttpServerResponse response = routingContext.response(); response.putHeader("content-type", "application/json"); response.write(someJSON).end(); });

在這種狀況下,路由將匹配任何accept匹配標頭的請求application/json

如下是一些accept匹配標題示例

接受:application / json接受:application / * Accept:application / json,text / html Accept:application / json; q = 0.7,text / html; q = 0.8,text / plain

您還能夠將路由標記爲生成多個MIME類型。若是是這種狀況,那麼您將使用getAcceptableContentType查找已接受的實際MIME類型。

router.route().produces("application/json").produces("text/html").handler(routingContext -> { HttpServerResponse response = routingContext.response(); // Get the actual MIME type acceptable String acceptableContentType = routingContext.getAcceptableContentType(); response.putHeader("content-type", acceptableContentType); response.write(whatever).end(); });

在上面的示例中,若是您發送了帶有如下accept標頭的請求

接受:application / json; q = 0.7,text / html

而後路線將匹配而且acceptableContentType將包含,text/html由於二者都是可接受的但具備更高的q值。

結合路由標準

您能夠經過多種不一樣方式組合上述全部路由條件,例如:

Route route = router.route(HttpMethod.PUT, "myapi/orders") .consumes("application/json") .produces("application/json"); route.handler(routingContext -> { // This would be match for any PUT method to paths starting with "myapi/orders" with a // content-type of "application/json" // and an accept header matching "application/json" });

啓用和禁用路由

您可使用禁用路由disable匹配時將忽略禁用的路由。

您可使用從新啓用已禁用的路由 enable

上下文數據

您可使用中的上下文數據RoutingContext來維護要在請求的生命週期內在處理程序之間共享的任何數據。

這是一個示例,其中一個處理程序在上下文數據中設置一些數據,後續處理程序檢索它:

您可使用它put來放置任何對象,並 get從上下文數據中檢索任何對象。

發送到路徑的請求/some/path/other將匹配兩個路由。

router.get("/some/path").handler(routingContext -> { routingContext.put("foo", "bar"); routingContext.next(); }); router.get("/some/path/other").handler(routingContext -> { String bar = routingContext.get("foo"); // Do something with bar routingContext.response().end(); });

或者,您可使用訪問整個上下文數據映射data

從新路由

到目前爲止,全部路由機制都容許您以順序方式處理請求,但有時您可能但願返回。因爲上下文不公開有關上一個或下一個處理程序的任何信息,主要是由於此信息是動態的,所以有一種方法能夠從當前路由器的開頭從新啓動整個路由。

router.get("/some/path").handler(routingContext -> { routingContext.put("foo", "bar"); routingContext.next(); }); router.get("/some/path/B").handler(routingContext -> routingContext.response().end()); router.get("/some/path").handler(routingContext -> routingContext.reroute("/some/path/B"));

所以,從代碼中能夠看到,若是請求到達時/some/path若是首先向上下文添加值,則移動到下一個處理程序,該處理程序從新路由請求以/some/path/B終止請求。

您能夠根據新路徑或基於新路徑和方法從新路由。但請注意,基於方法的從新路由可能會引入安全問題,由於例如一般安全的GET請求可能會成爲DELETE。

在故障處理程序上也容許從新路由,可是因爲從新路由器的性質,當被調用時,當前狀態代碼和故障緣由被重置。爲了使從新路由處理程序在須要時生成正確的狀態代碼,例如:

router.get("/my-pretty-notfound-handler").handler(ctx -> ctx.response() .setStatusCode(404) .end("NOT FOUND fancy html here!!!")); router.get().failureHandler(ctx -> { if (ctx.statusCode() == 404) { ctx.reroute("/my-pretty-notfound-handler"); } else { ctx.next(); } });

應該清楚的是,從新路由工做paths,所以若是您須要在從新路由中保留和/或添加狀態,則應該使用該RoutingContext對象。例如,您想要使用額外參數從新路由到新路徑:

router.get("/final-target").handler(ctx -> { // continue from here... }); // THE WRONG WAY! (Will reroute to /final-target excluding the query string) router.get().handler(ctx -> ctx.reroute("/final-target?variable=value")); // THE CORRECT WAY! router.get().handler(ctx -> ctx .put("variable", "value") .reroute("/final-target"));

即便錯誤的從新路由路徑會警告您忽略查詢字符串,也會發生從新路由,由於實現將從路徑中刪除任何查詢字符串或html片斷。

子路由器

有時,若是你有不少處理程序,將它們分紅多個路由器是有意義的。若是要在不一樣的應用程序中重用一組處理程序(以不一樣的路徑根目錄爲根),這也頗有用。

爲此,您能夠將路由器安裝在另外一個路由器安裝點安裝的路由器稱爲 子路由器子路由器能夠安裝其餘子路由器,所以若是您願意,能夠擁有多個級別的子路由器。

讓咱們看一個安裝有另外一個路由器的子路由器的簡單示例。

該子路由器將維護與簡單的虛構REST API相對應的處理程序集。咱們將把它安裝在另外一臺路由器上。未顯示REST API的完整實現。

這是子路由器:

Router restAPI = Router.router(vertx); restAPI.get("/products/:productID").handler(rc -> { // TODO Handle the lookup of the product.... rc.response().write(productJSON); }); restAPI.put("/products/:productID").handler(rc -> { // TODO Add a new product... rc.response().end(); }); restAPI.delete("/products/:productID").handler(rc -> { // TODO delete the product... rc.response().end(); });

若是此路由器用做頂級路由器,則GET / PUT / DELETE請求對URL進行/products/product1234 調用。

可是,假設咱們已經擁有另外一個路由器所描述的網站:

Router mainRouter = Router.router(vertx); // Handle static resources mainRouter.route("/static/*").handler(myStaticHandler); mainRouter.route(".*\\.templ").handler(myTemplateHandler);

在這種狀況下,咱們如今能夠將子路由器安裝在主路由器上,而不是安裝點 /productsAPI

mainRouter.mountSubRouter("/productsAPI", restAPI);

這意味着如今能夠經過如下路徑訪問REST API: /productsAPI/products/product1234

本土化

Vert.x Web會解析Accept-Language標頭並提供一些幫助方法,以便按質量肯定哪一個是客戶端的首選區域設置或首選區域設置的排序列表。

Route route = router.get("/localized").handler(rc -> { // although it might seem strange by running a loop with a switch we // make sure that the locale order of preference is preserved when // replying in the users language. for (LanguageHeader language : rc.acceptableLanguages()) { switch (language.tag()) { case "en": rc.response().end("Hello!"); return; case "fr": rc.response().end("Bonjour!"); return; case "pt": rc.response().end("Olá!"); return; case "es": rc.response().end("Hola!"); return; } } // we do not know the user language so lets just inform that back: rc.response().end("Sorry we don't speak: " + rc.preferredLanguage()); });

main方法acceptableLocales將返回用戶理解的有序語言環境列表,若是您只對用戶首選語言環境感興趣,則幫助程序: preferredLocale將返回列表的第1個元素,或者null若是用戶未提供語言環境。

路線匹配失敗

若是沒有任何路由匹配任何特定請求,Vert.x-Web將根據匹配失敗發出錯誤信號:

  • 404若是沒有路徑匹配路徑

  • 405若是路由與路徑匹配但與HTTP方法不匹配

  • 406若是路由與路徑和方法匹配,但它沒法提供具備匹配Accept標頭的內容類型的響應

  • 415若是路徑與路徑和方法匹配可是它不能接受 Content-type

  • 400若是路徑與路徑和方法匹配,但它不能接受空體

您可使用手動管理這些故障 errorHandler

錯誤處理

除了設置處理請求以處理請求以外,您還能夠設置處理程序來處理路由中的故障。

故障處理程序使用與您使用普通處理程序徹底相同的路徑匹配條件。

例如,您能夠提供僅處理某些路徑上的故障或某些HTTP方法的故障處理程序。

這容許您爲應用程序的不一樣部分設置不一樣的故障處理程序。

這是一個示例故障處理程序,只有在路由到如下開頭的路徑的GET請求時發生的故障纔會被調用/somepath/

Route route = router.get("/somepath/*"); route.failureHandler(frc -> { // This will be called for failures that occur // when routing requests to paths starting with // '/somepath/' });

若是處理程序拋出異常,或者處理程序調用fail指定HTTP狀態代碼以故意發出故障信號,則將發生故障路由 

若是從處理程序捕獲到異常,則會致使狀態代碼500發出故障

處理故障時,故障處理程序將傳遞路由上下文,該路由上下文還容許檢索故障或故障代碼,以便故障處理程序可使用它來生成故障響應。

Route route1 = router.get("/somepath/path1/"); route1.handler(routingContext -> { // Let's say this throws a RuntimeException throw new RuntimeException("something happened!"); }); Route route2 = router.get("/somepath/path2"); route2.handler(routingContext -> { // This one deliberately fails the request passing in the status code // E.g. 403 - Forbidden routingContext.fail(403); }); // Define a failure handler // This will get called for any failures in the above handlers Route route3 = router.get("/somepath/*"); route3.failureHandler(failureRoutingContext -> { int statusCode = failureRoutingContext.statusCode(); // Status code will be 500 for the RuntimeException or 403 for the other failure HttpServerResponse response = failureRoutingContext.response(); response.setStatusCode(statusCode).end("Sorry! Not today"); });

對於在狀態消息頭中運行與錯誤處理程序相關的不容許字符使用狀況時發生錯誤的可能性,原始狀態消息將從錯誤代碼更改成默認消息。這是一個權衡,以保持HTTP協議的語義工做,而不是忽然崩潰和關閉套接字而不正確完成協議。

請求身體處理

BodyHandler容許您檢索請求主體,限制車身尺寸和處理文件上傳。

對於須要此功能的任何請求,您應該確保正文處理程序位於匹配的路由上。

此處理程序的使用要求它儘快安裝在路由器中,由於它須要安裝處理程序以使用HTTP請求主體,這必須在執行任何異步調用以前完成。

router.route().handler(BodyHandler.create());

獲取請求正文

若是您知道請求正文是JSON,那麼您可使用getBodyAsJson,若是您知道它是您可使用的字符串getBodyAsString,或者將其做爲緩衝區使用來檢索getBody

限制體型

要限制請求主體的大小,請建立主體處理程序,而後使用setBodyLimit 指定最大主體大小(以字節爲單位)。這對於避免使用很是大的物體耗盡內存很是有用。

若是嘗試發送大於最大大小的主體Request Entity Too Large,將發送HTTP狀態代碼413 - 

默認狀況下沒有身體限制。

合併表單屬性

默認狀況下,正文處理程序會將任何表單屬性合併到請求參數中。若是您不想要此行爲,可使用禁用它setMergeFormAttributes

處理文件上傳

正文處理程序還用於處理多部分文件上載。

若是正文處理程序位於請求的匹配路由上,則任何文件上載都將自動流式傳輸到uploads目錄,這是file-uploads默認狀況下。

每一個文件都將得到一個自動生成的文件名,文件上傳將在路由上下文中提供fileUploads

這是一個例子:

router.route().handler(BodyHandler.create());

router.post("/some/path/uploads").handler(routingContext -> { Set<FileUpload> uploads = routingContext.fileUploads(); // Do something with uploads.... });

每一個文件上載都由一個FileUpload實例描述,該實例容許訪問各類屬性,例如名稱,文件名和大小。

處理cookie

Vert.x-Web使用cookies支持cookie CookieHandler

對於須要此功能的任何請求,您應確保cookie處理程序位於匹配的路由上。

router.route().handler(CookieHandler.create());

操縱餅乾

您可使用getCookie按名稱檢索cookie,或使用它cookies來檢索整個集合。

要刪除cookie,請使用removeCookie

添加cookie使用addCookie

當寫入響應頭時,cookie集將自動寫回響應中,以便瀏覽器能夠存儲它們。

Cookie由實例描述Cookie這容許您檢索名稱,值,域,路徑和其餘常規cookie屬性。

如下是查詢和添加Cookie的示例:

router.route().handler(CookieHandler.create()); router.route("some/path/").handler(routingContext -> { Cookie someCookie = routingContext.getCookie("mycookie"); String cookieValue = someCookie.getValue(); // Do something with cookie... // Add a cookie - this will get written back in the response automatically routingContext.addCookie(Cookie.cookie("othercookie", "somevalue")); });

處理會話

Vert.x-Web爲會話提供現成的支持。

會話在HTTP請求之間持續瀏覽器會話的長度,併爲您提供一個能夠添加會話範圍信息的位置,例如購物籃。

Vert.x-Web使用會話cookie來標識會話。會話cookie是臨時的,當瀏覽器關閉時將被刪除。

咱們不會將會話的實際數據放在會話cookie中 - cookie只是使用標識符來查找服務器上的實際會話。標識符是使用安全隨機生成的隨機UUID,所以它應該是有效的不可知的。

Cookie在HTTP請求和響應中經過網絡傳遞,所以在使用會話時確保使用HTTPS始終是明智之舉。若是您嘗試經過直接HTTP使用會話,Vert.x將警告您。

要在應用程序中啓用會話,您必須SessionHandler 在應用程序邏輯以前具備匹配的路由。

會話處理程序處理會話cookie的建立和會話的查找,所以您沒必要本身執行此操做。

會話商店

要建立會話處理程序,您須要具備會話存儲實例。會話存儲是保存應用程序的實際會話的對象。

會話存儲負責保存安全的僞隨機數生成器,以保證安全的會話ID。該PRNG獨立於商店,這意味着給定來自商店A的會話ID,由於它們具備不一樣的種子和狀態,因此不能導出商店B的會話ID。

默認狀況下,此PRNG使用混合模式,阻止播種,非阻塞生成。PRNG還將每隔5分鐘從新植入64位新熵。可是,這可使用系統屬性進行配置:

  • io.vertx.ext.auth.prng.algorithm例如:SHA1PRNG

  • io.vertx.ext.auth.prng.seed.interval例如:1000(每秒)

  • io.vertx.ext.auth.prng.seed.bits例如:128

除非您注意到PRNG算法正在影響應用程序的性能,不然大多數用戶不須要配置這些值。

Vert.x-Web提供了兩個開箱即用的會話存儲實現,若是您願意,也能夠本身編寫。

指望實現遵循ServiceLoader約定,而且將公開從類路徑在運行時可用的全部存儲。當有多個實現可用時,能夠實例化並配置成功的第一個實現成爲默認實現。若是沒有,則默認值取決於Vert.x的建立模式。若是羣集模式可用,則羣集會話存儲是默認存儲,不然本地存儲是默認存儲。

本地會話商店

使用此存儲,會話本地存儲在內存中,僅在此實例中可用。

若是您只有一個Vert.x實例在應用程序中使用粘性會話而且已將負載均衡器配置爲始終將HTTP請求路由到同一Vert.x實例,則此存儲是合適的。

若是您沒法確保您的請求都將在同一服務器上終止,則請不要使用此存儲,由於您的請求最終可能會出如今不瞭解您的會話的服務器上。

本地會話存儲經過使用共享本地映射來實現,而且具備清除過時會話的收割器。

可使用帶有密鑰的json消息配置收割者間隔:reaperInterval

如下是建立本地的一些示例 SessionStore

SessionStore store1 = LocalSessionStore.create(vertx); // Create a local session store specifying the local shared map name to use // This might be useful if you have more than one application in the same // Vert.x instance and want to use different maps for different applications SessionStore store2 = LocalSessionStore.create(vertx, "myapp3.sessionmap"); // Create a local session store specifying the local shared map name to use and // setting the reaper interval for expired sessions to 10 seconds SessionStore store3 = LocalSessionStore.create(vertx, "myapp3.sessionmap", 10000);

集羣會話商店

使用此存儲,會話存儲在可經過Vert.x羣集訪問的分佈式地圖中。

若是您使用粘性會話,則此存儲是合適的,即您的負載均衡器正在未來自同一瀏覽器的不一樣請求分發到不一樣的服務器。

您可使用此存儲從羣集中的任何節點訪問您的會話。

要使用羣集會話存儲,應確保Vert.x實例已羣集。

如下是建立羣集的一些示例 SessionStore

Vertx.clusteredVertx(new VertxOptions().setClustered(true), res -> { Vertx vertx = res.result(); // Create a clustered session store using defaults SessionStore store1 = ClusteredSessionStore.create(vertx); // Create a clustered session store specifying the distributed map name to use // This might be useful if you have more than one application in the cluster // and want to use different maps for different applications SessionStore store2 = ClusteredSessionStore.create(vertx, "myclusteredapp3.sessionmap"); });

建立會話處理程序

建立會話存儲後,您能夠建立會話處理程序,並將其添加到路徑中。您應確保在應用程序處理程序以前將會話處理程序路由到。

您還須要包含一個,CookieHandler由於會話處理程序使用cookie來查找會話。路由器時,cookie處理程序應位於會話處理程序以前。

這是一個例子:

Router router = Router.router(vertx); // We need a cookie handler first router.route().handler(CookieHandler.create()); // Create a clustered session store using defaults SessionStore store = ClusteredSessionStore.create(vertx); SessionHandler sessionHandler = SessionHandler.create(store); // Make sure all requests are routed through the session handler too router.route().handler(sessionHandler); // Now your application handlers router.route("/somepath/blah/").handler(routingContext -> { Session session = routingContext.session(); session.put("foo", "bar"); // etc });

會話處理程序將確保會話存儲中自動查找(或在沒有會話時建立)會話,並在到達應用程序處理程序以前在路由上下文中設置。

使用會話

在處理程序中,您可使用如下方式訪問會話實例session

您將數據放入會話中put,從會話中獲取數據get,而後從會話中刪除數據remove

會話中項目的鍵始終是字符串。的值能夠是任何類型的用於本地會話存儲器,並用於一個集羣會話存儲器它們能夠是任何基本類型,或者BufferJsonObject, JsonArray或一個可序列化的對象,做爲值必須在整個羣集序列化。

如下是操做會話數據的示例:

router.route().handler(CookieHandler.create()); router.route().handler(sessionHandler); // Now your application handlers router.route("/somepath/blah").handler(routingContext -> { Session session = routingContext.session(); // Put some data from the session session.put("foo", "bar"); // Retrieve some data from a session int age = session.get("age"); // Remove some data from a session JsonObject obj = session.remove("myobj"); });

響應完成後,會話會自動寫回商店。

您可使用手動銷燬會話destroy這將從上下文和會話存儲中刪除會話。請注意,若是沒有會話,將自動爲來自經過會話處理程序路由的瀏覽器的下一個請求建立新會話。

會話超時

若是在超過超時期限的時間內未訪問會話,則會自動超時。會話超時後,會從商店中刪除。

當請求到達而且會話被查找而且響應完成而且會話存儲回存儲中時,會話被自動標記爲被訪問。

您還可使用setAccessed手動將會話標記爲已訪問。

能夠在建立會話處理程序時配置會話超時。默認超時爲30分鐘。

身份驗證/受權

Vert.x附帶了一些開箱即用的處理程序,用於處理身份驗證和受權。

建立一個auth處理程序

要建立auth處理程序,您須要一個實例AuthProviderAuth提供程序用於用戶的身份驗證和受權。Vert.x在vertx-auth項目中提供了幾個開箱即用的auth提供程序實例。有關auth提供程序以及如何使用和配置它們的完整信息,請參閱auth文檔。

這是一個在給定auth提供程序的狀況下建立基本auth處理程序的簡單示例。

router.route().handler(CookieHandler.create()); router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx))); AuthHandler basicAuthHandler = BasicAuthHandler.create(authProvider);

在您的應用程序中處理auth

假設您但願對以其開頭的路徑的全部請求都要/private/進行身份驗證。爲此,請確保您的auth處理程序位於這些路徑上的應用程序處理程序以前:

router.route().handler(CookieHandler.create()); router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx)).setAuthProvider(authProvider)); AuthHandler basicAuthHandler = BasicAuthHandler.create(authProvider); // All requests to paths starting with '/private/' will be protected router.route("/private/*").handler(basicAuthHandler); router.route("/someotherpath").handler(routingContext -> { // This will be public access - no login required }); router.route("/private/somepath").handler(routingContext -> { // This will require a login // This will have the value true boolean isAuthenticated = routingContext.user() != null; });

若是AUTH處理程序已成功驗證和受權用戶將注入一個User 對象入RoutingContext所以它在你的處理程序可用: user

若是您但願將User對象存儲在會話中,以便在請求之間可用,這樣您就沒必要對每一個請求進行身份驗證,那麼您應確保在auth以前在匹配的路由上有會話處理程序和用戶會話處理程序處理程序。

得到用戶對象後,您還能夠以編程方式使用其上的方法來受權用戶。

若是要使用戶註銷,能夠調用clearUser 路由上下文。

HTTP基自己份驗證

HTTP基自己份驗證是一種簡單的身份驗證方法,適用於簡單的應用程序。

使用基自己份驗證,憑據將在HTTP標頭中經過線路以非加密方式發送,所以使用HTTPS而非HTTP來提供應用程序相當重要。

使用基自己份驗證,若是用戶請求須要受權的資源,則基自己份驗證處理程序將發回401帶有標頭WWW-Authenticate響應這會提示瀏覽器顯示登陸對話框並提示用戶輸入其用戶名和密碼。

再次請求資源,此次使用Authorization標頭集,包含在Base64中編碼的用戶名和密碼。

當基自己份驗證處理程序收到此信息時,它會AuthProvider 使用用戶名和密碼調用配置對用戶進行身份驗證。若是驗證成功,則處理程序嘗試受權用戶。若是成功,則容許請求的路由繼續到應用程序處理程序,不然403返回響應以表示拒絕訪問。

可使用訪問要授予的資源所需的一組權限來設置auth處理程序。

重定向auth處理程序

使用重定向身份驗證處理時,若是用戶嘗試訪問受保護資源而且未登陸,則會將用戶重定向到登陸頁面。

而後,用戶填寫登陸表單並提交。這由對用戶進行身份驗證的服務器處理,若是通過身份驗證,則將用戶重定向回原始資源。

要使用重定向身份驗證,您須要配置實例RedirectAuthHandler而不是基自己份驗證處理程序。

您還須要設置處理程序以提供實際的登陸頁面,以及處理實際登陸自己的處理程序。爲了處理登陸,咱們爲此提供了一個預構建的處理程序FormLoginHandler

這是一個簡單應用程序的示例,在默認重定向URL上使用重定向auth處理程序/loginpage

router.route().handler(CookieHandler.create()); router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx)).setAuthProvider(authProvider)); AuthHandler redirectAuthHandler = RedirectAuthHandler.create(authProvider); // All requests to paths starting with '/private/' will be protected router.route("/private/*").handler(redirectAuthHandler); // Handle the actual login // One of your pages must POST form login data router.post("/login").handler(FormLoginHandler.create(authProvider)); // Set a static server to serve static resources, e.g. the login page router.route().handler(StaticHandler.create()); router.route("/someotherpath").handler(routingContext -> { // This will be public access - no login required }); router.route("/private/somepath").handler(routingContext -> { // This will require a login // This will have the value true boolean isAuthenticated = routingContext.user() != null; });

JWT受權

使用JWT受權能夠經過權限保護資源,而沒有足夠權限的用戶將被拒絕訪問。您須要添加io.vertx:vertx-auth-jwt:3.8.0要使用依賴項JWTAuthProvider

要使用此處理程序,須要執行如下兩個步驟:

  • 設置處理程序以發出令牌(或依賴第三方)

  • 設置處理程序以過濾請求

請注意,這兩個處理程序應僅在HTTPS上可用,不這樣作能夠嗅探傳輸中的令牌,從而致使會話劫持攻擊。

這是一個關於如何發出令牌的示例:

Router router = Router.router(vertx); JWTAuthOptions authConfig = new JWTAuthOptions() .setKeyStore(new KeyStoreOptions() .setType("jceks") .setPath("keystore.jceks") .setPassword("secret")); JWTAuth authProvider = JWTAuth.create(vertx, authConfig); router.route("/login").handler(ctx -> { // this is an example, authentication should be done with another provider... if ("paulo".equals(ctx.request().getParam("username")) && "secret".equals(ctx.request().getParam("password"))) { ctx.response().end(authProvider.generateToken(new JsonObject().put("sub", "paulo"), new JWTOptions())); } else { ctx.fail(401); } });

既然你的客戶端有一個令牌,那麼所須要的是forall *後續請求,HTTP頭 Authorization被填充:Bearer <token>例如:

Router router = Router.router(vertx); JWTAuthOptions authConfig = new JWTAuthOptions() .setKeyStore(new KeyStoreOptions() .setType("jceks") .setPath("keystore.jceks") .setPassword("secret")); JWTAuth authProvider = JWTAuth.create(vertx, authConfig); router.route("/protected/*").handler(JWTAuthHandler.create(authProvider)); router.route("/protected/somepage").handler(ctx -> { // some handle code... });

JWT容許您將任何您喜歡的信息添加到令牌自己。經過執行此操做,服務器中沒有容許您擴展應用程序而無需羣集會話數據的狀態。爲了向令牌添加數據,在建立令牌期間只需將數據添加到JsonObject參數:

JWTAuthOptions authConfig = new JWTAuthOptions() .setKeyStore(new KeyStoreOptions() .setType("jceks") .setPath("keystore.jceks") .setPassword("secret")); JWTAuth authProvider = JWTAuth.create(vertx, authConfig); authProvider.generateToken(new JsonObject().put("sub", "paulo").put("someKey", "some value"), new JWTOptions());

消費時也同樣:

Handler<RoutingContext> handler = rc -> { String theSubject = rc.user().principal().getString("sub"); String someKey = rc.user().principal().getString("someKey"); };

配置所需的權限

使用任何auth處理程序,您還能夠配置訪問資源所需的權限。

默認狀況下,若是未配置權限,則只需登陸便可訪問資源,不然用戶必須同時登陸(已經過身份驗證)並具備所需權限。

如下是配置應用程序的示例,以便應用程序的不一樣部分須要不一樣的權限。請注意,權限的含義由您使用的基礎身份驗證提供程序肯定。例如,某些可能支持基於角色/權限的模型,但其餘人可能使用其餘模型。

AuthHandler listProductsAuthHandler = RedirectAuthHandler.create(authProvider); listProductsAuthHandler.addAuthority("list_products"); // Need "list_products" authority to list products router.route("/listproducts/*").handler(listProductsAuthHandler); AuthHandler settingsAuthHandler = RedirectAuthHandler.create(authProvider); settingsAuthHandler.addAuthority("role:admin"); // Only "admin" has access to /private/settings router.route("/private/settings/*").handler(settingsAuthHandler);

連接多個auth處理程序

有時您但願在單個應用程序中支持多個authN / authZ機制。爲此您可使用ChainAuthHandler鏈式身份驗證處理程序將嘗試對一系列處理程序執行身份驗證。該鏈適用於AuthN和AuthZ,所以若是身份驗證在鏈的給定處理程序中有效,則將使用相同的處理程序執行受權(若是請求)。

重要的是要知道某些處理程序須要特定的提供程序,例如:

所以,預計不會在全部處理程序之間共享提供程序。有些狀況下,能夠跨處理程序共享提供程序,例如:

因此說你要建立一個同時接受HTTP Basic Authentication的應用程序Form Redirect您將開始將鏈配置爲:

ChainAuthHandler chain = ChainAuthHandler.create(); // add http basic auth handler to the chain chain.append(BasicAuthHandler.create(provider)); // add form redirect auth handler to the chain chain.append(RedirectAuthHandler.create(provider)); // secure your route router.route("/secure/resource").handler(chain); // your app router.route("/secure/resource").handler(ctx -> { // do something... });

所以,當用戶發出沒有Authorization標頭的請求時,這意味着鏈將沒法使用基本auth處理程序進行身份驗證,並將嘗試使用重定向處理程序進行身份驗證。因爲重定向處理程序始終重定向,所以您將被髮送到您在該處理程序中配置的登陸表單。

與vertx-web中的正常路由同樣,auth chaning是一個序列,所以若是您但願回退到瀏覽器,使用HTTP Basic身份驗證而不是重定向來請求用戶憑據,則只須要反轉附加的順序。連鎖,鏈條。

如今假設您在提供Authorization帶有值的標頭的位置發出請求Basic [token]在這種狀況下,基本的auth處理程序將嘗試進行身份驗證,若是它成功,鏈將中止而且vertx-web將繼續處理您的處理程序。若是令牌無效,例如錯誤的用戶名/密碼,則鏈將繼續到如下條目。在這種特定狀況下,重定向auth處理程序。

提供靜態資源

Vert.x-Web附帶了一個開箱即用的處理程序,用於提供靜態Web資源,所以您能夠很是輕鬆地編寫靜態Web服務器。

服務靜態資源,如.html.css.js或任何其餘靜態資源,您使用的一個實例StaticHandler

對靜態處理程序處理的路徑的任何請求都將致使文件從文件系統上的目錄或類路徑中提供。默認的靜態文件目錄是webroot能夠配置的。

在如下示例中,全部以路徑開頭的請求/static/都將從目錄中提供webroot

router.route("/static/*").handler(StaticHandler.create());

例如,若是存在帶路徑/static/css/mystyles.css請求,靜態服務將在目錄中查找文件webroot/css/mystyle.css

它還會在類路徑上查找一個名爲的文件webroot/css/mystyle.css這意味着您能夠將全部靜態資源打包到一個jar文件(或fatjar)中並像這樣分發它們。

當Vert.x第一次在類路徑上找到資源時,它會將其解壓縮並將其緩存在磁盤上的臨時目錄中,所以每次都沒必要執行此操做。

處理程序將處理範圍感知請求。當客戶端向靜態資源發出請求時,處理程序將經過在Accept-Ranges標頭上聲明單元來通知它能夠處理範圍感知請求包含Range具備正確單元和開始和結束索引標頭的其餘請求將接收具備正確Content-Range標頭的部分響應

配置緩存

默認狀況下,靜態處理程序將設置緩存標頭以使瀏覽器可以有效地緩存文件。

Vert.x的Web設置標題cache-controllast-modifieddate

cache-controlmax-age=86400默認設置爲這至關於一天。setMaxAgeSeconds若是須要,能夠配置它 

若是瀏覽器發送帶有if-modified-since標頭的GET或HEAD請求,而且該資源自該日期起未被修改,304則返回狀態,告知瀏覽器使用其本地緩存的資源。

若是不須要處理緩存頭,則能夠禁用它setCachingEnabled

啓用緩存處理後,Vert.x-Web將緩存內存中資源的最後修改日期,這樣能夠避免磁盤命中每次都檢查實際的上次修改日期。

緩存中的條目具備到期時間,在此以後,將再次檢查磁盤上的文件並更新緩存條目。

若是您知道您的文件永遠不會在磁盤上更改,那麼緩存條目將永遠不會過時。這是默認值。

若是您知道在服務器運行時您的文件可能在磁盤上發生更改,那麼您能夠將只讀文件設置爲false setFilesReadOnly

要在任什麼時候候啓用能夠在內存中緩存的最大條目數,您可使用 setMaxCacheSize

要配置可使用的緩存條目的到期時間setCacheEntryTimeout

配置索引頁面

對根路徑的任何請求/都將致使索引頁面被提供。默認狀況下,索引頁面是index.html這能夠配置setIndexPage

更改Web根目錄

默認狀況下,將從目錄提供靜態資源webroot配置此用途 setWebRoot

提供隱藏文件

默認狀況下,服務器將提供隱藏文件(以文件開頭.)。

若是您不想要提供隱藏文件,可使用它進行配置setIncludeHidden

目錄列表

服務器還能夠執行目錄列表。默認狀況下,禁用目錄列表。要啓用它setDirectoryListing

啓用目錄列表時,返回的內容取決於accept標頭中的內容類型

對於text/html目錄列表,可使用用於呈現目錄列表頁面的模板進行配置setDirectoryTemplate

禁用磁盤上的文件緩存

默認狀況下,Vert.x會將從類路徑提供的文件緩存到磁盤上的文件中,該文件位於.vertx當前工做目錄中調用的目錄的子目錄中。這在將服務部署爲生產中的fatjars時很是有用,每次從類路徑提供文件都很慢。

在開發過程當中,這可能會致使問題,就像在服務器運行時更​​新靜態內容同樣,緩存文件將不會提供更新的文件。

要禁用文件緩存能夠提供您vert.x選項的屬性fileResolverCachingEnabledfalse爲了向後兼容,它還會將該值默認爲系統屬性vertx.disableFileCaching例如,您能夠在IDE中設置運行配置,以便在運行主類時進行設置。

CORS處理

跨源資源共享是一種安全機制,容許從一個域請求資源並從另外一個域提供資源。

Vert.x-Web包含一個處理CorsHandlerCORS協議的處理程序

這是一個例子:

router.route().handler(CorsHandler.create("vertx\\.io").allowedMethod(HttpMethod.GET)); router.route().handler(routingContext -> { // Your app handlers });

模板

Vert.x-Web包括動態頁面生成功能,包括對幾個流行模板引擎的開箱即用支持。您也能夠輕鬆添加本身的。

模板引擎由描述TemplateEngine爲了渲染模板 render,使用了。

使用模板最簡單的方法不是直接調用模板引擎而是使用模板引擎 TemplateHandler此處理程序根據HTTP請求中的路徑爲您調用模板引擎。

默認狀況下,模板處理程序將在名爲的目錄中查找模板templates這能夠配置。

處理程序將返回具備text/html默認內容類型的呈現結果這也能夠配置。

建立模板處理程序時,您將傳入所需模板引擎的實例。模板引擎未嵌入到vertx-web中,所以您須要配置項目以訪問它們。爲每一個模板引擎提供配置。

這裏有些例子:

TemplateEngine engine = HandlebarsTemplateEngine.create(); TemplateHandler handler = TemplateHandler.create(engine); // This will route all GET requests starting with /dynamic/ to the template handler // E.g. /dynamic/graph.hbs will look for a template in /templates/graph.hbs router.get("/dynamic/*").handler(handler); // Route all GET requests for resource ending in .hbs to the template handler router.getWithRegex(".+\\.hbs").handler(handler);

MVEL模板引擎

要使用MVEL,您須要將如下依賴項添加到項目中: io.vertx:vertx-web-templ-mvel:3.8.0使用如下命令建立MVEL模板引擎的實例:io.vertx.ext.web.templ.MVELTemplateEngine#create()

使用MVEL模板引擎時,.templ若是文件名中未指定擴展名,它將默認查找帶擴展名的模板

路由上下文RoutingContext在MVEL模板中可用做context變量,這意味着您能夠基於上下文中的任何內容(包括請求,響應,會話或上下文數據)來呈現模板。

這裏有些例子:

請求路徑是@ {context.request()。path()}

會話中的變量'foo'是@ {context.session()。get('foo')}

上下文數據中的值「bar」是@ {context.get('bar')}

有關如何編寫MVEL模板的信息請參閱MVEL模板文檔

玉模板引擎

要使用Jade模板引擎,您須要將如下依賴項添加到項目中: io.vertx:vertx-web-templ-jade:3.8.0使用如下方法建立Jade模板引擎的實例:io.vertx.ext.web.templ.JadeTemplateEngine#create()

使用Jade模板引擎時,.jade若是文件名中未指定擴展名,它將默認查找帶擴展名的模板

路由上下文RoutingContext在Jade模板中可用做context變量,這意味着您能夠基於上下文中的任何內容(包括請求,響應,會話或上下文數據)來呈現模板。

這裏有些例子:


HTML
   title = context.get('foo')+ context.request()。path()
 身體

有關如何編寫Jade模板的信息請參閱Jade4j文檔

把手模板引擎

要使用Handlebars,您須要將如下依賴項添加到項目中: io.vertx:vertx-web-templ-handlebars:3.8.0使用如下方法建立Handlebars模板引擎的實例:io.vertx.ext.web.templ.HandlebarsTemplateEngine#create()

使用Handlebars模板引擎時,.hbs若是文件名中未指定擴展名,它將默認查找帶擴展名的模板

Handlebars模板沒法調用對象中的任意方法,所以咱們不能將路由上下文傳遞給模板,讓模板像咱們可使用其餘模板引擎同樣內省它。

相反,上下文data在模板中可用。

若是要訪問其餘數據(如請求路徑,請求參數或會話數據),則應在模板處理程序以前將其添加處處理程序中的上下文數據中。例如:

TemplateHandler handler = TemplateHandler.create(engine); router.get("/dynamic").handler(routingContext -> { routingContext.put("request_path", routingContext.request().path()); routingContext.put("session_data", routingContext.session().data()); routingContext.next(); }); router.get("/dynamic/").handler(handler);

有關如何編寫把手模板的信息請參閱Handlebars Java端口文檔

Thymeleaf模板引擎

要使用Thymeleaf,您須要爲項目添加如下依賴項: io.vertx:vertx-web-templ-thymeleaf:3.8.0使用如下方法建立Thymeleaf模板引擎的實例:io.vertx.ext.web.templ.ThymeleafTemplateEngine#create()

使用Thymeleaf模板引擎時,.html若是文件名中未指定擴展名,它將默認查找帶擴展名的模板

路由上下文RoutingContext在Thymeleaf模板中可用做context變量,這意味着您能夠基於上下文中的任何內容(包括請求,響應,會話或上下文數據)來呈現模板。

這裏有些例子:

[剪斷]
<p th:text =「$ {context.get('foo')}」> </ p>
<p th:text =「$ {context.get('bar')}」> </ p>
<p th:text =「$ {context.normalisedPath()}」> </ p>
<p th:text =「$ {context.request()。params()。get('param1')}」> </ p>
<p th:text =「$ {context.request()。params()。get('param2')}」> </ p>
[剪斷]

有關如何編寫Thymeleaf模板的信息請參閱Thymeleaf文檔

Apache FreeMarker模板引擎

要使用Apache FreeMarker,您須要將如下依賴項添加到項目中: io.vertx:vertx-web-templ-freemarker:3.8.0使用如下方法建立Apache FreeMarker模板引擎的實例:io.vertx.ext.web.templ.Engine#create()

使用Apache FreeMarker模板引擎時,.ftl若是文件名中未指定擴展名,它將默認查找帶擴展名的模板

路由上下文RoutingContext在Apache FreeMarker模板中做爲context變量提供,這意味着您能夠基於上下文中的任何內容(包括請求,響應,會話或上下文數據)來呈現模板。

這裏有些例子:

[剪斷]
<p th:text =「$ {context.foo}」> </ p>
<p th:text =「$ {context.bar}」> </ p>
<p th:text =「$ {context.normalisedPath()}」> </ p>
<p th:text =「$ {context.request()。params()。param1}」> </ p>
<p th:text =「$ {context.request()。params()。param2}」> </ p>
[剪斷]

有關如何編寫Apache FreeMarker模板的信息請參閱Apache FreeMarker文檔

卵石模板引擎

要使用Pebble,您須要爲項目添加如下依賴項: io.vertx:vertx-web-templ-pebble:3.8.0使用如下方法建立Pebble模板引擎的實例:io.vertx.ext.web.templ.PebbleTemplateEngine#create(vertx)

使用Pebble模板引擎時,.peb若是文件名中未指定擴展名,它將默認查找帶擴展名的模板

路由上下文RoutingContext在Pebble模板中可用做context變量,這意味着您能夠基於上下文中的任何內容(包括請求,響應,會話或上下文數據)來呈現模板。

這裏有些例子:

[剪斷]
<p th:text =「{{context.foo}}」> </ p>
<p th:text =「{{context.bar}}」> </ p>
<p th:text =「{{context.normalisedPath()}}」> </ p>
<p th:text =「{{context.request()。params()。param1}}」> </ p>
<p th:text =「{{context.request()。params()。param2}}」> </ p>
[剪斷]

有關如何編寫Pebble模板的信息請參閱Pebble文檔

搖桿模板引擎

要使用Rocker,請將其io.vertx:vertx-web-templ-rocker:3.8.0做爲依賴項添加到項目中。而後,您可使用建立Rocker模板引擎實例io.vertx.ext.web.templ.rocker#create()

而後,傳遞給render方法的JSON上下文對象的值將做爲模板參數公開。鑑於:

[剪斷]
final JsonObject context = new JsonObject()
 .put(「foo」,「badger」)
 .put(「bar」,「fox」)
 .put(「context」,new JsonObject()。put(「path」,「/ foo / bar」));

engine.render(context,「somedir / TestRockerTemplate2」,render  - > {
 //(...)
});
[剪斷]

而後模板能夠做爲如下somedir/TestRockerTemplate2.rocker.html資源文件:

@import io.vertx.core.json.JsonObject
@args(JsonObject context,String foo,String bar)
你好@foo和@bar
請求路徑是@ context.getString(「path」)

禁用緩存

在開發期間,您可能但願禁用模板緩存,以便在每一個請求上從新評估模板。爲此,您須要設置系統屬性:io.vertx.ext.web.TemplateEngine.disableCacheto true

默認狀況下,它將爲false。所以始終啓用緩存。

錯誤處理程序

您可使用模板處理程序或其餘方式呈現本身的錯誤,但Vert.x-Web還包含一個能夠爲您呈現錯誤頁面的四四方方的「漂亮」錯誤處理程序。

處理程序是ErrorHandler要使用錯誤處理程序,只需將其設置爲您想要覆蓋的任何路徑的失敗處理程序。

請求記錄器

Vert.x-Web包含一個LoggerHandler可用於記錄HTTP請求的處理程序您應該在任何可能失敗的處理程序以前安裝此處理程序RoutingContext

默認狀況下,請求會記錄到Vert.x記錄器,該記錄器能夠配置爲使用JUL日誌記錄,log4j或SLF4J。

LoggerFormat

提供favicon

Vert.x-Web包含FaviconHandler特別用於服務favicons 的處理程序

可使用文件系統的路徑指定Favicons,或者默認狀況下,Vert.x-Web將使用名稱在類路徑中查找文件favicon.ico這意味着您將favicon捆綁在應用程序的jar中。

超時處理程序

Vert.x-Web包含一個超時處理程序,若是處理時間過長,您可使用它來超時請求。

這是使用的實例配置的TimeoutHandler

若是請求在寫入503響應以前超時,則響應將返回給客戶端。

下面是一個使用超時處理程序的示例,該處理程序將/foo超過5秒後開始的全部路徑請求

router.route("/foo/").handler(TimeoutHandler.create(5000));

響應時間處理程序

此處理程序設置標頭x-response-time響應標頭,其中包含從接收請求到寫入響應標頭的時間(以毫秒爲單位),例如:

x響應時間:1456ms

內容類型處理程序

ResponseContentTypeHandler能夠設置Content-Type自動報頭。假設咱們正在構建一個RESTful Web應用程序。咱們須要在全部處理程序中設置內容類型:

router.get("/api/books").produces("application/json").handler(rc -> findBooks(ar -> { if (ar.succeeded()) { rc.response().putHeader("Content-Type", "application/json").end(toJson(ar.result())); } else { rc.fail(ar.cause()); } }));

若是API表面變得很是大,則設置內容類型會變得很麻煩。要避免這種狀況,請添加ResponseContentTypeHandler到相應的路由:

router.route("/api/*").handler(ResponseContentTypeHandler.create()); router.get("/api/books").produces("application/json").handler(rc -> findBooks(ar -> { if (ar.succeeded()) { rc.response().end(toJson(ar.result())); } else { rc.fail(ar.cause()); } }));

處理程序從中獲取適當的內容類型getAcceptableContentType所以,您能夠輕鬆共享同一個處理程序以生成不一樣類型的數據:

router.route("/api/*").handler(ResponseContentTypeHandler.create()); router.get("/api/books").produces("text/xml").produces("application/json").handler(rc -> findBooks(ar -> { if (ar.succeeded()) { if (rc.getAcceptableContentType().equals("text/xml")) { rc.response().end(toXML(ar.result())); } else { rc.response().end(toJson(ar.result())); } } else { rc.fail(ar.cause()); } }));

SockJS

SockJS是一個客戶端JavaScript庫和協議,它提供了一個簡單的相似WebSocket的接口,容許您鏈接到SockJS服務器,而無論實際的瀏覽器或網絡是否容許真正的WebSockets。

它經過支持瀏覽器和服務器之間的各類不一樣傳輸,並根據瀏覽器和網絡功能在運行時選擇一個來實現這一點。

全部這些對您來講都是透明的 - 您只需使用相似WebSocket的界面便可

有關SockJS的更多信息,請訪問SockJS網站

SockJS處理程序

Vert.x提供了一個開箱即用的處理程序SockJSHandler,在Vert.x-Web應用程序中使用SockJS。

您應該使用每一個SockJS應用程序建立一個處理程序SockJSHandler.create您還能夠在建立實例時指定配置選項。配置選項用實例描述SockJSHandlerOptions

Router router = Router.router(vertx); SockJSHandlerOptions options = new SockJSHandlerOptions().setHeartbeatInterval(2000); SockJSHandler sockJSHandler = SockJSHandler.create(vertx, options); router.route("/myapp/*").handler(sockJSHandler);

處理SockJS套接字

在服務器端,您在SockJS處理程序上設置了一個處理程序,每次從客戶端創建SockJS鏈接時都會調用它:

傳遞給處理程序的對象是SockJSSocket這有一個熟悉的相似套接字的接口,你能夠讀取和寫入相似於a NetSocket或a WebSocket它還實現了ReadStream, WriteStream所以您能夠將其與其餘讀寫流相連。

下面是一個簡單的SockJS處理程序的示例,該處理程序只返回它讀取的任何數據:

Router router = Router.router(vertx);

SockJSHandlerOptions options = new SockJSHandlerOptions().setHeartbeatInterval(2000); SockJSHandler sockJSHandler = SockJSHandler.create(vertx, options); sockJSHandler.socketHandler(sockJSSocket -> { // Just echo the data back sockJSSocket.handler(sockJSSocket::write); }); router.route("/myapp/*").handler(sockJSHandler);

客戶端

在客戶端JavaScript中,您使用SockJS客戶端庫進行鏈接。

你能夠在這裏找到

有關使用SockJS JavaScript客戶端的完整詳細信息,請訪問SockJS網站,但總結一下,您可使用如下內容:

var sock = new SockJS('http://mydomain.com/myapp');

sock.onopen = function(){
 的console.log( '開放');
};

sock.onmessage = function(e){
 console.log('message',e.data);
};

sock.onclose = function(){
 的console.log( '關閉');
};

sock.send( '試驗');

sock.close();

配置SockJS處理程序

可使用各類選項配置處理程序SockJSHandlerOptions

insertJSESSIONID

插入JSESSIONID cookie,以便負載均衡器確保對特定SockJS會話的請求始終路由到正確的服務器。默認是true

sessionTimeout

close當一段時間沒有看到接收鏈接的客戶端時,服務器發送事件。此延遲由此設置配置。默認狀況下,在close5秒內未看到接收鏈接時將發出事件。

heartbeatInterval

爲了防止代理和負載均衡器關閉長時間運行的http請求,咱們須要僞裝鏈接處於活動狀態並偶爾發送心跳包。此設置控制此操做的頻率。默認狀況下,每25秒發送一次心跳包。

maxBytesStreaming

大多數流傳輸在客戶端保存響應,而且不釋放傳遞的消息使用的內存。這種運輸須要偶爾進行垃圾收集。max_bytes_streaming設置在關閉以前可經過單個HTTP流請求發送的最小字節數。以後客戶端須要打開新請求。將此值設置爲1能夠有效地禁用流式傳輸,並使流式傳輸的行爲相似於輪詢傳輸。默認值爲128K。

libraryURL

不支持跨域通訊的傳輸('eventsource'到名稱之一)使用iframe技巧。一個簡單的頁面從SockJS服務器(使用其外部域)提供,並放置在一個不可見的iframe中。從這個iframe運行的代碼不須要擔憂跨域問題,由於它從域本地運行到SockJS服務器。這個iframe也須要加載SockJS javascript客戶端庫,這個選項容許你指定它的url(若是你不肯定,請指向最新的縮小的SockJS客戶端版本,這是默認值)。默認值爲http://cdn.jsdelivr.net/sockjs/0.3.4/sockjs.min.js

disabledTransports

這是您要禁用的傳輸列表。可能的值爲WEBSOCKET,EVENT_SOURCE,HTML_FILE,JSON_P,XHR。

SockJS事件總線橋

Vert.x-Web附帶一個稱爲事件總線橋的內置SockJS套接字處理程序,它有效地將服務器端Vert.x事件總線擴展到客戶端JavaScript。

這將建立一個分佈式事件總線,它不只跨越服務器端的多個Vert.x實例,還包括在瀏覽器中運行的客戶端JavaScript。

所以,咱們能夠建立一個包含許多瀏覽器和服務器的龐大分佈式總線。只要鏈接服務器,瀏覽器就沒必要鏈接到同一臺服務器。

這是經過提供一個簡單的客戶端JavaScript庫來實現的,該庫vertx-eventbus.js提供了一個很是相似於服務器端Vert.x事件總線API的API,它容許您向事件總線發送和發佈消息並註冊處理程序以接收消息。

此JavaScript庫使用JavaScript SockJS客戶端經過終止於SockJSHandler服務器端的SockJS鏈接來隧道傳輸事件總線流量

而後在其SockJSHandler安裝一個特殊的SockJS套接字處理程序,它處理SockJS數據並將其與服務器端事件總線橋接。

要激活網橋,只需調用 bridgeSockJS處理程序便可。

Router router = Router.router(vertx); SockJSHandler sockJSHandler = SockJSHandler.create(vertx); BridgeOptions options = new BridgeOptions(); sockJSHandler.bridge(options); router.route("/eventbus/*").handler(sockJSHandler);

在客戶端JavaScript中,您使用'vertx-eventbus.js`庫來建立與事件總線的鏈接以及發送和接收消息:

<script src="http://cdn.jsdelivr.net/sockjs/0.3.4/sockjs.min.js"></script> <script src='vertx-eventbus.js'></script> <script>  var eb = new EventBus('http://localhost:8080/eventbus'); eb.onopen = function() { // set a handler to receive a message eb.registerHandler('some-address', function(error, message) { console.log('received a message: ' + JSON.stringify(message)); }); // send a message eb.send('some-address', {name: 'tim', age: 587}); } </script>

該示例的第一件事是建立事件總線的實例

var eb = new EventBus('http://localhost:8080/eventbus');

構造函數的參數是鏈接到事件總線的URI。因爲咱們使用前綴建立橋,eventbus咱們將在那裏鏈接。

在打開鏈接以前,您沒法對鏈接執行任何操做。當它打開時,onopen將調用處理程序。

該橋支持自動從新鏈接,具備可配置的延遲和退避選項。

var eb = new EventBus('http://localhost:8080/eventbus'); eb.enableReconnect(true); eb.onopen = function() {}; // Set up handlers here, will be called on initial connection and all reconnections eb.onreconnect = function() {}; // Optional, will only be called on reconnections // Alternatively, pass in an options object var options = { vertxbus_reconnect_attempts_max: Infinity, // Max reconnect attempts vertxbus_reconnect_delay_min: 1000, // Initial delay (in ms) before first reconnect attempt vertxbus_reconnect_delay_max: 5000, // Max delay (in ms) between reconnect attempts vertxbus_reconnect_exponent: 2, // Exponential backoff factor vertxbus_randomization_factor: 0.5 // Randomization factor between 0 and 1 }; var eb2 = new EventBus('http://localhost:8080/eventbus', options); eb2.enableReconnect(true); // Set up handlers...

您可使用依賴項管理器檢索客戶端庫:

  • Maven(在你的pom.xml):

<dependency> <groupId>io.vertx</groupId> <artifactId>vertx-web</artifactId> <version>3.8.0</version> <classifier>client</classifier> <type>js</type> </dependency>
  • Gradle(在您的build.gradle文件中):

compile 'io.vertx:vertx-web:3.8.0:client'

該圖書館也可用於:

請注意,API已在3.0.0和3.1.0版本之間進行了更改。請檢查更改日誌。之前的客戶端仍然兼容,仍然可使用,但新客戶端提供更多功能,而且更接近vert.x事件總線API。

保護橋樑

若是您在沒有保護它的狀況下啓動了上述示例中的橋接器,並嘗試經過它發送消息,您會發現消息神祕地消失了。他們發生了什麼?

對於大多數應用程序,您可能不但願客戶端JavaScript可以向服務器端的任何處理程序或全部其餘瀏覽器發送任何消息。

例如,您可能在事件總線上有一個服務,容許訪問或刪除數據。咱們不但願行爲不端或惡意的客戶端可以刪除數據庫中的全部數據!

此外,咱們不必定但願任何客戶端可以監放任何事件總線地址。

爲了解決這個問題,SockJS橋將默認拒絕經過任何消息。您能夠告訴橋接器哪些消息能夠經過。(對於老是容許經過的回覆消息,有一個例外)。

換句話說,網橋就像一種具備默認拒絕全部策略的防火牆

配置網橋告訴它應該經過哪些消息很容易。

您能夠使用在調用bridge時傳入的內容來指定要容許入站和出站流量的 匹配項BridgeOptions

每一個匹配都是一個PermittedOptions對象:

setAddress

這表示郵件發送到的確切地址。若是要容許基於確切地址的郵件,請使用此字段。

setAddressRegex

這是一個與地址匹配的正則表達式。若是要容許基於正則表達式的消息,請使用此字段。若是address指定了該字段,則該字段將被忽略。

setMatch

這容許您根據其結構容許消息。匹配中的任何字段都必須存在於消息中,而且具備相同的值以容許它們。這當前僅適用於JSON消息。

若是消息是入站(即從客戶端的JavaScript被髮送到服務器),當它收到Vert.x的Web看起來經過任何入境許可匹配。若是有任何匹配,將容許經過。

若是消息在發送到客戶端以前出局(即從服務器發送到客戶端JavaScript),則Vert.x-Web將查看任何出站容許的匹配。若是有任何匹配,將容許經過。

實際匹配的工做原理以下:

若是address已指定字段,則address必須與消息的地址徹底匹配才能將其視爲匹配。

若是address還沒有指定addressRegex字段且已指定字段,則正則表達式address_re必須與消息的地址匹配才能被視爲匹配。

若是match已指定字段,則消息的結構也必須匹配。經過查看匹配對象中的全部字段和值並檢查它們是否存在於實際的消息體中來構建匹配。

這是一個例子:

Router router = Router.router(vertx); SockJSHandler sockJSHandler = SockJSHandler.create(vertx); // Let through any messages sent to 'demo.orderMgr' from the client PermittedOptions inboundPermitted1 = new PermittedOptions().setAddress("demo.orderMgr"); // Allow calls to the address 'demo.persistor' from the client as long as the messages // have an action field with value 'find' and a collection field with value // 'albums' PermittedOptions inboundPermitted2 = new PermittedOptions().setAddress("demo.persistor") .setMatch(new JsonObject().put("action", "find") .put("collection", "albums")); // Allow through any message with a field `wibble` with value `foo`. PermittedOptions inboundPermitted3 = new PermittedOptions().setMatch(new JsonObject().put("wibble", "foo")); // First let's define what we're going to allow from server -> client // Let through any messages coming from address 'ticker.mystock' PermittedOptions outboundPermitted1 = new PermittedOptions().setAddress("ticker.mystock"); // Let through any messages from addresses starting with "news." (e.g. news.europe, news.usa, etc) PermittedOptions outboundPermitted2 = new PermittedOptions().setAddressRegex("news\\..+"); // Let's define what we're going to allow from client -> server BridgeOptions options = new BridgeOptions(). addInboundPermitted(inboundPermitted1). addInboundPermitted(inboundPermitted1). addInboundPermitted(inboundPermitted3). addOutboundPermitted(outboundPermitted1). addOutboundPermitted(outboundPermitted2); sockJSHandler.bridge(options); router.route("/eventbus/*").handler(sockJSHandler);

須要受權郵件

事件總線橋還能夠配置爲使用Vert.x-Web受權功能來要求對橋上的入站或出站的消息進行受權。

爲此,您能夠向上一節中描述的匹配添加額外字段,以肯定匹配所需的權限。

要聲明登陸用戶的特定權限是必需的,以容許您使用該setRequiredAuthority字段的消息 

這是一個例子:

PermittedOptions inboundPermitted = new PermittedOptions().setAddress("demo.orderService"); // But only if the user is logged in and has the authority "place_orders" inboundPermitted.setRequiredAuthority("place_orders"); BridgeOptions options = new BridgeOptions().addInboundPermitted(inboundPermitted);

對於要受權的用戶,他們必須首先登陸,其次具備所需的權限。

要處理登陸並實際驗證,您能夠配置正常的Vert.x auth處理程序。例如:

Router router = Router.router(vertx); // Let through any messages sent to 'demo.orderService' from the client PermittedOptions inboundPermitted = new PermittedOptions().setAddress("demo.orderService"); // But only if the user is logged in and has the authority "place_orders" inboundPermitted.setRequiredAuthority("place_orders"); SockJSHandler sockJSHandler = SockJSHandler.create(vertx); sockJSHandler.bridge(new BridgeOptions(). addInboundPermitted(inboundPermitted)); // Now set up some basic auth handling: router.route().handler(CookieHandler.create()); router.route().handler(SessionHandler.create(LocalSessionStore.create(vertx))); AuthHandler basicAuthHandler = BasicAuthHandler.create(authProvider); router.route("/eventbus/*").handler(basicAuthHandler); router.route("/eventbus/*").handler(sockJSHandler);

處理事件總線橋事件

若是您但願在橋上發生事件時收到通知,則能夠在調用時提供處理程序 bridge

每當橋上發生事件時,它將被傳遞給處理程序。該事件由一個實例描述 BridgeEvent

該事件能夠是如下類型之一:

SOCKET_CREATED

建立新的SockJS套接字時將發生此事件。

SOCKET_IDLE

當SockJS套接字處於空閒狀態的時間比最初配置的時間長時,將發生此事件。

SOCKET_PING

當爲SockJS套接字更新最後一個ping時間戳時,將發生此事件。

SOCKET_CLOSED

當SockJS套接字關閉時,將發生此事件。

發送

當嘗試從客戶端向服務器發送消息時,將發生此事件。

發佈

嘗試從客戶端向服務器發佈消息時,將發生此事件。

接收

當嘗試將消息從服​​務器傳遞到客戶端時,將發生此事件。

寄存器

當客戶端嘗試註冊處理程序時,將發生此事件。

UNREGISTER

當客戶端嘗試取消註冊處理程序時,將發生此事件。

該事件使您可使用type並檢查事件的原始消息來檢索類型getRawMessage

原始消息是具備如下結構的JSON對象:

{
 「type」:「send」|「publish」|「receive」|「register」|「unregister」,
 「地址」:發送/發佈/註冊/未註冊的事件總線地址
 「身體」:信息的主體
}

該事件也是一個例子Future處理完事件後,您能夠完成未來的true進一步處理。

若是您不但願處理事件,您能夠完成將來false這是一個有用的功能,使您能夠對經過網橋的消息進行本身的過濾,或者可能應用一些細粒度的受權或指標。

這是一個例子,若是它們包含單詞「Armadillos」,咱們拒絕流過橋的全部消息。

Router router = Router.router(vertx); // Let through any messages sent to 'demo.orderMgr' from the client PermittedOptions inboundPermitted = new PermittedOptions().setAddress("demo.someService"); SockJSHandler sockJSHandler = SockJSHandler.create(vertx); BridgeOptions options = new BridgeOptions().addInboundPermitted(inboundPermitted); sockJSHandler.bridge(options, be -> { if (be.type() == BridgeEventType.PUBLISH || be.type() == BridgeEventType.RECEIVE) { if (be.getRawMessage().getString("body").equals("armadillos")) { // Reject it be.complete(false); return; } } be.complete(true); }); router.route("/eventbus/*").handler(sockJSHandler);

如下是如何配置和處理SOCKET_IDLE橋接事件類型的示例。請注意setPingTimeout(5000),若是ping消息未在5秒內從客戶端到達,則會觸發SOCKET_IDLE橋接事件。

Router router = Router.router(vertx); // Initialize SockJS handler SockJSHandler sockJSHandler = SockJSHandler.create(vertx); BridgeOptions options = new BridgeOptions().addInboundPermitted(inboundPermitted).setPingTimeout(5000); sockJSHandler.bridge(options, be -> { if (be.type() == BridgeEventType.SOCKET_IDLE) { // Do some custom handling... } be.complete(true); }); router.route("/eventbus/*").handler(sockJSHandler);

在客戶端JavaScript中,您使用'vertx-eventbus.js`庫來建立與事件總線的鏈接以及發送和接收消息:

<script src="http://cdn.jsdelivr.net/sockjs/0.3.4/sockjs.min.js"></script> <script src='vertx-eventbus.js'></script> <script>  var eb = new EventBus('http://localhost:8080/eventbus', {"vertxbus_ping_interval": 300000}); // sends ping every 5 minutes. eb.onopen = function() { // set a handler to receive a message eb.registerHandler('some-address', function(error, message) { console.log('received a message: ' + JSON.stringify(message)); }); // send a message eb.send('some-address', {name: 'tim', age: 587}); } </script>

該示例的第一件事是建立事件總線的實例

var eb = new EventBus('http://localhost:8080/eventbus', {"vertxbus_ping_interval": 300000});

構造函數的第二個參數告訴sockjs庫每5分鐘發送一次ping消息。由於服務器配置爲每隔5秒SOCKET_IDLE就會發生一次ping→ 將在服務器上觸發。

您還能夠修改原始消息,例如更改正文。對於從客戶端流入的消息,您還能夠向消息添加標頭,這是一個示例:

Router router = Router.router(vertx); // Let through any messages sent to 'demo.orderService' from the client PermittedOptions inboundPermitted = new PermittedOptions().setAddress("demo.orderService"); SockJSHandler sockJSHandler = SockJSHandler.create(vertx); BridgeOptions options = new BridgeOptions().addInboundPermitted(inboundPermitted); sockJSHandler.bridge(options, be -> { if (be.type() == BridgeEventType.PUBLISH || be.type() == BridgeEventType.SEND) { // Add some headers JsonObject headers = new JsonObject().put("header1", "val").put("header2", "val2"); JsonObject rawMessage = be.getRawMessage(); rawMessage.put("headers", headers); be.setRawMessage(rawMessage); } be.complete(true); }); router.route("/eventbus/*").handler(sockJSHandler);

CSRF跨站請求僞造

CSRF或有時也稱爲XSRF是一種未經受權的站點能夠獲取用戶私有數據的技術。Vert.x-Web包含一個處理程序CSRFHandler,可用於防止跨站點請求僞造請求。

在此處理程序下的每一個get請求中,cookie將使用惟一標記添加到響應中。而後,客戶端須要將此令牌返回到標頭中。因爲cookie被髮送,所以要求cookie處理程序也存在於路由器上。

在開發依賴User-Agent執行POST操做的非單頁應用程序時,沒法在HTML Forms上指定Headers。爲了解決這個問題,當且僅當在與表頭名稱相同的表單屬性中不存在標題時,還將檢查標題值,例如:

---
<form action="/submit" method="POST"> <input type="hidden" name="X-XSRF-TOKEN" value="abracadabra"> </form> ---

用戶有責任爲表單字段填寫正確的值。喜歡使用僅HTML解決方案的用戶能夠經過從X-XSRF-TOKEN 在CSRFHandler對象實例化期間選擇的鍵或標題名稱下的路由上下文中獲取標記值來填充此值

router.route().handler(CookieHandler.create()); router.route().handler(CSRFHandler.create("abracadabra")); router.route().handler(rc -> { });

使用AJAX

當經過ajax訪問受保護的路由時,須要在請求中傳遞csrf令牌。一般,這是使用請求標頭完成的,由於添加請求標頭一般能夠在中央位置輕鬆完成,而無需修改負載。

CSRF令牌是從密鑰下的服務器端上下文獲取的X-XSRF-TOKEN(除非您指定了不一樣的名稱)。須要將此令牌暴露給客戶端,一般是將其包含在初始頁面內容中。一種可能性是將其存儲在HTML <meta>標記中,而後能夠在JavaScript請求時檢索值。

如下內容能夠包含在您的視圖中(下面的車把示例):

<meta name="csrf-token" content="${X-XSRF-TOKEN}">

如下是使用Fetch API使用頁面上<meta>標記中的CSRF令牌發佈到/ process路由的示例:

// Read the CSRF token from the <meta> tag var token = document.querySelector('meta[name="csrf-token"]').getAttribute('content') // Make a request using the Fetch API fetch('/process', { credentials: 'same-origin', // <-- includes cookies in the request headers: { 'X-XSRF-TOKEN': token // <-- is the csrf token as a header }, method: 'POST', body: { key: 'value' } })

VirtualHost處理程序

虛擬主機處理程序將驗證請求主機名,若是匹配,它將向已註冊的處理程序發送請求,不然將在正常處理程序鏈內繼續。

根據Host標頭檢查請求是否匹配,模式容許使用通配符,例如,.vertx.io或徹底域名www.vertx.io

router.route().handler(VirtualHostHandler.create("*.vertx.io", routingContext -> { // do something if the request is for *.vertx.io }));

OAuth2AuthHandler處理程序

OAuth2AuthHandler容許使用的OAuth2協議安全的路線快速設置。此處理程序簡化了authCode流程。使用它來保護某些資源並使用Gi​​tHub進行身份驗證的示例能夠實現爲:

OAuth2Auth authProvider = GithubAuth.create(vertx, "CLIENT_ID", "CLIENT_SECRET"); // create a oauth2 handler on our running server // the second argument is the full url to the callback as you entered in your provider management console. OAuth2AuthHandler oauth2 = OAuth2AuthHandler.create(authProvider, "https://myserver.com/callback"); // setup the callback handler for receiving the GitHub callback oauth2.setupCallback(router.route()); // protect everything under /protected router.route("/protected/*").handler(oauth2); // mount some handler under the protected zone router.route("/protected/somepage").handler(rc -> rc.response().end("Welcome to the protected resource!")); // welcome page router.get("/").handler(ctx -> ctx.response().putHeader("content-type", "text/html").end("Hello<br><a href=\"/protected/somepage\">Protected by Github</a>"));

OAuth2AuthHandler將設置適當的回調OAuth2處理程序,所以用戶無需處理權限服務器響應的驗證。很是重要的是要知道權限服務器響應只有一次有效,這意味着若是客戶端發出從新加載回調URL,它將被聲明爲無效請求,由於驗證將失敗。

一條經驗法則是,一旦執行有效的回調,就會發出客戶端重定向到受保護資源的問題。此重定向還應建立會話cookie(或其餘會話機制),所以不要求用戶對每一個請求進行身份驗證。

因爲OAuth2規範的性質,爲了使用其餘OAuth2提供程序須要進行細微更改,但vertx-auth爲您提供了許多開箱即用的實現:

可是,若是您使用的是不公開的提供程序,您仍然可使用基本API執行此操做:

OAuth2Auth authProvider = OAuth2Auth.create(vertx, OAuth2FlowType.AUTH_CODE, new OAuth2ClientOptions() .setClientID("CLIENT_ID") .setClientSecret("CLIENT_SECRET") .setSite("https://accounts.google.com") .setTokenPath("https://www.googleapis.com/oauth2/v3/token") .setAuthorizationPath("/o/oauth2/auth")); // create a oauth2 handler on our domain: "http://localhost:8080" OAuth2AuthHandler oauth2 = OAuth2AuthHandler.create(authProvider, "http://localhost:8080"); // these are the scopes oauth2.addAuthority("profile"); // setup the callback handler for receiving the Google callback oauth2.setupCallback(router.get("/callback")); // protect everything under /protected router.route("/protected/*").handler(oauth2); // mount some handler under the protected zone router.route("/protected/somepage").handler(rc -> rc.response().end("Welcome to the protected resource!")); // welcome page router.get("/").handler(ctx -> ctx.response().putHeader("content-type", "text/html").end("Hello<br><a href=\"/protected/somepage\">Protected by Google</a>"));

您須要手動提供提供商的全部詳細信息,但最終結果是相同的。

處理程序將爲您的應用程序固定配置的回調URL。用法很簡單,由於爲處理程序提供路由實例,全部設置都將爲您完成。在典型的用例中,您的提供商會詢問您的應用程序的回調網址是什麼,而後輸入如下網址:https://myserver.com/callback這是處理程序的第二個參數,如今您只須要設置它。爲了使最終用戶更容易,您只需調用setupCallback方法便可。

這是您將處理程序固定到服務器的方式https://myserver.com:8447/callback請注意,端口號對於默認值不是必需的,對於http爲80,對於https爲443。

OAuth2AuthHandler oauth2 = OAuth2AuthHandler.create(provider, "https://myserver.com:8447/callback"); // now allow the handler to setup the callback url for you oauth2.setupCallback(router.route());

在示例中,路由對象是內聯建立的,Router.route()可是若是要徹底控制調用處理程序的順序(例如,您但願在鏈中儘快調用它),則始終能夠建立路徑對象並將其做爲此方法的引用傳遞給它。

一個現實世界的例子

到目前爲止,您已經學會了如何使用Oauth2 Handler,可是您會注意到每一個請求都須要進行身份驗證。這是由於處理程序沒有狀態,而且示例中沒有應用狀態管理。

雖然對於面向API的端點,建議不使用狀態,例如,對於面向用戶的endpoinst使用JWT(咱們將在後面介紹),咱們能夠將身份驗證結果保存在會話中。爲此,咱們須要一個相似如下代碼段的應用程序:

router.route()
  .handler(CookieHandler.create()); // Simple auth service which uses a GitHub to // authenticate the user OAuth2Auth authProvider = GithubAuth.create(vertx, "YOUR PROVIDER CLIENTID", "YOUR PROVIDER CLIENT SECRET"); // We need a user session handler too to make sure // the user is stored in the session between requests router.route() .handler(SessionHandler.create(LocalSessionStore.create(vertx)).setAuthProvider(authProvider)); // we now protect the resource under the path "/protected" router.route("/protected").handler( OAuth2AuthHandler.create(authProvider) // we now configure the oauth2 handler, it will // setup the callback handler // as expected by your oauth2 provider. .setupCallback(router.route("/callback")) // for this resource we require that users have // the authority to retrieve the user emails .addAuthority("user:email") ); // Entry point to the application, this will render // a custom template. router.get("/").handler(ctx -> ctx.response() .putHeader("Content-Type", "text/html") .end( "<html>\n" + " <body>\n" + " <p>\n" + " Well, hello there!\n" + " </p>\n" + " <p>\n" + " We're going to the protected resource, if there is no\n" + " user in the session we will talk to the GitHub API. Ready?\n" + " <a href=\"/protected\">Click here</a> to begin!</a>\n" + " </p>\n" + " <p>\n" + " <b>If that link doesn't work</b>, remember to provide\n" + " your own <a href=\"https://github.com/settings/applications/new\">\n" + " Client ID</a>!\n" + " </p>\n" + " </body>\n" + "</html>")); // The protected resource router.get("/protected").handler(ctx -> { // at this moment your user object should contain the info // from the Oauth2 response, since this is a protected resource // as specified above in the handler config the user object is never null User user = ctx.user(); // just dump it to the client for demo purposes ctx.response().end(user.toString()); });

混合OAuth2和JWT

有些提供商使用JWT令牌做爲訪問令牌,這是RFC6750的一項功能, 當人們但願混合基於客戶端的身份驗證和API受權時,它很是有用。例如,假設您有一個提供一些受保護HTML文檔的應用程序,但您也但願它可供API使用。在這種狀況下,API沒法輕鬆執行OAuth2所需的重定向握手,但可使用提早提供的令牌。

只要提供程序配置爲支持JWT,處理程序就會自動處理。

在現實生活中,這意味着您的API可使用Authorization帶有值的標頭訪問受保護的資源Bearer BASE64_ACCESS_TOKEN

相關文章
相關標籤/搜索