這是本人正在寫的《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 WebFlux
與Reactor-Netty
都有一套屬於自己的實現,只不過前者為了適應Spring Web
的一些習慣做了大量的適配設計,整個過程比較複雜,後者提供了一套簡單而靈活的實現。那麼本章我們就從Reactor-Netty
內對它的實現開始,正式向Spring WebFlux
進行過渡。
HttpServerRoutes設定
往往我們在給後臺伺服器提交HTTP
請求的時候,往往會涉及到get
,head
,post
,put
這幾種型別,還會包括請求地址,服務端會根據請求型別和請求地址提供對應的服務,然後才是具體的處理,那麼我們是不是可以將尋找服務的這個過程抽取出來,形成服務路由查詢。
於是,在Reactor-Netty
中,設計了一個HttpServerRoutes
介面,該介面繼承了BiFunction<HttpServerRequest, HttpServerResponse, Publisher<Void>>
,用來路由請求,當請求來臨時,對我們所設計的路由規則按順序依次查詢,直到第一個匹配,然後呼叫對應的處理handler
。HttpServerRoutes
介面內針對於我們常用的get
,head
,post
,put
,delete
等請求設計了對應的路由規則(具體請看下面原始碼)。
我們在使用的時候首先會呼叫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>>
,剩下的condition
與resolver
就可以按照我們前面說的邏輯進行。
此時,HttpRouteHandler
屬於一個真正的請求校驗者和請求業務處理者,我們現在要將它們的功能通過一系列邏輯串聯形成一個處理流程,那麼這裡可以通過一個代理模式進行,我們在HttpServerRoutes
的實現類中通過一個List
集合管理了數量不等的HttpRouteHandler
例項,對外,我們在使用reactor.netty.http.server.HttpServer#handle
時只會看到一個BiFunction<HttpServerRequest, HttpServerResponse, Publisher<Void>>
實現,那麼,所有的邏輯流程處理都應該在這個BiFunction
的apply(...)
實現中進行,於是,我們就有下面的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>
中進行,最後將組合成功的HttpServerRoutes
以BiFunction<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();
}