Vert.x(vertx) Web开发-路由
在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开发-路由相关推荐
- Web开发中的路由是什么意思?(关键词:Web开发/路由)
路由就是URL到函数的映射. 在web开发中,"route"是指根据url, 分配到对应的处理程序. 路由: 就是一个路径的解析,根据客户端提交的路径,将请求解析到相应的控制器上: ...
- Vert.x(vertx) 认证和授权详解(包含认证和授权在Web系统中的使用)
每个线上系统几乎都是离不开认证和授权的,Vert.x提供了灵活.简单.便捷的认证和授权的支持.Vert.x抽象出了两个核心的认证和授权的接口,一个是AuthProvider,另一个是User.通过这两 ...
- web开发与django认识 MVC和MVT的区别 路由的匹配
文章目录 一.web开发 1.简介 2.客户端服务器请求过程 3.web开发思维 二.Django 1.Django简介 2.Django与flask的优缺点 3.Django的环境安装 4.virt ...
- Vert.x(vertx) 实现TCP服务
对于Java开发人员,想要实现一个http服务,非常简单,写个servlet,打成war包,放到tomcat下就能运行.但如果要实现一个tcp服务就没那么简单了,因为tcp是传输层协议,并不像http ...
- Vert.x(vertx) 创建HTTP服务
Vert.x底层通信框架依赖于Netty,并封装了对Http协议的支持,因此可以非常方便的进行Web开发,且不依赖于任何中间件.笔者所在的公司老系统使用的是SSM架构的项目,部署在Weblogic上, ...
- Vert.x(vertx) 连接MySQL、Oracle数据库
Vert.x提供异步访问数据库的API,可能这里有朋友会有疑惑,直接使用我们之前的熟悉的Mybatis或者Hibernate不行吗,可行,但数据库操作是一个耗时操作,使用传统的同步模型,容易阻塞线程, ...
- Vert.x(vertx) 事件总线(EventBus)与 远程服务调用
Event Bus(事件总线) 是Vert.x的神经系统,负责应用系统消息的传递.Vert.x各模块(Verticle)之间的相互调用就是通过Event Bus实现的,因此各Verticle之间是高度 ...
- Vert.x(vertx) 简明介绍
摘要 Vert.x最大的特点就在于异步(底层基于Netty),通过事件循环(EventLoop)来调起存储在异步任务队列(CallBackQueue)中的任务,大大降低了传统阻塞模型中线程对于操作系统 ...
- Vert.x(vertx)入门资料
1.vert.x简介 vert.x 采用类似 Node.js 的 eventloop callback 机制,优势是 Eventloop 是单线程场景下几乎是最快的并发解决方案,但也需要周边生态的支持 ...
最新文章
- 汇总 | 深度学习中图像语义分割基准数据集详解
- 86.git使用 建立和克隆远程仓库
- 35岁的程序员正在消失?No,我认识了一个50岁的程序员!
- java中用iterator去检查最大值_Java中的Iterator vs forEach
- 正向代理与反向代理;
- zookeeper命令行操作
- 基于51单片机的时钟系统
- Cross compile webrtc for ios on mac os.
- Linux基础-编译安装Python
- pm2启动jenkins不存在tty的问题
- Java从入门到精通+第三版.pdf
- SFML1 俄罗斯方块代码解析
- 计算机核心期刊加拿大,ssci或cssci期刊北京大学图书馆版核心期刊国外学术.doc...
- Kali Linux 暴力破解 wifi密码
- 四巨头键盘钢琴音源完整版-Spectrasonics Keyscape v1.1.3C WiN-MAC
- 广州市黄埔区水利工程管理划定带动水利人才上升
- postgresql中实现按周统计详解
- Observable与Subject
- 随机梯度下降算法 入门介绍(最通俗易懂)
- 前端CSS核心部分盒子模型