Vert.x-Web的讲解和使用(一)

时间:2021-01-02 18:01:27


Vert.x-Web

Vert.x-Web是vert.x的web构建模块。

你可以使用它构建现代的可扩展的web应用程序。

Vert.x提供了一些相当底层的方式处理HTTP请求,但是对于大多数的应用程序这些也就够用了。

Vert.x-Web基于Vert.x-Core为更容易的构建真正的web应用程序提供了丰富的功能。

Vert.x-Web的设计灵感来自Node.js和Ruby,并且成功的继承了Vert.x.2.x版本。

Vert.x-Web是强大的,灵活以及可嵌入的。你可以使用其中的部分功能或者完全抛弃它来构建自己的应用程序。还有Vert.x不是一个容器。

你可以使用Vert.x来构建经典的WEB应用程序,基于restfull的Web应用程序,实时通信的web应用程序或者任何类型的web应用程序。Vert.x完全可以胜任这些工作。创建什么类型的WEB应用程序完全是有你来决定,Vert.x仅仅提供支持而已。

Vert.x-Web是一个极好的编写RESTful服务的,但是我们不强迫你写这样的程序。

关于一些Vert.x的关键特征:

路由(基于方法、路径等)

正则表达式模式匹配的路径

从路径提取参数

内容协商

请求主体处理

身体大小限制

Cookie解析和处理

多表单上传

多文件上传

子路由器支持

会话支持——本地和集群

交叉起源资源共享支持

错误页面处理程序
基本身份验证
基于重定向的身份验证
授权处理程序
JWT基于授权
用户/角色/权限授权
标识处理

服务器端呈现的模板支持,包括支持以下模板引擎的:
Handlebars
Jade
MVEL
Thymeleaf

响应时间处理程序
静态文件服务,包括缓存逻辑和目录清单。
请求超时的支持

SockJS支持
事件总线桥

大多数的Vert.x的特性被实现为处理事件(Handler),这样你就可以自己实现它。随着时间的推移我们将加入更多特性。

下面我们将讨论这些特性

使用Vert.x-Web

如果你想要使用Vert.x可以在你的构建配置文件中添加如下依赖关系:

Maven(在你的pom.xml):

<dependency>

<groupId>{maven-groupId}</groupId>

<artifactId>{maven-artifactId}</artifactId>

<version>{maven-version}</version>

</dependency>

Gradle(在你的build.gradle):

compile {maven-groupId}:{maven-artifactId}:{maven-version}

Re-cap 基于 Vert-x核心HTTP 服务器

 Vert.x-Web基于Vert.x核心的公开API.所以在你使用Vert.x-Web的时候你应该对vert.x的核心API有必要的理解。

下面我们将带你熟悉一下Vert.x的核心HTTP文档。

这里有一个使用Vert.x核心API编写的《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);

我们创建了一个HTTPServer的实例,并且设置了一个请求处理事件,当一个请求到达服务器的时候,事件将被激发并作出应答。

当请求到达服务器时,我们将设置content-type为Text/plain并且输出Hello World!的文字。这样一次请求就结束了。

最后我们将服务绑定到8080端口。

然后你可以在浏览器中访问http://localhost:8080,如果出现Hello World! 说明我们的Web服务启动成功!!

基本Vert.x-Web概念

Router 是Vert.x的核心概念之一。它的一个对象可以维护零个或多个路径。

Router获得一个HTTP请求然后找到与这个请求相匹配的路径,并将这个请求转交到这个路径上。

路径上可以关联处理事件,路径接收的请求后,触发处理事件,处理完成后结束或者转到下一个程序处理。

下面是一个简单的Router的例子:

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::accept).listen(8080);

这个例子是我们上一章所讲的Hello World。只不过这次使用Vert.x-Web实现。

我们先创建了一个HTTPServer,然后创建了一个Router。这次我们创建了一个简单的没有任何匹配规则的Router,这意味着它将匹配所有到达服务器的请求。

然后我们为Router创建处理程序。处理程序将响应所有到达服务器的请求。

这个Router将进入标准的RoutingContext处理程序(包含标准的HttpServerRequest和HttpServerResponse)而其他的东西则使Vert.x-Web工作更简单。

每一个请求都是一个路径,每一个路径都有相应的路径的上下文实例,这些请求将被相同的上下文实例的处理程序处理。

一旦我们设置了处理程序后,所有到达服务起的请求将被处理程序响应。

处理请求和调用下一个处理程序

当Vert.x-Web决定由一个路径的处理程序请求另一个路径的处理程序时,你只需要调用下一个路径的处理程序并将RoutingContext对象传入处理程序。

如果在当前处理程序中你不想结束请求,你还可以将请求转入到另一个相匹配的处理程序中。

你不是必须马上调用下一个处理程序。你可以为下个处理程序调用设置延时,让其在多长时间之后运行:

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

   // Call the next matching route after a 5 second delay
   routingContext.vertx().setTimer(5000, tid -> routingContext.next());
 });

 Route route2 = router.route("/some/path/").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 route3 = router.route("/some/path/").handler(routingContext -> {

   HttpServerResponse response = routingContext.response();
   response.write("route3");

   // Now end the response
   routingContext.response().end();
 });

在上面的例子中:route1 写入响应 等待5秒后 ,调用route2 , 然后在等待5秒后 route3运行 最后响应结束。

注意:所有这一切发生的时候 没有线程处于阻塞状态。

使用阻塞处理程序

有时候,你需要做一些花费时间的事情,需要处理程序循环一段时间。比如:调用遗留阻塞API或者做一些密集的计算。

使用平常的处理程序你不能做这样的事情,所以我们提供了可阻塞的处理程序。

可阻塞的处理程序看起来就像一个普通的处理程序,但是在Vert.x中它使用的是一个工作池里的线程,而不是通常使用的循环。

你可以为一个路径(route)添加一个blockingHandler。比如这样:

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

   // Do something that might take some time synchronously
   service.doSomethingThatBlocks();

   // Now call the next handler
   routingContext.next();

 });

默认情况下:所有阻塞程序运行相同的上下文时,这意味着在当前处理程序执行完成之前,下一个将不被执行。如果你不关心执行顺序和不在意你的阻塞处理程序并行执行,你可以设置特殊的指令屏蔽它。

没看明白附上原文:

By default, any blocking handlers executed on the same context (e.g. the same verticle instance) are ordered - this means the next one won’t be executed until the previous one has completed. If you don’t care about orderering and don’t mind your blocking handlers executing in parallel you can set the blocking ha ndler specifying ordered as false using blockingHandler.

路经的设置

一个Route可以设置请求的URI匹配路径,在本例中,他将匹配任何相同的请求路径。

在接下来的例子中这个处理程序将会响应/some/path/路径的请求。Route将会忽略斜杠(/)所以它也将响应 /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`
 });

Route路经的开始

经常我们需要访问特定的开头的路径,要做到这一点,我们可以使用一个表达式。有一个更简单的方法就是在定义路径的时候使用星号(*)作为路径的结束。

如下的例子中 这个处理程序将响应所有以/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 route = router.route("/some/path/*");

 route.handler(routingContext -> {
   // This handler will be called same as previous example
 });

Route捕获路径参数

我们可以使用占位符来匹配路径用来捕获路径参数。

比如这样:

 Route route = router.route(HttpMethod.POST, "/catalogue/products/:productype/: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/ 然后Route会匹配为:productType的值为 tools和productID的值为drill123.

Route使用正则表达式

Route可以使用正则表达式。

 Route route = router.route().pathRegex(".*foo");

 route.handler(routingContext -> {

   // This handler will be called for:

   // /some/path/foo
   // /foo
   // /foo/bar/wibble/foo
   // /foo/bar

   // But not:
   // /bar/wibble
 });

另外在创建Route的时候也可以指定正则表达式:

 Route route = router.routeWithRegex(".*foo");

 route.handler(routingContext -> {

   // This handler will be called same as previous example

 });

Route使用正则表达式捕获路径参数

你可以使用正则表达式捕获路径参数。这里有个例子:

 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.

抓取和抓取组都使用正则表达式。

捕获指定提交方法的路径

默认情况下 Route会匹配所有提交的HTTP请求,不论是get提交,还是post提交。

如果你想让Route匹配特殊的HTTP提交方式,你可以使用 method()方法:

 Route route = router.route().method(HttpMethod.POST);

 route.handler(routingContext -> {

   // This handler will be called for any POST request

 });

或者你可以在创建Route的时候指定:

 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/

 });

如果你想让 Route匹配特定的HTTP提交方式,你还可以使用其他的方法 比如: get() post() put()。像下面的例子那样:

 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`

 });
如果你想匹配多个提交方式的话,你可以多次调用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

 });

Route的顺序

Route的默认顺序就是你添加时的顺序。

当一个请求到达的时候,Router会遍历每个Route并且检查是否匹配,如果匹配的话对应的处理程序将被唤醒,用来处理请求。

如果处理程序随后调用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

route2

route3

这些Route按照添加的顺序来响应来自/some/path的请求。

如果你想改变默认的排序,你仅需要为Route的order()方法指定一个特殊的整数值。

Route在创建的时候被分配一个序号,并且按照这个序号的前后顺序被添加到Router。第一个的编号为0 第二个编号为1 。。。。

通过指定Route的顺序,你可以改变Route的默认顺序。如果你想让一个Route在0之前执行的话,序号可以是一个负数。

让我们改变默认的顺序,让route2在route1前运行:

 Route route1 = router.route("/some/path/").handler(routingContext -> {

   HttpServerResponse response = routingContext.response();
   response.write("route1\n");

   // Now call the next matching route
   routingContext.next();
 });

 Route route2 = 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("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();
 });

 // Change the order of route2 so it runs before route1
 route2.order(-1);

现在的响应内容将变成这样的:

route2

route1

route3

如果两个相匹配的Route拥有相同的序号的时候,他们的执行顺序将按照添加的顺序来执行(即默认顺序)。

如果你想要一个Route在最后执行的话,你只需要调用last()方法。

Route设置请求的MIME类型

你可以使用consumes()方法设置一个Route指定匹配的request的MIME类型。

在这种情况下,RquestBody中将包含一个Content-type用于指定请求的MiME的类型,MIME类型的值将被comsumes()设置的值匹配。

基本上。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`

 });
也可以匹配多个MIME类型:

 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

 });
还可以匹配指定结尾的MIME类型:

 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

 });

如果你不指定带/的类型,它会认为你使用自定义的类型。


组合使用Route匹配

你可以使用不同的方法组合我们上面讲过的Route的方法。
例如:

 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"

 });

禁用和启用Route

你可以使用route的disable()方法禁用Route,一个禁用的Route在匹配的时候将会被忽略。
同样你可以使用Route的enable()方法启用它。


上下文数据

如果你想要在一次请求的两个处理程序之间传输数据的话,你可以将数据保存在RoutingContext中。
这里有一个例子,我们在一个处理程序中将一点数据放到上下文中存储并且在下一个处理程序中再次拿到它。
你可以放进上下文中任何对象,并且能从上下文中把它取出来。
请求 /some/path/other将会匹配如下两个Route。

 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()方法获得RoutingContext的上下文的Map集合。

Router挂载

有时你把很多的处理程序有意义的分割到多个Router中.如果你想在不同的应用程序或者不同的跟路径下使用他们的话,下面的内容就相当有用了。

你可以将一个Router挂载到另一个Router下,这样就可以做到Router的重复利用了。这个挂载在别的Router下的Router就被叫为子Router。

如果你需要的话子Router下还可以挂载其他的子Router,这样你就可以有几个级别的Router了。

让我们看一个将子Router挂载到其他Router的例子:

这个子Router维护了一些简单的REST的API的处理程序,我们将它挂载到另一个Router下。在此我们就不实现这个REST API了。

这是子Router:

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

 });
如果这个Router作为一个*的Router,那么GET/PUT/DELETE请求就会被响应。 就像/products/product1234会成功调用API。

比如说,我们有一个被另一个Router描述的网站。

 Router mainRouter = Router.router(vertx);

 // Handle static resources
 mainRouter.route("/static/*").handler(myStaticHandler);

 mainRouter.route(".*\\.templ").handler(myTemplateHandler);

我们可以定义一个挂载点/productsAPI用于挂载子Router到这个主Router下。

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

这意味着REST API的可访问路径编程了/productsAPI/products/product1234.

默认的404处理程序

如果没有相匹配的Route.Vert.x将会抛出404响应。

我们可以定义自己的错误处理程序,或者提供可用的错误增强程序,如果没有私有的错误处理程序,Vert.x将会抛出404(Not Found)响应。



本翻译只为抛砖引玉,有什么不到位的地方希望大家能够海涵(小弟英语水平菜鸟一枚),有什么错误还请诸位大大不吝赐教,小弟将不胜感激。

本翻译只为抛砖引玉,有什么不到位的地方希望大家能够海涵(小弟英语水平菜鸟一枚)