在Vert.x 创建HTTP服务 中我们已经创建了一个简单的HttpServer,但这个HttpServer比较低级,对于请求参数解析、Session等常用功能都需要我们通过编码实现,也就是要重复造轮子,非常不方便。

Vert.x提供了Web开发组件vertx-web,提供了一堆Web开发中常用的功能。比如参数封装,路由,国际化,认证和授权,session和cookie以及模板等,可以非常方便的进行Vert.x Web开发。

本篇主要介绍Web开发中的路由功能,路由简单说就是把用户请求交给合适的处理器处理的组件。如下图所示

路由是Web开发中最基础也是最常用的功能,Vert.x提供了强大的路由功能,包括正则匹配,二级路由等。本文从两个方面来讲述路由,分别是路由的使用和路由的实现原理(源码阅读)

一、基本使用

1. 基本路由实现

1.在pom文件中,加入vertx-web的依赖包

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

2.创建一个HttpServer

/*** 简单的路由使用* * @author lenovo**/
public class SimpleRouter extends AbstractVerticle {@Overridepublic void start() throws Exception {// 创建HttpServerHttpServer server = vertx.createHttpServer();// 创建路由对象Router router = Router.router(vertx);// 监听/index地址router.route("/index").handler(request -> {request.response().end("INDEX SUCCESS");});// 把请求交给路由处理--------------------(1)server.requestHandler(router::accept);server.listen(8888);}public static void main(String[] args) {Vertx.vertx().deployVerticle(new SimpleRouter());}}

上面这段代码还是比较好理解的,主要就是在Vert.x 创建HTTP服务的基础上增加了Router,并且最后把请求的处理交给Router来处理。这样当我们访问服务器时,就会根据匹配规则,找到对应的处理器来进行处理。

这里有一个地方,就是在代码中标(1)的部分,router::accept这个可能很多朋友不理解,这个也是JDK8的一个新特性,实际上就是一个语法糖,下面是一段不使用JDK8的代码,想必大家看了下面这段代码就都名白了。router是我们创建的router对象,然后把回调的值传给router对象的accept方法。

server.requestHandler(new Handler<HttpServerRequest>() {@Overridepublic void handle(HttpServerRequest event) {router.accept(event);}
});

通过比较,我们会发现,使用JDK8 的新特性,代码上还是相对会简洁很多的。

学习都有一个2.8原则,就是说学习80%的知识可能只用20%的时间,路由也是一样,通过上面这些你实际上已经可以去进行路由了,所以也算是路由的80%的东西了,剩下的20%你可能需要花费更多的时间。

2. 限制HTTP请求方法

HTTP协议中定义的请求方法有GET POST PUT DELETE等,我们之前通过在浏览器地址栏输入的都是get请求,如果我们要限制,只能使用POST请求该如何处理呢?也非常简单,可以直接通过router对象提供了post方法来进行路径的匹配。

router.post("/post").handler(request -> {request.response().end("post");
});

当我们在浏览器直接请求时,你会看到如下结果

当你使用POST发送时,结果如下;

   

除了post方法以外,还可以使用put,get等方法。除了直接使用这些方法以外,还有另外一种形式,也可以指定请求的方法,route方法有个重载,第一个参数是HttpMethdo,第二个参数是匹配的URL路径,如下:

router.route(HttpMethod.GET, "/method").handler(request -> {request.response().end("method");
});

HttpMethod是一个枚举类,可用的枚举值如下

@VertxGen
public enum HttpMethod {OPTIONS, GET, HEAD, POST, PUT, DELETE, TRACE, CONNECT, PATCH, OTHER
}

3. 二级路由

我们经常会遇到一些情形,比如要对产品做增删改查,我们可能会有如下路由规则的定义

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

这样虽然能够满足我么的要求,但是,如果比较多的话明显的看起来比较乱,Vertx给我们提供了二级路由来解决这个问题。我们可以直接把上面的restAPI这个作为子路由,挂载到一个主路由中。也就是说,我们创建一个主路由,然后把上面的restAPI这个路由规则通过mount方法挂载就可以了。

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

4. 指定路由顺序(Order)

我们可能会配置很多的路由规则,而Vert.x进行路由匹配的规则非常简单,默认就是当匹配成功之后就不再继续匹配了。比如我们有如下代码

router.route("/index/*").handler(request -> {request.response().end("Index");
});router.route("/index/main").handler(request -> {request.response().end("IndexMain");
});

当我们访问:http://localhost:8080/index/main 的时候,我们希望匹配显然是下面的,我们希望返回的是 IndexMain,而实际情况是返回是Index。为了解决这个问题,我们有两种办法,第一个是把下面的代码放到上面,但这样可能也并不符合我们的习惯,还有另外一种方式就是制定order。

router.route("/index/*").order(2).handler(request -> {request.response().end("Index");
});router.route("/index/main").order(-1).handler(request -> {request.response().end("IndexMain");
});

还有一种情况,如果想要所有匹配的规则都执行,可以通过requestContext 的next方法

router.route("/index/*").order(2).handler(request -> {
//  request.response().end("Index");System.out.println(1);request.next(); // 调下一个匹配规则
});router.route("/index/main").order(-1).handler(request -> {
//  request.response().end("IndexMain");System.out.println("2");request.next(); // 调下一个匹配规则
});

5.参数获取

用户经常会上传一些参数,那么我们在Vertx中如何接收用户上传的参数呢?

第一种就是get请求直接拼接在URL后的参数,比如:http://localhost:8080/method?param=hello

router.route(HttpMethod.GET, "/method").handler(request -> {String param = request.request().getParam("param");System.out.println("接收到用户传递的参数为:" + param);request.response().end("method");
});

第二种是获取路径的参数,比如:http://localhost:8080/method/xiaoming/xm123

// 获取参数
router.route(HttpMethod.GET, "/method/:user/:pass").handler(request -> {String user = request.request().getParam("user");String pass = request.request().getParam("pass");request.response().putHeader("Content-type", "text/html;charset=utf-8").end("接收到的用户名为:" + user + " 接收到的密码为:" + pass);
});

第三种是获取到请求体中的数据,也就是post提交的数据。这个稍微有一些繁琐,首先要指定一个BodyHandle,然后才能通过requestContext对象来获取body体中的数据。

router.route().handler(BodyHandler.create()); // 在路由的最前面,指定body的处理器

获取body体的数据的方法有很多,可以获取到字符串,可以直接转成Json对象等等,下面是直接读取到字符串

router.post("/post").handler(request -> {String res = request.getBodyAsString(); // 获取到body体的数据System.out.println(res);request.response().end("post");
});

6. 请求处理分发到具体的类

上面的代码,我们把接收用户请求和处理用户请求的逻辑放到了一个类里,这显然是不好的,会导致代码的结构不清晰,多个人在一起维护的时候变得困难。

我们可以把具体的请求处理逻辑和监听分开,使用单独的类来做业务的处理,这非常简单。我们来重写最开始处理index监听的代码、

1.创建一个类,实现Handler接口

public class IndexHandle implements Handler<RoutingContext> {@Overridepublic void handle(RoutingContext event) {event.response().end("IndexController");}}

2.修改监听,在监听到index请求时,将处理器指定为IndexHandle

// 监听/index地址
router.route("/index").handler(new IndexHandle());

这里的Handle是不是看起来就像我们之前所写的Controller,当然,这就是Controller。

7.特定的处理器

为了处理index地址的请求,我们创建了一个IndexHandler。对于不同的业务,可以创建不同的处理器来处理,但往往还有一些,比如静态资源处理,Session处理器,请求体处理器。像这些处理器几乎在所有的Web开发场景中都会使用到,而且这些处理器和业务无关,所以每次写就会重复造轮子,官方已经给我们提供了非常多的好用的处理器。如下图所示

二、实现原理

如果你熟读源码,你会发现,Vert.x体系结构设计的非常美妙,有着非常好的扩展性。Vert.x的Web模块就可以看做是对核心模块的扩展,下面就看看Web模块中是如何实现路由功能的。

上面已经用过路由了,代码非常简单,为了便于大家的阅读,再次贴出来核心代码,如下

// 创建HttpServer
HttpServer server = vertx.createHttpServer();// 创建路由对象
Router router = Router.router(vertx);// 监听/index地址
router.route("/index").handler(request -> {request.response().write(Buffer.buffer("INDEX SUCCESS")).end();
});// 把请求交给路由处理
server.requestHandler(router);

这里先跟大家解释下,Vertx路由的使用,也就是本文上半部分的内容,是在18年7月份写的,而源码部分是在2019年4月份写的,在这个阶段里,Vert.x经历了几个版本的更新,API也有些许改变。其中,router就有了改版,当然新的版本也是兼容旧的版本的。之前把请求交给路由处理是这么写的

在新版本中已经提示过期了,新版本中的写法就是上面代码中的写法,直接把router传进入就可以了,不再需要使用方法引用的形式。

我们源码的阅读就从这里开始!

requestHandler接收的参数类型是 Handler<HttpServerRequest> ,方法的定义如下

HttpServer requestHandler(Handler<HttpServerRequest> handler);

requestHandler可以直接接收router作为实参,说明router就一定是Handler<HttpServerRequest> 类型,看router类的定义,果然如我们所猜想,如下

public interface Router extends Handler<HttpServerRequest> {}

既然继承了Handler接口,就一定需要实现Handler接口中定义的方法,而Handler接口中只定义了一个方法,就是

void handle(E event);

当请求进来的时候,会找到requestHandler,进而调用Handler接口的handle方法,下面就要看下Router实现类的handle方法的实现了

@Override
public void handle(HttpServerRequest request) {if (log.isTraceEnabled()) log.trace("Router: " + System.identityHashCode(this) +" accepting request " + request.method() + " " + request.absoluteURI());new RoutingContextImpl(null, this, request, routes).next();
}

这里核心的代码就是创建了一个RoutingContext的实例,并调用其next方法,直接进入到next方法

@Override
public void next() {if (!iterateNext()) {checkHandleNoMatch();}
}

凭我们使用路由的经验,我们可以指定多个路由,且每个路由都可以指定order,且在单个route中可以调用next方法来执行下一个匹配的路由,因此这里相当于一个路由链,通过next方法关联起来。

而iterateNext就是在执行匹配到的路由链,直到执行完毕最后一个,进入到checkHandlerNoMatch方法。

其实路由的核心就在于这个iterateNext方法的实现,代码比较多,分开来看,先看一部分,如下

while (iter.hasNext()) { // Search for more handlersRouteImpl route = iter.next();currentRouteNextHandlerIndex.set(0);currentRouteNextFailureHandlerIndex.set(0);try {if (route.matches(this, mountPoint(), failed)) {if (log.isTraceEnabled()) log.trace("Route matches: " + route);try {currentRoute = route;if (log.isTraceEnabled()) log.trace("Calling the " + (failed ? "failure" : "") + " handler");if (failed && currentRoute.hasNextFailureHandler(this)) {currentRouteNextFailureHandlerIndex.incrementAndGet();route.handleFailure(this);} else if (currentRoute.hasNextContextHandler(this)) {currentRouteNextHandlerIndex.incrementAndGet();route.handleContext(this);} else {continue;}} catch (Throwable t) {if (log.isTraceEnabled()) log.trace("Throwable thrown from handler", t);if (!failed) {if (log.isTraceEnabled()) log.trace("Failing the routing");fail(t);} else {// Failure in handling failure!if (log.isTraceEnabled()) log.trace("Failure in handling failure");unhandledFailure(-1, t, route.router());}}return true;}} catch (Throwable e) {if (log.isTraceEnabled()) log.trace("IllegalArgumentException thrown during iteration", e);// Failure in matches algorithm (If the exception is instanceof IllegalArgumentException probably is a QueryStringDecoder error!)if (!this.response().ended())unhandledFailure((e instanceof IllegalArgumentException) ? 400 : -1, e, route.router());return true;}
}

iter是成员变量

protected Iterator<RouteImpl> iter;

是routes的迭代器

this.iter = routes.iterator();

那么while(iter.hasNext()) 就是要迭代所有的route,然后进行匹配,进而为匹配到的route,执行route的handle方法。如果一切条件成立,执行

进入到handleContext方法,代码如下

void handleContext(RoutingContextImplBase context) {Handler<RoutingContext> contextHandler;synchronized (this) {contextHandler = contextHandlers.get(context.currentRouteNextHandlerIndex() - 1);}contextHandler.handle(context);
}

先获取到当前的处理器,调用处理器的handle方法,也就是第一部分中我们所写的回调方法,类似于这种

这里所写的request,就是上面传入的context,也就是RoutingContext对象。

到这里,我们已经完成了一个从请求过来,交给路由处理,并执行处理器方法这么一整个流程。你对路由的理解是不是更深刻了呢?

Vert.x相关系列文章

(一)Vert.x 简明介绍 https://blog.csdn.net/king_kgh/article/details/80772657

(二)Vert.x创建简单的HTTP服务 https://blog.csdn.net/king_kgh/article/details/80804078

(三)Vert.x Web开发之路由 https://blog.csdn.net/king_kgh/article/details/80848571

(四)Vert.x TCP服务实现 https://blog.csdn.net/king_kgh/article/details/84870775

(五)Vert.x数据库访问 https://blog.csdn.net/king_kgh/article/details/84894599

(六)Vert.x认证和授权 https://blog.csdn.net/king_kgh/article/details/85218454

(七)Vert.x事件总线(Event Bus)与远程服务调用 https://blog.csdn.net/king_kgh/article/details/86993812

Vert.x 案例代码:https://github.com/happy-fly

笔者就职于一家互联网支付公司,公司的核心项目使用的是Vert.x技术体系。记得笔者刚进入公司,接触Vert.x的时候,找遍了大大小小的网站,发现市面上关于Vert.x的文档除了官方文档外,几乎找不到其他资料。当时就励志要出一个专栏来写写Vert.x,以此帮助在Vert.x上采坑的朋友。因为笔者能力有限,文章中难免会有错误和疏漏,如果您对文章有意见或者好的建议,可以直接留言或者发送邮件到18366131816@163.com。如果您也对Vert.感兴趣,可以加入到我们,共同学习Vert.x,并推进国内开发者对Vert.x的认知。

Vert.x(vertx) Web开发-路由相关推荐

  1. Web开发中的路由是什么意思?(关键词:Web开发/路由)

    路由就是URL到函数的映射. 在web开发中,"route"是指根据url, 分配到对应的处理程序. 路由: 就是一个路径的解析,根据客户端提交的路径,将请求解析到相应的控制器上: ...

  2. Vert.x(vertx) 认证和授权详解(包含认证和授权在Web系统中的使用)

    每个线上系统几乎都是离不开认证和授权的,Vert.x提供了灵活.简单.便捷的认证和授权的支持.Vert.x抽象出了两个核心的认证和授权的接口,一个是AuthProvider,另一个是User.通过这两 ...

  3. web开发与django认识 MVC和MVT的区别 路由的匹配

    文章目录 一.web开发 1.简介 2.客户端服务器请求过程 3.web开发思维 二.Django 1.Django简介 2.Django与flask的优缺点 3.Django的环境安装 4.virt ...

  4. Vert.x(vertx) 实现TCP服务

    对于Java开发人员,想要实现一个http服务,非常简单,写个servlet,打成war包,放到tomcat下就能运行.但如果要实现一个tcp服务就没那么简单了,因为tcp是传输层协议,并不像http ...

  5. Vert.x(vertx) 创建HTTP服务

    Vert.x底层通信框架依赖于Netty,并封装了对Http协议的支持,因此可以非常方便的进行Web开发,且不依赖于任何中间件.笔者所在的公司老系统使用的是SSM架构的项目,部署在Weblogic上, ...

  6. Vert.x(vertx) 连接MySQL、Oracle数据库

    Vert.x提供异步访问数据库的API,可能这里有朋友会有疑惑,直接使用我们之前的熟悉的Mybatis或者Hibernate不行吗,可行,但数据库操作是一个耗时操作,使用传统的同步模型,容易阻塞线程, ...

  7. Vert.x(vertx) 事件总线(EventBus)与 远程服务调用

    Event Bus(事件总线) 是Vert.x的神经系统,负责应用系统消息的传递.Vert.x各模块(Verticle)之间的相互调用就是通过Event Bus实现的,因此各Verticle之间是高度 ...

  8. Vert.x(vertx) 简明介绍

    摘要 Vert.x最大的特点就在于异步(底层基于Netty),通过事件循环(EventLoop)来调起存储在异步任务队列(CallBackQueue)中的任务,大大降低了传统阻塞模型中线程对于操作系统 ...

  9. Vert.x(vertx)入门资料

    1.vert.x简介 vert.x 采用类似 Node.js 的 eventloop callback 机制,优势是 Eventloop 是单线程场景下几乎是最快的并发解决方案,但也需要周边生态的支持 ...

最新文章

  1. 汇总 | 深度学习中图像语义分割基准数据集详解
  2. 86.git使用 建立和克隆远程仓库
  3. 35岁的程序员正在消失?No,我认识了一个50岁的程序员!
  4. java中用iterator去检查最大值_Java中的Iterator vs forEach
  5. 正向代理与反向代理;
  6. zookeeper命令行操作
  7. 基于51单片机的时钟系统
  8. Cross compile webrtc for ios on mac os.
  9. Linux基础-编译安装Python
  10. pm2启动jenkins不存在tty的问题
  11. Java从入门到精通+第三版.pdf
  12. SFML1 俄罗斯方块代码解析
  13. 计算机核心期刊加拿大,ssci或cssci期刊北京大学图书馆版核心期刊国外学术.doc...
  14. Kali Linux 暴力破解 wifi密码
  15. 四巨头键盘钢琴音源完整版-Spectrasonics Keyscape v1.1.3C WiN-MAC
  16. 广州市黄埔区水利工程管理划定带动水利人才上升
  17. postgresql中实现按周统计详解
  18. Observable与Subject
  19. 随机梯度下降算法 入门介绍(最通俗易懂)
  20. 前端CSS核心部分盒子模型

热门文章

  1. 企业微信 之 创建应用并添加至侧边栏
  2. JAVA汉字拼音解析
  3. CityMaker研修之路 02 伟景行(CityMaker)的倾斜之路
  4. Java实现十种行为型模式
  5. ping——判断两个设备是否在同一个局域网下
  6. 洛谷P1367 蚂蚁
  7. 实用解析dmp文件内容
  8. Python调用百度AI接口
  9. 《日语综合教程》第七册 第六課 自然と人間
  10. 人工智能知识全面讲解:特征工程