feat: add HybridRouter add LRUCache

This commit is contained in:
yangshangbo
2025-07-04 14:49:00 +08:00
parent a76ad033b0
commit 763b2e62b5
25 changed files with 788 additions and 270722 deletions
-24
View File
@@ -1,24 +0,0 @@
# Features
- [x] Hono App
- [x] `fn get`
- [x] `fn post`
- [x] `fn put`
- [x] `fn delete`
- [x] `fn patch`
- [x] `fn head`
- [x] `fn options`
- [x] `fn listen`
- [x] Request
- [x] `property url`
- [x] `property header`
- [x] `property query`
- [ ] `property header`
- [ ] `property data`
- [ ] `property IP`
- [x] Response
- [x] `fn text`
- [x] `fn json`
- [x] `fn html`
- [ ] Router
- [ ] Middleware
+108 -122
View File
@@ -2,28 +2,18 @@ module hono
import net.urllib
import net.http
import regex
import time
type Handler = fn (req Request) http.Response
struct RequestHandler {
path string
handler Handler = unsafe { nil }
mut: app Hono
}
struct Router {
mut:
handlers struct {
mut:
get []RequestHandler
post []RequestHandler
put []RequestHandler
delete []RequestHandler
patch []RequestHandler
head []RequestHandler
options []RequestHandler
get []IRequestHandler
post []IRequestHandler
put []IRequestHandler
delete []IRequestHandler
patch []IRequestHandler
head []IRequestHandler
options []IRequestHandler
}
}
@@ -33,35 +23,50 @@ mut:
router Router = Router{}
routes map[string] Hono = {}
base_path string
pub mut:
hybrid_router HybridRouter
}
pub fn (mut app Hono) get(path string, handler Handler) {
mut handlers := RequestHandler{path,handler,app}
pub fn (mut app Hono) get(path string, handler fn (Request) http.Response) {
handlers := RequestHandler{path, handler}
app.router.handlers.get << handlers
app.hybrid_router.add_route('GET', handlers, app.base_path)
}
pub fn (mut app Hono) post(path string, handler Handler) {
app.router.handlers.post << RequestHandler{path,handler,app}
pub fn (mut app Hono) post(path string, handler fn (Request) http.Response) {
handlers := RequestHandler{path, handler}
app.router.handlers.post << handlers
app.hybrid_router.add_route('POST', handlers, app.base_path)
}
pub fn (mut app Hono) put(path string, handler Handler) {
app.router.handlers.put << RequestHandler{path,handler,app}
pub fn (mut app Hono) put(path string, handler fn (Request) http.Response) {
handlers := RequestHandler{path, handler}
app.router.handlers.put << handlers
app.hybrid_router.add_route('PUT', handlers, app.base_path)
}
pub fn (mut app Hono) delete(path string, handler Handler) {
app.router.handlers.delete << RequestHandler{path,handler,app}
pub fn (mut app Hono) delete(path string, handler fn (Request) http.Response) {
handlers := RequestHandler{path, handler}
app.router.handlers.delete << handlers
app.hybrid_router.add_route('DELETE', handlers, app.base_path)
}
pub fn (mut app Hono) patch(path string, handler Handler) {
app.router.handlers.patch << RequestHandler{path,handler,app}
pub fn (mut app Hono) patch(path string, handler fn (Request) http.Response) {
handlers := RequestHandler{path, handler}
app.router.handlers.patch << handlers
app.hybrid_router.add_route('PATCH', handlers, app.base_path)
}
pub fn (mut app Hono) head(path string, handler Handler) {
app.router.handlers.head << RequestHandler{path,handler,app}
pub fn (mut app Hono) head(path string, handler fn (Request) http.Response) {
handlers := RequestHandler{path, handler}
app.router.handlers.head << handlers
app.hybrid_router.add_route('HEAD', handlers, app.base_path)
}
pub fn (mut app Hono) options(path string, handler Handler) {
app.router.handlers.options << RequestHandler{path,handler,app}
pub fn (mut app Hono) options(path string, handler fn (Request) http.Response) {
handlers := RequestHandler{path, handler}
app.router.handlers.options << handlers
app.hybrid_router.add_route('OPTIONS', handlers, app.base_path)
}
struct ServerHanler {
@@ -69,121 +74,102 @@ mut:
app Hono
}
fn ServerHanler.new(app Hono) ServerHanler {
fn server_hanler_new(app Hono) ServerHanler {
return ServerHanler{
app: app
}
}
fn match_path_with_regex(real_path string, reg_path string) (bool, regex.RE) {
start := time.now()
mut one_more_star_reg := regex.regex_opt(r'\*{2,}') or { panic(err) }
repl_on_e_more_star_fn := fn (re regex.RE, in_txt string, start int, end int) string {
return r'[^#\?]+'
}
mut replaced_reg_path := reg_path.replace('?', r'\?')
replaced_reg_path = replaced_reg_path.replace('+', r'\+')
mut replaced_path := one_more_star_reg.replace_by_fn(replaced_reg_path, repl_on_e_more_star_fn)
replaced_path = replaced_path.replace('*', r'[^#\?]+')
mut pamam_reg := regex.regex_opt(r':[^/]+') or { panic(err) }
repl_fn := fn (re regex.RE, in_txt string, start int, end int) string {
match_str := in_txt[start..end]
return '(?P<${match_str[1..]}>[^/]+)'
}
replaced_path = pamam_reg.replace_by_fn(replaced_path, repl_fn)
replaced_path = replaced_path + '$'
mut reg := regex.regex_opt(replaced_path) or { panic(err) }
end := time.now()
println('Regex time: ${end - start}')
return reg.matches_string(real_path), reg
}
fn get_all_handlers_from_hono(app Hono,req http.Request) []RequestHandler {
mut result := []RequestHandler{}
handlers := match req.method {
.get {
app.router.handlers.get
}
.post {
app.router.handlers.post
}
.put {
app.router.handlers.put
}
.delete {
app.router.handlers.delete
}
.patch {
app.router.handlers.patch
}
.head {
app.router.handlers.head
}
.options {
app.router.handlers.options
}
else {
[]
}
}
result << handlers
for key in app.routes.keys() {
subapp_handlers := get_all_handlers_from_hono(app.routes[key],req)
result << subapp_handlers
}
return result
}
fn (s ServerHanler) handle(req http.Request) http.Response {
fn (mut s ServerHanler) handle(req http.Request) http.Response {
mut res := http.Response{
status_code: 404
body: 'Not Found'
}
handlers := get_all_handlers_from_hono(s.app,req)
for handler in handlers {
url := urllib.parse(req.url) or {
urllib.URL{
path: '/'
url := urllib.parse(req.url) or {
urllib.URL{
path: '/'
}
}
if route_match := s.app.hybrid_router.match_route(req.method.str(), url.path) {
// 解析 query
raw_query := url.query().to_map()
mut query_map := map[string]string{}
for key, values in raw_query {
if values.len > 0 {
query_map[key] = values[0]
}
}
match_result, mut replaced_path_reg := match_path_with_regex(url.path, handler.app.base_path + handler.path)
if match_result {
mut param_map := map[string]string{}
mut pamam_reg := regex.regex_opt(r':\w+') or { panic(err) }
all_params := pamam_reg.find_all_str(handler.path)
for param in all_params {
param_name := param[1..]
group := replaced_path_reg.get_group_by_name(url.path, param_name)
param_map[param_name] = group
}
query_map := url.query().to_map()
res = handler.handler(Request{
url: url.path
param: param_map
query: query_map
})
break
// param 由路由匹配结果提供
param_map := route_match.params.clone()
// body
body := req.data
// 构造 hono.Request
hreq := Request{
url: url.path
param: param_map
query: query_map
body: body
}
res = route_match.handler.handle(hreq)
}
return res
}
pub fn (mut app Hono) listen(port string) {
app.server.addr = port
app.server.handler = ServerHanler.new(app)
app.server.handler = server_hanler_new(app)
app.server.listen_and_serve()
}
pub fn (mut app Hono) route(prefix string,mut subapp Hono) {
for mut request_handler in subapp.router.handlers.get {
request_handler.app.base_path = prefix
}
for mut request_handler in subapp.router.handlers.post {
request_handler.app.base_path = prefix
}
pub fn (mut app Hono) route(prefix string, mut subapp Hono) {
// 简化路由合并,直接添加所有处理器
app.routes[prefix] = subapp
// 将子应用的路由添加到主应用
for handler in subapp.router.handlers.get {
app.router.handlers.get << handler
}
for handler in subapp.router.handlers.post {
app.router.handlers.post << handler
}
for handler in subapp.router.handlers.put {
app.router.handlers.put << handler
}
for handler in subapp.router.handlers.delete {
app.router.handlers.delete << handler
}
for handler in subapp.router.handlers.patch {
app.router.handlers.patch << handler
}
for handler in subapp.router.handlers.head {
app.router.handlers.head << handler
}
for handler in subapp.router.handlers.options {
app.router.handlers.options << handler
}
}
pub fn (mut app Hono) set_base_path(base_path string) {
app.base_path = base_path
}
pub fn (app Hono) get_router_stats() (int, int, int, int) {
mut hybrid_router := app.hybrid_router
static_routes, dynamic_routes := hybrid_router.get_all_routes()
cache_size, cache_capacity := hybrid_router.get_cache_stats()
return static_routes.len, dynamic_routes.len, cache_size, cache_capacity
}
pub fn (mut app Hono) clear_cache() {
app.hybrid_router.clear_cache()
}
pub fn new_hono() Hono {
return Hono{
server: http.Server{}
router: Router{}
routes: map[string]Hono{}
base_path: ''
hybrid_router: new_hybrid_router()
}
}
+65
View File
@@ -0,0 +1,65 @@
module hono
// 简化的LRU缓存实现
pub struct LRUCache {
mut:
capacity int
cache map[string]RouteMatch
keys []string // 维护访问顺序
}
pub fn new_lru_cache(capacity int) LRUCache {
return LRUCache{
capacity: capacity
cache: map[string]RouteMatch{}
keys: []string{}
}
}
pub fn (mut cache LRUCache) get(key string) ?RouteMatch {
if key in cache.cache {
// 移动到列表末尾(最近使用)
idx := cache.keys.index(key)
if idx != -1 {
cache.keys.delete(idx)
}
cache.keys << key
return cache.cache[key]
}
return none
}
pub fn (mut cache LRUCache) put(key string, value RouteMatch) {
if key in cache.cache {
// 更新现有项
cache.cache[key] = value
// 移动到列表末尾
idx := cache.keys.index(key)
if idx != -1 {
cache.keys.delete(idx)
}
cache.keys << key
} else {
// 添加新项
cache.cache[key] = value
cache.keys << key
// 如果超出容量,删除最久未使用的项
if cache.cache.len > cache.capacity {
oldest_key := cache.keys[0]
cache.cache.delete(oldest_key)
cache.keys.delete(0)
}
}
}
pub fn (mut cache LRUCache) clear() {
cache.cache.clear()
cache.keys.clear()
}
pub fn (cache LRUCache) size() int {
return cache.cache.len
}
+25 -5
View File
@@ -4,9 +4,29 @@ import net.http
pub struct Request {
pub:
url string
header http.Header
query map[string][]string
param map[string]string
data map[string]string
url string
param map[string]string
query map[string]string
body string
}
// 接口定义,允许异构的泛型处理器
pub interface IRequestHandler {
path string
handle(req Request) http.Response
}
// 泛型处理器类型
pub type Handler = fn (Request) http.Response
// 泛型请求处理器结构体
pub struct RequestHandler {
pub:
path string
handler fn (Request) http.Response = unsafe { nil }
}
// 实现 IRequestHandler 接口
pub fn (rh RequestHandler) handle(req Request) http.Response {
return rh.handler(req)
}
+21 -18
View File
@@ -2,38 +2,41 @@ module hono
import net.http
// 响应工具
pub struct Response {
}
pub fn Response.json(body string) http.Response {
// 创建HTML响应
pub fn Response.html(content string) http.Response {
return http.Response{
header: http.new_header(http.HeaderConfig{
key: http.CommonHeader.content_type
value: 'application/json'
})
status_code: 200
body: body
header: http.new_header(key: .content_type, value: 'text/html; charset=utf-8')
body: content
}
}
pub fn Response.text(body string) http.Response {
// 创建JSON响应
pub fn Response.json(content string) http.Response {
return http.Response{
header: http.new_header(http.HeaderConfig{
key: http.CommonHeader.content_type
value: 'text/plain'
})
status_code: 200
body: body
header: http.new_header(key: .content_type, value: 'application/json; charset=utf-8')
body: content
}
}
pub fn Response.html(body string) http.Response {
// 创建文本响应
pub fn Response.text(content string) http.Response {
return http.Response{
header: http.new_header(http.HeaderConfig{
key: http.CommonHeader.content_type
value: 'text/html'
})
status_code: 200
body: body
header: http.new_header(key: .content_type, value: 'text/plain; charset=utf-8')
body: content
}
}
// 创建错误响应
pub fn Response.error(status_code int, message string) http.Response {
return http.Response{
status_code: status_code
body: message
}
}
+217
View File
@@ -0,0 +1,217 @@
module hono
import regex
import time
// 路由节点类型
enum RouteType {
static
dynamic
wildcard
}
// 路由节点
struct RouteNode {
mut:
path string
handler IRequestHandler
route_type RouteType
children map[string]&RouteNode
param_name string
base_path string
}
// 路由匹配结果
pub struct RouteMatch {
pub:
handler IRequestHandler
params map[string]string
path string
base_path string
}
// 混合路由器
pub struct HybridRouter {
mut:
static_routes map[string]IRequestHandler
dynamic_routes []IRequestHandler
cache LRUCache
}
pub fn new_hybrid_router() HybridRouter {
return HybridRouter{
static_routes: map[string]IRequestHandler{}
dynamic_routes: []IRequestHandler{}
cache: new_lru_cache(1000) // 缓存1000个路由匹配结果
}
}
// 判断路径是否为静态路径
fn is_static_path(path string) bool {
return !path.contains(':') && !path.contains('*') && !path.contains('?') && !path.contains('+')
}
// 判断路径是否为动态路径
fn is_dynamic_path(path string) bool {
return path.contains(':') || path.contains('*') || path.contains('?') || path.contains('+')
}
// 优化的正则匹配函数
fn (mut router HybridRouter) match_path_with_regex(real_path string, reg_path string) (bool, regex.RE, []string) {
start := time.now()
// 处理多个星号 **
mut one_more_star_reg := regex.regex_opt(r'\*{2,}') or { return false, regex.RE{}, []string{} }
repl_on_e_more_star_fn := fn (re regex.RE, in_txt string, start int, end int) string {
return r'[^#\?]*' // 匹配任意字符(包括斜杠)
}
// 转义特殊字符
mut replaced_reg_path := reg_path.replace('?', r'\?')
replaced_reg_path = replaced_reg_path.replace('+', r'\+')
replaced_reg_path = replaced_reg_path.replace('.', r'\.')
replaced_reg_path = replaced_reg_path.replace('(', r'\(')
replaced_reg_path = replaced_reg_path.replace(')', r'\)')
replaced_reg_path = replaced_reg_path.replace('[', r'\[')
replaced_reg_path = replaced_reg_path.replace(']', r'\]')
replaced_reg_path = replaced_reg_path.replace('{', r'\{')
replaced_reg_path = replaced_reg_path.replace('}', r'\}')
replaced_reg_path = replaced_reg_path.replace('^', r'\^')
replaced_reg_path = replaced_reg_path.replace('$', r'\$')
replaced_reg_path = replaced_reg_path.replace('|', r'\|')
// 处理多个星号
mut replaced_path := one_more_star_reg.replace_by_fn(replaced_reg_path, repl_on_e_more_star_fn)
replaced_path = replaced_path.replace('*', r'[^/#\?]+') // 单个星号匹配单个路径段
// 提取参数名并替换为命名捕获组
mut param_names := []string{}
mut pamam_reg := regex.regex_opt(r':[a-zA-Z_][a-zA-Z0-9_]*') or { return false, regex.RE{}, []string{} }
replaced_path = pamam_reg.replace_by_fn(replaced_path, fn [mut param_names] (re regex.RE, in_txt string, start int, end int) string {
param_name := in_txt[start+1..end]
param_names << param_name
return '(?P<' + param_name + '>[^/]+)'
})
// 添加结束锚点
replaced_path = replaced_path + '$'
println('原始路径: ${reg_path}')
println('编译后正则: ${replaced_path}')
println('参数名: ${param_names}')
// 编译正则表达式
mut reg := regex.regex_opt(replaced_path) or { return false, regex.RE{}, []string{} }
end := time.now()
println('Regex time: ${end - start}')
return reg.matches_string(real_path), reg, param_names
}
// 添加路由
pub fn (mut router HybridRouter) add_route(method string, handler IRequestHandler, base_path string) {
full_path := handler.path
println('添加路由: ${method} ${full_path}')
println('是否为静态路径: ${is_static_path(full_path)}')
if is_static_path(full_path) {
router.static_routes['${method}:${full_path}'] = handler
println('添加到静态路由')
} else {
router.dynamic_routes << handler
println('添加到动态路由')
}
}
// 快速静态路径匹配
fn (router HybridRouter) match_static_route(method string, path string) ?IRequestHandler {
key := '${method}:${path}'
if key in router.static_routes {
return router.static_routes[key]
}
return none
}
// 优化的动态路径匹配
fn (mut router HybridRouter) match_dynamic_route(method string, path string) ?RouteMatch {
// 先检查缓存
cache_key := '${method}:${path}'
if cached := router.cache.get(cache_key) {
return cached
}
for handler in router.dynamic_routes {
match_result, replaced_path_reg, param_names := router.match_path_with_regex(path, handler.path)
if match_result {
mut param_map := map[string]string{}
start, end := replaced_path_reg.match_string(path)
println('匹配路径: ${path}')
println('匹配结果: start=${start}, end=${end}')
println('参数名列表: ${param_names}')
if start >= 0 && end > start {
// 从原始路由路径中提取参数名
mut pamam_reg := regex.regex_opt(r':\w+') or { panic(err) }
all_params := pamam_reg.find_all_str(handler.path)
for param in all_params {
param_name := param[1..]
group := replaced_path_reg.get_group_by_name(path, param_name)
println('提取参数 ${param_name} = ${group}')
param_map[param_name] = group
}
}
println('最终参数映射: ${param_map}')
route_match := RouteMatch{
handler: handler
params: param_map
path: handler.path
base_path: ''
}
router.cache.put(cache_key, route_match)
return route_match
}
}
return none
}
// 主匹配函数
pub fn (mut router HybridRouter) match_route(method string, path string) ?RouteMatch {
// 1. 先尝试静态路径匹配(最快)
if static_handler := router.match_static_route(method, path) {
return RouteMatch{
handler: static_handler
params: map[string]string{}
path: path
base_path: ''
}
}
// 2. 再尝试动态路径匹配
return router.match_dynamic_route(method, path)
}
// 获取所有路由(用于调试)
pub fn (router HybridRouter) get_all_routes() ([]string, []string) {
mut static_paths := []string{}
mut dynamic_paths := []string{}
for key in router.static_routes.keys() {
static_paths << key
}
for handler in router.dynamic_routes {
dynamic_paths << handler.path
}
return static_paths, dynamic_paths
}
// 清理缓存
pub fn (mut router HybridRouter) clear_cache() {
router.cache.clear()
}
// 获取缓存统计信息
pub fn (router HybridRouter) get_cache_stats() (int, int) {
return router.cache.size(), router.cache.capacity
}
-17
View File
@@ -1,17 +0,0 @@
module hono
fn init() {
is_match, _ := match_path_with_regex('/api/v1/user', '/api/v1/:user_id')
assert is_match == true
is_match2, reg2 := match_path_with_regex('/api/v1/abc/user', '/api/**/user')
println('reg2: ${reg2.query}')
assert is_match2 == true
is_match3, reg := match_path_with_regex('/users/123/posts/456/comments', '/users/**')
println('reg: ${reg.query}')
assert is_match3 == true
is_match4, _ := match_path_with_regex('/article+&9/article1/why-!important#999', '/article+&*/:article/why-!important#999')
assert is_match4 == true
is_match5, _ := match_path_with_regex('/api/aaa/bbb/////888', '/api/**/888')
assert is_match5 == true
println('all tests passed')
}