diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/ServerEngine.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/ServerEngine.java index 90c04191b..25f0395fd 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/ServerEngine.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/ServerEngine.java @@ -20,7 +20,10 @@ import org.dromara.hutool.http.server.ServerConfig; import org.dromara.hutool.http.server.handler.HttpHandler; /** - * HTTP服务器引擎 + * HTTP服务器引擎,执行流程为: + *
{@code
+ *    init -> setHandler -> start
+ * }
* * @author looly * @since 6.0.0 diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/SimpleServer.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/SimpleServer.java index 1f130c825..40b2120d0 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/SimpleServer.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/SimpleServer.java @@ -82,6 +82,7 @@ public class SimpleServer { .setPort(address.getPort()) .setSslContext(sslContext); this.engine.init(serverConfig); + this.engine.initEngine(); } /** diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/SunHttpServerEngine.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/SunHttpServerEngine.java index 49d26a86f..205198566 100644 --- a/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/SunHttpServerEngine.java +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/engine/sun/SunHttpServerEngine.java @@ -140,35 +140,45 @@ public class SunHttpServerEngine extends AbstractServerEngine { this.server.stop(0); this.server = null; } + this.server = createServer(this.config); } @Override protected void initEngine() { - final ServerConfig config = this.config; + // 请求处理器 + createContext("/", exchange -> handler.handle( + new SunServerRequest(exchange), + new SunServerResponse(exchange) + )); + } + /** + * 创建{@link HttpServer} + * + * @param config {@link ServerConfig} + * @return {@link HttpServer} + */ + private static HttpServer createServer(final ServerConfig config){ + final HttpServer server; // SSL final InetSocketAddress address = new InetSocketAddress(config.getHost(), config.getPort()); final SSLContext sslContext = config.getSslContext(); try { if (null != sslContext) { - final HttpsServer server = HttpsServer.create(address, 0); - server.setHttpsConfigurator(new HttpsConfigurator(sslContext)); - this.server = server; + final HttpsServer httpsServer = HttpsServer.create(address, 0); + httpsServer.setHttpsConfigurator(new HttpsConfigurator(sslContext)); + server = httpsServer; } else { - this.server = HttpServer.create(address, 0); + server = HttpServer.create(address, 0); } } catch (final IOException e) { throw new IORuntimeException(e); } // 线程池和连接配置 - setExecutor(createExecutor(config)); + server.setExecutor(createExecutor(config)); - // 请求处理器 - createContext("/", exchange -> handler.handle( - new SunServerRequest(exchange), - new SunServerResponse(exchange) - )); + return server; } /** diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/handler/PathTrie.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/handler/PathTrie.java new file mode 100644 index 000000000..29fd0f599 --- /dev/null +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/handler/PathTrie.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.http.server.handler; + +import org.dromara.hutool.core.text.StrUtil; +import org.dromara.hutool.core.text.split.SplitUtil; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 路由处理器
+ * 根据请求的路径精确匹配路由,并调用对应的处理器 + * + * @author Looly + * @since 6.0.0 + */ +public class PathTrie { + + private final Node root; + + /** + * 构造 + */ + public PathTrie() { + root = new Node(); + } + + /** + * 添加路由 + * + * @param path 路径 + * @param handler 处理器 + */ + public void add(final String path, final HttpHandler handler) { + Node node = root; + final List parts = SplitUtil.splitTrim(path, StrUtil.SLASH); + for (final String part : parts) { + node = node.getOrCreateChildren(part); + } + node.isEndOfPath = true; + node.handler = handler; + } + + /** + * 查找匹配的处理器,采用最长匹配模式,即:
+ * 传入"a/b/c",存在"a/b/c",则直接匹配,否则匹配"a/b",否则匹配"a" + * + * @param path 路径 + * @return 处理器 + */ + public HttpHandler match(final String path) { + Node matchedNode = null; + Node node = root; + final List parts = SplitUtil.splitTrim(path, StrUtil.SLASH); + for (final String part : parts) { + node = node.getChildren(part); + if (node == null) { + break; + } + if(node.isEndOfPath){ + matchedNode = node; + } + } + return null == matchedNode ? null : matchedNode.handler; + } + + static class Node { + Map children; + boolean isEndOfPath; + HttpHandler handler; + + public Node() { + isEndOfPath = false; + handler = null; + } + + /** + * 获取子节点 + * + * @param part 节点标识 + * @return 子节点 + */ + public Node getChildren(final String part) { + return null == children ? null : children.get(part); + } + + /** + * 获取或创建子节点 + * + * @param part 节点标识 + * @return 子节点 + */ + public Node getOrCreateChildren(final String part) { + if(null == children){ + children = new HashMap<>(); + } + return children.computeIfAbsent(part, c -> new Node()); + } + } +} + diff --git a/hutool-http/src/main/java/org/dromara/hutool/http/server/handler/RouteHttpHandler.java b/hutool-http/src/main/java/org/dromara/hutool/http/server/handler/RouteHttpHandler.java new file mode 100644 index 000000000..8d4165a82 --- /dev/null +++ b/hutool-http/src/main/java/org/dromara/hutool/http/server/handler/RouteHttpHandler.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.http.server.handler; + +import org.dromara.hutool.core.lang.Assert; + +/** + * 路由处理器
+ * 根据请求的路径精确匹配路由,并调用对应的处理器,如果没有定义处理器,使用默认处理器 + * + * @author Looly + * @since 6.0.0 + */ +public class RouteHttpHandler implements HttpHandler { + + private final PathTrie pathTrie; + private final HttpHandler defaultHandler; + + /** + * 构造 + * + * @param defaultHandler 默认处理器 + */ + public RouteHttpHandler(final HttpHandler defaultHandler) { + this.pathTrie = new PathTrie(); + this.defaultHandler = Assert.notNull(defaultHandler); + } + + + /** + * 添加路由 + * + * @param path 路径 + * @param handler 处理器 + * @return this + */ + public RouteHttpHandler route(final String path, final HttpHandler handler) { + if (null != handler) { + pathTrie.add(path, handler); + } + return this; + } + + @Override + public void handle(final ServerRequest request, final ServerResponse response) { + final String path = request.getPath(); + final HttpHandler handler = pathTrie.match(path); + if (null != handler) { + handler.handle(request, response); + } else { + // 没有path匹配,使用默认处理器 + defaultHandler.handle(request, response); + } + } +} diff --git a/hutool-http/src/test/java/org/dromara/hutool/http/server/SimpleServerTest.java b/hutool-http/src/test/java/org/dromara/hutool/http/server/SimpleServerTest.java index b11fd8148..76136f7c7 100644 --- a/hutool-http/src/test/java/org/dromara/hutool/http/server/SimpleServerTest.java +++ b/hutool-http/src/test/java/org/dromara/hutool/http/server/SimpleServerTest.java @@ -23,7 +23,6 @@ import org.dromara.hutool.http.HttpUtil; import org.dromara.hutool.http.meta.ContentType; import org.dromara.hutool.http.meta.HeaderName; import org.dromara.hutool.http.multipart.UploadFile; -import org.dromara.hutool.http.server.engine.sun.SunServerRequest; import org.dromara.hutool.json.JSONUtil; import java.net.HttpCookie; @@ -40,7 +39,7 @@ public class SimpleServerTest { .setRoot(FileUtil.file("html")) // get数据测试,返回请求的PATH .addAction("/get", (request, response) -> - response.write(((SunServerRequest)request).getURI().toString(), ContentType.TEXT_PLAIN.toString()) + response.write(request.getPath(), ContentType.TEXT_PLAIN.toString()) ) // 返回JSON数据测试 .addAction("/restTest", (request, response) -> { diff --git a/hutool-http/src/test/java/org/dromara/hutool/http/server/handler/PathTrieTest.java b/hutool-http/src/test/java/org/dromara/hutool/http/server/handler/PathTrieTest.java new file mode 100644 index 000000000..e124bf63f --- /dev/null +++ b/hutool-http/src/test/java/org/dromara/hutool/http/server/handler/PathTrieTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2024 Hutool Team and hutool.cn + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.dromara.hutool.http.server.handler; + +import org.dromara.hutool.core.lang.Console; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.*; + +public class PathTrieTest { + @Test + public void testPathTrie() { + final PathTrie trie = new PathTrie(); + + // 添加一些路径和对应的处理器 + trie.add("/user", (req, res) -> Console.log("User handler")); + trie.add("/user/profile", (req, res) -> Console.log("Profile handler")); + + // 测试精确匹配 + assertNotNull(trie.match("/user")); + // 匹配父路径 + assertNotNull(trie.match("/user/test1")); + // 匹配最近的上级路径 + assertNotNull(trie.match("/user/test1/test2")); + + // 自动忽略空路径,尾部的/也忽略 + assertNotNull(trie.match("/user/profile")); + assertNotNull(trie.match("/user/profile/")); + assertNotNull(trie.match("/user////profile/")); + + // 测试不存在的路径 + assertNull(trie.match("/nonexistent")); + assertNull(trie.match("/")); + assertNull(trie.match("")); + assertNull(trie.match(null)); + } +}