这是本人正在写的《Java 编程方法论:响应式Reactor3、Reactor-Netty和Spring WebFlux》一书的文章节选,它是《Java编程方法论:响应式RxJava与代码设计实战》的续篇,也可作为独立的一本来读

这是此节上半段的节选内容

HttpHandler的探索

通过前面的章节,我们已经接触了Reactor-Netty整个流程的设计实现细节,同时也涉及到了reactor.netty.http.server.HttpServer#handle,准确得说,它是一个SPI(Service Provider Interface)接口,对外提供BiFunction<? super HttpServerRequest, ? super HttpServerResponse, ? extends Publisher<Void>> handler,这样,我们可以针对该handler依据自身环境进行相应实现。

Spring WebFluxReactor-Netty都有一套属于自己的实现,只不过前者为了适应Spring Web的一些习惯做了大量的适配设计,整个过程比较复杂,后者提供了一套简单而灵活的实现。那么本章我们就从Reactor-Netty内对它的实现开始,正式向Spring WebFlux进行过渡。

HttpServerRoutes设定

往往我们在给后台服务器提交HTTP请求的时候,往往会涉及到getheadpostput这几种类型,还会包括请求地址,服务端会根据请求类型和请求地址提供对应的服务,然后才是具体的处理,那么我们是不是可以将寻找服务的这个过程抽取出来,形成服务路由查找。

于是,在Reactor-Netty中,设计了一个HttpServerRoutes接口,该接口继承了BiFunction<HttpServerRequest, HttpServerResponse, Publisher<Void>>,用来路由请求,当请求来临时,对我们所设计的路由规则按顺序依次查找,直到第一个匹配,然后调用对应的处理handlerHttpServerRoutes接口内针对于我们常用的getheadpostputdelete等请求设计了对应的路由规则(具体请看下面源码)。

我们在使用的时候首先会调用HttpServerRoutes#newRoutes得到一个DefaultHttpServerRoutes实例,然后加入我们设计的路由规则,关于路由规则的设计,其实就是将一条条规则通过一个集合管理起来,然后在需要时进行遍历匹配即可,这里它的核心组织方法就是reactor.netty.http.server.HttpServerRoutes#route,在规则设计完后,我们就可以设计对应每一条规则的BiFunction<HttpServerRequest, HttpServerResponse, Publisher<Void>>函数式实现,最后,当请求路由匹配成功,就可以调用我们的BiFunction实现,对请求进行处理。

//reactor.netty.http.server.HttpServerRoutes
public interface HttpServerRoutes extends
BiFunction<HttpServerRequest, HttpServerResponse, Publisher<Void>> { static HttpServerRoutes newRoutes() {
return new DefaultHttpServerRoutes();
} default HttpServerRoutes delete(String path,
BiFunction<? super HttpServerRequest, ? super HttpServerResponse, ? extends Publisher<Void>> handler) {
return route(HttpPredicate.delete(path), handler);
} ... default HttpServerRoutes get(String path,
BiFunction<? super HttpServerRequest, ? super HttpServerResponse, ? extends Publisher<Void>> handler) {
return route(HttpPredicate.get(path), handler);
} default HttpServerRoutes head(String path,
BiFunction<? super HttpServerRequest, ? super HttpServerResponse, ? extends Publisher<Void>> handler) {
return route(HttpPredicate.head(path), handler);
} default HttpServerRoutes index(final BiFunction<? super HttpServerRequest, ? super HttpServerResponse, ? extends Publisher<Void>> handler) {
return route(INDEX_PREDICATE, handler);
} default HttpServerRoutes options(String path,
BiFunction<? super HttpServerRequest, ? super HttpServerResponse, ? extends Publisher<Void>> handler) {
return route(HttpPredicate.options(path), handler);
} default HttpServerRoutes post(String path,
BiFunction<? super HttpServerRequest, ? super HttpServerResponse, ? extends Publisher<Void>> handler) {
return route(HttpPredicate.post(path), handler);
} default HttpServerRoutes put(String path,
BiFunction<? super HttpServerRequest, ? super HttpServerResponse, ? extends Publisher<Void>> handler) {
return route(HttpPredicate.put(path), handler);
} HttpServerRoutes route(Predicate<? super HttpServerRequest> condition,
BiFunction<? super HttpServerRequest, ? super HttpServerResponse, ? extends Publisher<Void>> handler); ... }

关于路由规则的设计,结合前面所讲,我们可以在HttpServerRoutes的实现类中设计一个List用来存储一条条的规则,接下来要做的就是将制定的规则一条条放入其中即可,因为这是一个添加过程,并不需要返回值,我们可以使用Consumer<? super HttpServerRoutes>来代表这个过程。对于请求的匹配,往往都是对请求的条件判断,那我们可以使用Predicate<? super HttpServerRequest>来代表这个判断逻辑,由于单条路由规则匹配对应的BiFunction<HttpServerRequest, HttpServerResponse, Publisher<Void>>处理,那么我们是不是可以将这两者耦合到一起,于是reactor.netty.http.server.DefaultHttpServerRoutes.HttpRouteHandler就设计出来了:

//reactor.netty.http.server.DefaultHttpServerRoutes.HttpRouteHandler
static final class HttpRouteHandler
implements BiFunction<HttpServerRequest, HttpServerResponse, Publisher<Void>>,
Predicate<HttpServerRequest> { final Predicate<? super HttpServerRequest> condition;
final BiFunction<? super HttpServerRequest, ? super HttpServerResponse, ? extends Publisher<Void>>
handler;
final Function<? super String, Map<String, String>> resolver; HttpRouteHandler(Predicate<? super HttpServerRequest> condition,
BiFunction<? super HttpServerRequest, ? super HttpServerResponse, ? extends Publisher<Void>> handler,
@Nullable Function<? super String, Map<String, String>> resolver) {
this.condition = Objects.requireNonNull(condition, "condition");
this.handler = Objects.requireNonNull(handler, "handler");
this.resolver = resolver;
} @Override
public Publisher<Void> apply(HttpServerRequest request,
HttpServerResponse response) {
return handler.apply(request.paramsResolver(resolver), response);
} @Override
public boolean test(HttpServerRequest o) {
return condition.test(o);
}
}

这里可能需要对request中的参数进行解析,所以对外提供了一个可供我们自定义的参数解析器实现接口:Function<? super String, Map<String, String>>,剩下的conditionresolver就可以按照我们前面说的逻辑进行。

此时,HttpRouteHandler属于一个真正的请求校验者和请求业务处理者,我们现在要将它们的功能通过一系列逻辑串联形成一个处理流程,那么这里可以通过一个代理模式进行,我们在HttpServerRoutes的实现类中通过一个List集合管理了数量不等的HttpRouteHandler实例,对外,我们在使用reactor.netty.http.server.HttpServer#handle时只会看到一个BiFunction<HttpServerRequest, HttpServerResponse, Publisher<Void>>实现,那么,所有的逻辑流程处理都应该在这个BiFunctionapply(...)实现中进行,于是,我们就有下面的reactor.netty.http.server.DefaultHttpServerRoutes实现:

//reactor.netty.http.server.DefaultHttpServerRoutes
final class DefaultHttpServerRoutes implements HttpServerRoutes { private final CopyOnWriteArrayList<HttpRouteHandler> handlers =
new CopyOnWriteArrayList<>();
...
@Override
public HttpServerRoutes route(Predicate<? super HttpServerRequest> condition,
BiFunction<? super HttpServerRequest, ? super HttpServerResponse, ? extends Publisher<Void>> handler) {
Objects.requireNonNull(condition, "condition");
Objects.requireNonNull(handler, "handler"); if (condition instanceof HttpPredicate) {
handlers.add(new HttpRouteHandler(condition,
handler,
(HttpPredicate) condition));
}
else {
handlers.add(new HttpRouteHandler(condition, handler, null));
}
return this;
} @Override
public Publisher<Void> apply(HttpServerRequest request, HttpServerResponse response) {
final Iterator<HttpRouteHandler> iterator = handlers.iterator();
HttpRouteHandler cursor; try {
while (iterator.hasNext()) {
cursor = iterator.next();
if (cursor.test(request)) {
return cursor.apply(request, response);
}
}
}
catch (Throwable t) {
Exceptions.throwIfJvmFatal(t);
return Mono.error(t); //500
} return response.sendNotFound();
}
...
}

可以看到route(...)方法只是做了HttpRouteHandler实例的构建并交由handlers这个list进行管理,通过上面的apply实现将前面的内容在流程逻辑中进行组合。于是,我们就可以在reactor.netty.http.server.HttpServer中设计一个route方法,对外提供一个SPI接口,将我们所提到的整个过程定义在这个方法中(得到一个HttpServerRoutes实例,然后通过它的route方法构建规则,构建过程在前面提到的Consumer<? super HttpServerRoutes>中进行,最后将组合成功的HttpServerRoutesBiFunction<HttpServerRequest, HttpServerResponse, Publisher<Void>>的角色作为参数交由HttpServer#handle)。

另外,我们在这里要特别注意下,在上面DefaultHttpServerRoutes实现的apply方法中,可以看出,一旦请求匹配,处理完后就直接返回结果,不再继续遍历匹配,也就是说每次新来的请求,只调用所声明匹配规则顺序的第一个匹配。

//reactor.netty.http.server.HttpServer#route
public final HttpServer route(Consumer<? super HttpServerRoutes> routesBuilder) {
Objects.requireNonNull(routesBuilder, "routeBuilder");
HttpServerRoutes routes = HttpServerRoutes.newRoutes();
routesBuilder.accept(routes);
return handle(routes);
}

于是,我们就可以通过下面的Demo来应用上面的设计:

import reactor.core.publisher.Mono;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer; public class Application { public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
.route(routes ->
routes.get("/hello", <1>
(request, response) -> response.sendString(Mono.just("Hello World!")))
.post("/echo", <2>
(request, response) -> response.send(request.receive().retain()))
.get("/path/{param}", <3>
(request, response) -> response.sendString(Mono.just(request.param("param")))))
.bindNow(); server.onDispose()
.block();
}
}

<1>处,当我们发出一个GET请求去访问/hello时就会得到一个字符串Hello World!

<2>处,当我们发出一个 POST请求去访问 /echo时就会将请求体作为响应内容返回。

<3>处,当我们发出一个 GET请求去访问 /path/{param}

时就会得到一个请求路径参数param的值。

关于SSE在这里的使用,我们可以看下面这个Demo,具体的代码细节就不详述了,看对应注释即可:

import com.fasterxml.jackson.databind.ObjectMapper;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.netty.DisposableServer;
import reactor.netty.http.server.HttpServer;
import reactor.netty.http.server.HttpServerRequest;
import reactor.netty.http.server.HttpServerResponse; import java.io.ByteArrayOutputStream;
import java.nio.charset.Charset;
import java.time.Duration;
import java.util.function.BiFunction; public class Application { public static void main(String[] args) {
DisposableServer server =
HttpServer.create()
.route(routes -> routes.get("/sse", serveSse()))
.bindNow(); server.onDispose()
.block();
} /**
* 准备 SSE response
* 参考 reactor.netty.http.server.HttpServerResponse#sse可以知道它的"Content-Type"
* 是"text/event-stream"
* flush策略为通过所提供的Publisher来每下发一个元素就flush一次
*/
private static BiFunction<HttpServerRequest, HttpServerResponse, Publisher<Void>> serveSse() {
Flux<Long> flux = Flux.interval(Duration.ofSeconds(10));
return (request, response) ->
response.sse()
.send(flux.map(Application::toByteBuf), b -> true);
} /**
* 将发元素按照按照给定的格式由Object转换为ByteBuf。
*/
private static ByteBuf toByteBuf(Object any) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
out.write("data: ".getBytes(Charset.defaultCharset()));
MAPPER.writeValue(out, any);
out.write("\n\n".getBytes(Charset.defaultCharset()));
}
catch (Exception e) {
throw new RuntimeException(e);
}
return ByteBufAllocator.DEFAULT
.buffer()
.writeBytes(out.toByteArray());
} private static final ObjectMapper MAPPER = new ObjectMapper();
}

Spring WebFlux之HttpHandler的探索的更多相关文章

  1. 爸爸又给Spring MVC生了个弟弟叫Spring WebFlux

    情景引入 很早之前,Java就火起来了,是因为它善于开发和处理网络方面的应用. Java有一个爱好,就是喜欢制定规范标准,但自己又不善于去实现. 反倒是一些服务提供商使用它的规范标准来制造应用服务器而 ...

  2. 朱晔和你聊Spring系列S1E5:Spring WebFlux小探

    阅读PDF版本 本文会来做一些应用对比Spring MVC和Spring WebFlux,观察线程模型的区别,然后做一下简单的压力测试. 创建一个传统的Spring MVC应用 先来创建一个新的web ...

  3. 深入剖析 Spring WebFlux

    一.WebFlux 简介 WebFlux 是 Spring Framework5.0 中引入的一种新的反应式Web框架.通过Reactor项目实现Reactive Streams规范,完全异步和非阻塞 ...

  4. Spring Webflux: Kotlin DSL [片断]

    原文链接:https://dzone.com/articles/spring-webflux-kotlin-dsl-snippets 作者:Biju Kunjummen 译者:Jackie Tang ...

  5. Spring WebFlux开门迎客,却来了一位特殊客人

    话说Spring WebFlux已经出现有一段时间了,但是知道他的人并不是很多.这让他很是闷闷不乐. 还有更惨的是,那些敢于吃螃蟹的人在尝试了他之后,有的竟把代码重新改回到Spring MVC的同步模 ...

  6. Spring WebFlux 响应式编程学习笔记(一)

    各位Javaer们,大家都在用SpringMVC吧?当我们不亦乐乎的用着SpringMVC框架的时候,Spring5.x又悄(da)无(zhang)声(qi)息(gu)的推出了Spring WebFl ...

  7. Spring WebFlux 要革了谁的命?

    Spring WebFlux 要革了谁的命?  mp.weixin.qq.com 托梦 Java国王昨晚做了一个梦. 梦中有个白胡子老头儿,颇有仙风道骨, 告诉他说:“你们Java啊,实在是太弱了,连 ...

  8. 基于Angular和Spring WebFlux做个小Demo

    前言 随着Spring Boot2.0正式发布,Spring WebFlux正式来到了Spring Boot大家族里面.由于Spring WebFlux可以通过更少的线程去实现更高的并发和使用更少的硬 ...

  9. Spring WebFlux, 它是一种异步的, 非阻塞的, 支持背压(Back pressure)机制的Web 开发WebFlux 支持两种编程风(姿)格(势) 使用@Controller这种基于注解

    概述 什么是 Spring WebFlux, 它是一种异步的, 非阻塞的, 支持背压(Back pressure)机制的Web 开发框架. 要深入了解 Spring WebFlux, 首先要了知道 R ...

随机推荐

  1. IDisposeable 最佳实现

    public class MyClass : IDisposable { #region 变量声明 // 指向外部非托管资源 private IntPtr handle; // 此类使用的其它托管资源 ...

  2. 图像滤镜艺术---(Lightleaks Filter)漏光滤镜

    原文:图像滤镜艺术---(Lightleaks Filter)漏光滤镜 (Lightleaks Filter)漏光滤镜 漏光拍摄其实就是一种摄影手法,最初是因为强烈光照导致相片交卷的过分曝光,最终在成 ...

  3. 全面提价2499元起小米6发布:四曲陶瓷机身+骁龙835+变焦双摄(小米在设计上也多次获得红点最佳、iF金奖等72项工业设计大奖)

    集微网  4月19日报道 今日,小米公司在北京召开正式推出了新一代旗舰手机“小米手机6”.在试玩过真机后,第一感觉就是这款手机做工与颜值相比此前小米手机提升巨大:有四曲面玻璃或陶瓷机身.不锈钢高亮边框 ...

  4. 使用Boost的DLL库管理动态链接库

    Boost 1.61新增了一个DLL库,跟Qt中的QLibrary类似,提供了跨平台的动态库链接库加载.调用等功能.http://www.boost.org/users/history/version ...

  5. VS2008 环境中完美搭建 Qt 4.7.4 静态编译的调试与发布(很不错,有六张插图说明,修改qmake.conf里的MD参数等)good

    为什么要在VS2008中使用QT静态编译呢?很简单,因为VS2008编译器编译出来的文件比mingw编译的要几乎小一半. 好了现在我们来做些准备工作,VS2008自然要安装的,然后打上SP1的补丁.然 ...

  6. 图形界面编程成就了C++

    听有人说C#.VB比C++好是因为做界面方便还算傻得可爱,听有人说用C++做数值计算而不屑于做界面可就对不起咱C++的恩人了.这我可要说道说道. 想当年C++刚出江湖,名门出身,自立门派,想抢Obje ...

  7. QT运行cmd指令(两种办法:QProcess.start然后waitForFinished,运行cmd /c命令)

    QProcess p(); p.start("route");//写入要运行的指令即可 p.waitForStarted(); p.waitForFinished(); qDebu ...

  8. Excel报表开发(本节主要讲述导出到Excel操作)

    一.Excel导入到GridView以及数据库操作比较简单,这儿不做过多讲解,需要注意的有二点: 1.设置IMEX=1将强制混合数据转换为文本. 2.解决Excel驱动程序默认读取8行:将" ...

  9. c++类运算符重载遇到的函数形参问题

    class A { public: A(int arg1, int arg2); ~A(); A &operator = ( A &other); A operator + ( A & ...

  10. ZooKeeper学习第二期--ZooKeeper安装配置(转)

    转载来源:https://www.cnblogs.com/sunddenly/p/4018459.html 一.Zookeeper的搭建方式 Zookeeper安装方式有三种,单机模式和集群模式以及伪 ...