From b445bca96a989490d4f92456ca0412191bf3743a Mon Sep 17 00:00:00 2001 From: xfy Date: Thu, 2 Apr 2026 15:23:54 +0800 Subject: [PATCH] =?UTF-8?q?feat(server):=20=E5=AE=9E=E7=8E=B0=E5=9F=BA?= =?UTF-8?q?=E7=A1=80=20HTTP=20=E6=9C=8D=E5=8A=A1=E5=99=A8=E6=A0=B8?= =?UTF-8?q?=E5=BF=83=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 使用 fasthttp 替代 net/http,实现 Phase 2 核心模块: - HTTP 服务器:fasthttp.Server 配置超时和连接限制 - 路由系统:fasthttp/router 基于 radix tree 匹配 - 静态文件服务:安全检查、索引文件支持 - 日志系统:zerolog 结构化日志 - 中间件框架:链式组合接口 - 虚拟主机管理:按 Host 头选择处理器 Co-Authored-By: Claude --- docs/plan.md | 212 +++++++++++++++++++++++------- go.mod | 17 ++- go.sum | 23 ++++ internal/app/app.go | 60 ++++----- internal/handler/router.go | 48 +++++++ internal/handler/static.go | 57 ++++++++ internal/logging/logging.go | 49 +++++++ internal/middleware/middleware.go | 28 ++++ internal/server/server.go | 74 +++++++++++ internal/server/vhost.go | 62 +++++++++ 10 files changed, 552 insertions(+), 78 deletions(-) create mode 100644 internal/handler/router.go create mode 100644 internal/handler/static.go create mode 100644 internal/logging/logging.go create mode 100644 internal/middleware/middleware.go create mode 100644 internal/server/server.go create mode 100644 internal/server/vhost.go diff --git a/docs/plan.md b/docs/plan.md index 8c1413a..8d8a35b 100644 --- a/docs/plan.md +++ b/docs/plan.md @@ -139,6 +139,33 @@ make build 实现基础 HTTP 服务器、静态文件服务、请求路由、基础日志系统。 +### 技术选型 + +**HTTP 库**:使用 [fasthttp](https://github.com/valyala/fasthttp) 替代 `net/http`。 + +**选择理由**: +- **高性能**:比 net/http 快 6 倍 +- **零分配**:热点路径无内存分配,GC 压力最小 +- **原生支持高性能场景**:无需额外优化 + +**路由库**:使用 [fasthttp/router](https://github.com/fasthttp/router)。 + +**性能对比**: + +| 库 | 特点 | 性能 | +|----|------|------| +| fasthttp | 零分配,高性能 | ⭐⭐⭐⭐⭐ | +| net/http | 标准库,通用 | ⭐⭐⭐ | + +**关键差异**: + +| net/http | fasthttp | +|----------|----------| +| `http.Handler` 接口 | `RequestHandler` 函数 | +| `ServeMux` 内置路由 | 无,需 router 库 | +| `http.Request` 对象 | `*fasthttp.RequestCtx` | +| `http.ResponseWriter` | `ctx` 同时处理读写 | + ### 任务列表 #### 2.0 中间件框架(前置依赖) @@ -148,10 +175,12 @@ make build ```go // internal/middleware/middleware.go +import "github.com/valyala/fasthttp" + // Middleware 中间件接口 type Middleware interface { Name() string - Process(next http.Handler) http.Handler + Process(next fasthttp.RequestHandler) fasthttp.RequestHandler } // Chain 中间件链 @@ -160,14 +189,20 @@ type Chain struct { } // Apply 应用中间件链 -func (c *Chain) Apply(final http.Handler) http.Handler +func (c *Chain) Apply(final fasthttp.RequestHandler) fasthttp.RequestHandler { + handler := final + for i := len(c.middlewares) - 1; i >= 0; i-- { + handler = c.middlewares[i].Process(handler) + } + return handler +} ``` **设计要点**: -- 定义统一的中间件接口,所有中间件(security、compression、logging 等)实现此接口 -- 支持链式组合,按注册顺序执行 -- Phase 1 建立框架,后续阶段填充具体中间件实现 +- 定义统一的中间件接口,所有中间件实现 `RequestHandler` 函数签名 +- 支持链式组合,按注册顺序逆序包装(从后往前) +- Phase 2 建立框架,后续阶段填充具体中间件实现 #### 2.1 基础 HTTP 服务器 @@ -176,31 +211,46 @@ func (c *Chain) Apply(final http.Handler) http.Handler ```go // internal/server/server.go +import "github.com/valyala/fasthttp" + // Server HTTP 服务器 type Server struct { - config *config.Config - handler *handler.Handler - listeners map[string]*net.Listener - running bool - stopChan chan struct{} + config *config.Config + fastServer *fasthttp.Server + handler fasthttp.RequestHandler + running bool } // Start 启动服务器 -func (s *Server) Start() error +func (s *Server) Start() error { + s.fastServer = &fasthttp.Server{ + Handler: s.handler, + ReadTimeout: s.config.Server.ReadTimeout, + WriteTimeout: s.config.Server.WriteTimeout, + IdleTimeout: s.config.Server.IdleTimeout, + MaxConnsPerIP: s.config.Server.MaxConnsPerIP, + MaxRequestsPerConn: s.config.Server.MaxRequestsPerConn, + } + return s.fastServer.ListenAndServe(s.config.Server.Listen) +} -// Stop 停止服务器 -func (s *Server) Stop() error +// Stop 快速停止服务器 +func (s *Server) Stop() error { + return s.fastServer.Shutdown() +} // GracefulStop 优雅停止(等待请求完成) -func (s *Server) GracefulStop(timeout time.Duration) error +func (s *Server) GracefulStop(timeout time.Duration) error { + // fasthttp 的 Shutdown 本身就是优雅关闭 + return s.fastServer.Shutdown() +} ``` **实现要点**: -- 使用 `net/http` 标准库为基础 -- 支持 multiple listeners(多端口/多虚拟主机) -- 优雅关闭:`Shutdown()` 方法 -- keep-alive 配置:`ReadTimeout`、`WriteTimeout`、`IdleTimeout` +- 使用 `fasthttp.Server` 配置超时和连接限制 +- 优雅关闭:`Shutdown()` 方法自动等待请求完成 +- 配置项:`ReadTimeout`、`WriteTimeout`、`IdleTimeout`、`MaxConnsPerIP` #### 2.2 静态文件服务 @@ -209,59 +259,106 @@ func (s *Server) GracefulStop(timeout time.Duration) error ```go // internal/handler/static.go +import "github.com/valyala/fasthttp" + // StaticHandler 静态文件处理器 type StaticHandler struct { root string index []string } -// ServeHTTP 处理静态文件请求 -func (h *StaticHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) +// Handle 处理静态文件请求 +func (h *StaticHandler) Handle(ctx *fasthttp.RequestCtx) { + path := string(ctx.Path()) + + // 安全检查:防止目录遍历 + if strings.Contains(path, "..") { + ctx.Error("Forbidden", fasthttp.StatusForbidden) + return + } + + // 拼接文件路径 + filePath := filepath.Join(h.root, path) + + // 尝试索引文件 + if info, err := os.Stat(filePath); err == nil && info.IsDir() { + for _, idx := range h.index { + idxPath := filepath.Join(filePath, idx) + if fasthttp.ServeFile(ctx, idxPath) == nil { + return + } + } + } + + // 直接返回文件 + fasthttp.ServeFile(ctx, filePath) +} ``` **功能清单**: - 文件路径安全检查(防止目录遍历) -- MIME 类型自动识别(`mime.TypeByExtension`) +- MIME 类型自动识别(fasthttp 内置) - 索引文件支持(index.html、index.htm) -- 目录列表(可选,默认禁用) -- Range 请求支持(部分下载) +- Range 请求支持(fasthttp 内置) - 文件缓存优化(可选) -#### 2.3 请求路由(Location 匹配) +#### 2.3 请求路由 -**路由设计**: +**路由库**:使用 [fasthttp/router](https://github.com/fasthttp/router),基于 radix tree 高效匹配。 + +**实现**: ```go // internal/handler/router.go +import ( + "github.com/valyala/fasthttp" + "github.com/fasthttp/router" +) + // Router 请求路由器 type Router struct { - routes []*Route + router *router.Router } -// Route 路由规则 -type Route struct { - Path string // 路径模式 - Type MatchType // 匹配类型:精确/前缀/正则 - Handler http.Handler +// NewRouter 创建路由器 +func NewRouter() *Router { + return &Router{ + router: router.New(), + } } -// 匹配类型 -type MatchType int -const ( - MatchExact MatchType = iota // 精确匹配 (=) - MatchPrefix // 前缀匹配 (^~) - MatchRegex // 正则匹配 (~) -) +// Register 注册路由 +func (r *Router) Register(path string, handler fasthttp.RequestHandler) { + r.router.GET(path, handler) + r.router.POST(path, handler) + r.router.PUT(path, handler) + r.router.DELETE(path, handler) +} + +// Handler 返回路由处理器 +func (r *Router) Handler() fasthttp.RequestHandler { + return r.router.Handler +} ``` -**匹配优先级**(同 nginx): +**fasthttp/router 匹配类型**: -1. 精确匹配 `=` -2. 前缀匹配(优先)`^~` -3. 正则匹配 `~`(按顺序) -4. 最长前缀匹配 +| 类型 | 语法 | 示例 | +|------|------|------| +| Named | `{name}` | `/user/{id}` | +| Optional | `{name?}` | `/search/{q?}` | +| Regex | `{name:regex}` | `/user/{id:[0-9]+}` | +| Catch-All | `{filepath:*}` | `/files/{filepath:*}` | + +**参数提取**: + +```go +func handler(ctx *fasthttp.RequestCtx) { + id := ctx.UserValue("id") // 获取路由参数 +} +``` #### 2.4 多虚拟主机支持 @@ -270,17 +367,40 @@ const ( ```go // internal/server/vhost.go +import "github.com/valyala/fasthttp" + // VHostManager 虚拟主机管理器 type VHostManager struct { - hosts map[string]*VirtualHost // 按 server_name 索引 + hosts map[string]*VirtualHost // 按 server_name 索引 + defaultHost *VirtualHost // 默认主机 +} + +// VirtualHost 虚拟主机 +type VirtualHost struct { + name string + handler fasthttp.RequestHandler +} + +// Handler 返回虚拟主机选择器 +func (v *VHostManager) Handler() fasthttp.RequestHandler { + return func(ctx *fasthttp.RequestCtx) { + host := string(ctx.Host()) + if vhost, ok := v.hosts[host]; ok { + vhost.handler(ctx) + } else if v.defaultHost != nil { + v.defaultHost.handler(ctx) + } else { + ctx.Error("Host not found", fasthttp.StatusNotFound) + } + } } ``` **功能**: - 按 `Host` 头选择虚拟主机 -- 默认主机fallback -- SNI 支持(SSL) +- 默认主机 fallback +- SNI 支持(SSL,Phase 4) #### 2.5 基础日志系统(Phase 2 必需) diff --git a/go.mod b/go.mod index 58cbc52..1c83bbc 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,19 @@ module rua.plus/lolly go 1.26.1 -require gopkg.in/yaml.v3 v3.0.1 +require ( + github.com/fasthttp/router v1.5.4 + github.com/rs/zerolog v1.35.0 + github.com/valyala/fasthttp v1.69.0 + gopkg.in/yaml.v3 v3.0.1 +) + +require ( + github.com/andybalholm/brotli v1.2.0 // indirect + github.com/klauspost/compress v1.18.2 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + golang.org/x/sys v0.39.0 // indirect +) diff --git a/go.sum b/go.sum index a62c313..05cbafe 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,26 @@ +github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= +github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/fasthttp/router v1.5.4 h1:oxdThbBwQgsDIYZ3wR1IavsNl6ZS9WdjKukeMikOnC8= +github.com/fasthttp/router v1.5.4/go.mod h1:3/hysWq6cky7dTfzaaEPZGdptwjwx0qzTgFCKEWRjgc= +github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= +github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/rs/zerolog v1.35.0 h1:VD0ykx7HMiMJytqINBsKcbLS+BJ4WYjz+05us+LRTdI= +github.com/rs/zerolog v1.35.0/go.mod h1:EjML9kdfa/RMA7h/6z6pYmq1ykOuA8/mjWaEvGI+jcw= +github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38 h1:D0vL7YNisV2yqE55+q0lFuGse6U8lxlg7fYTctlT5Gc= +github.com/savsgio/gotils v0.0.0-20240704082632-aef3928b8a38/go.mod h1:sM7Mt7uEoCeFSCBM+qBrqvEo+/9vdmj19wzp3yzUhmg= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI= +github.com/valyala/fasthttp v1.69.0/go.mod h1:4wA4PfAraPlAsJ5jMSqCE2ug5tqUPwKXxVj8oNECGcw= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/app/app.go b/internal/app/app.go index 0a28b57..2ca8bfd 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -2,7 +2,6 @@ package app import ( - "context" "fmt" "os" "os/signal" @@ -10,6 +9,7 @@ import ( "time" "rua.plus/lolly/internal/config" + "rua.plus/lolly/internal/server" ) // 版本信息,通过 -ldflags 注入。 @@ -82,11 +82,10 @@ func startServer(cfgPath string) int { fmt.Printf("配置加载成功: %s\n", cfgPath) fmt.Printf("监听地址: %s\n", cfg.Server.Listen) - // 创建取消上下文,用于优雅停止(Phase 2 使用)。 - _, cancel := context.WithCancel(context.Background()) - defer cancel() + // 创建服务器 + srv := server.New(cfg) - // 启动信号监听。 + // 启动信号监听 sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGTERM, // 快速停止(kill 或 systemd stop) @@ -94,39 +93,38 @@ func startServer(cfgPath string) int { syscall.SIGQUIT, // 优雅停止 ) - // TODO: 启动 HTTP 服务器(Phase 2 实现) - // server := server.New(cfg) - // go server.Start() + // 启动服务器(在 goroutine 中) + errChan := make(chan error, 1) + go func() { + fmt.Println("服务器启动中...") + if err := srv.Start(); err != nil { + errChan <- err + } + }() - fmt.Println("服务器启动中...(HTTP 服务器待 Phase 2 实现)") - - // 等待信号。 - sig := waitSignal(sigChan) - - // 根据信号类型决定停止方式。 - switch sig { - case syscall.SIGQUIT: - // 优雅停止:等待请求完成。 - fmt.Printf("\n收到 SIGQUIT,优雅停止(等待 %v)...\n", shutdownTimeout) - // TODO: server.GracefulStop(shutdownTimeout) - time.Sleep(100 * time.Millisecond) // 模拟等待(Phase 2 实现真实逻辑) - case syscall.SIGTERM, syscall.SIGINT: - // 快速停止。 - fmt.Printf("\n收到 %v,停止服务器...\n", sigName(sig)) - // TODO: server.Stop() + // 等待信号或启动错误 + select { + case err := <-errChan: + fmt.Fprintf(os.Stderr, "服务器启动失败: %v\n", err) + return 1 + case sig := <-sigChan: + // 根据信号类型决定停止方式 + switch sig { + case syscall.SIGQUIT: + // 优雅停止:等待请求完成 + fmt.Printf("\n收到 SIGQUIT,优雅停止(等待 %v)...\n", shutdownTimeout) + srv.GracefulStop(shutdownTimeout) + case syscall.SIGTERM, syscall.SIGINT: + // 快速停止 + fmt.Printf("\n收到 %v,停止服务器...\n", sigName(sig.(syscall.Signal))) + srv.Stop() + } } - cancel() fmt.Println("服务器已停止") return 0 } -// waitSignal 等待信号,返回接收到的信号。 -func waitSignal(ch <-chan os.Signal) syscall.Signal { - sig := <-ch - return sig.(syscall.Signal) -} - // sigName 返回信号名称(用于日志输出)。 func sigName(sig syscall.Signal) string { switch sig { diff --git a/internal/handler/router.go b/internal/handler/router.go new file mode 100644 index 0000000..f9d26ba --- /dev/null +++ b/internal/handler/router.go @@ -0,0 +1,48 @@ +package handler + +import ( + "github.com/fasthttp/router" + "github.com/valyala/fasthttp" +) + +// Router 请求路由器 +type Router struct { + router *router.Router +} + +// NewRouter 创建路由器 +func NewRouter() *Router { + return &Router{ + router: router.New(), + } +} + +// GET 注册 GET 路由 +func (r *Router) GET(path string, handler fasthttp.RequestHandler) { + r.router.GET(path, handler) +} + +// POST 注册 POST 路由 +func (r *Router) POST(path string, handler fasthttp.RequestHandler) { + r.router.POST(path, handler) +} + +// PUT 注册 PUT 路由 +func (r *Router) PUT(path string, handler fasthttp.RequestHandler) { + r.router.PUT(path, handler) +} + +// DELETE 注册 DELETE 路由 +func (r *Router) DELETE(path string, handler fasthttp.RequestHandler) { + r.router.DELETE(path, handler) +} + +// HEAD 注册 HEAD 路由 +func (r *Router) HEAD(path string, handler fasthttp.RequestHandler) { + r.router.HEAD(path, handler) +} + +// Handler 返回路由处理器 +func (r *Router) Handler() fasthttp.RequestHandler { + return r.router.Handler +} \ No newline at end of file diff --git a/internal/handler/static.go b/internal/handler/static.go new file mode 100644 index 0000000..599f764 --- /dev/null +++ b/internal/handler/static.go @@ -0,0 +1,57 @@ +package handler + +import ( + "os" + "path/filepath" + "strings" + + "github.com/valyala/fasthttp" +) + +// StaticHandler 静态文件处理器 +type StaticHandler struct { + root string + index []string +} + +// NewStaticHandler 创建静态文件处理器 +func NewStaticHandler(root string, index []string) *StaticHandler { + return &StaticHandler{root: root, index: index} +} + +// Handle 处理静态文件请求 +func (h *StaticHandler) Handle(ctx *fasthttp.RequestCtx) { + path := string(ctx.Path()) + + // 安全检查:防止目录遍历 + if strings.Contains(path, "..") { + ctx.Error("Forbidden", fasthttp.StatusForbidden) + return + } + + // 拼接文件路径 + filePath := filepath.Join(h.root, path) + + // 检查文件/目录是否存在 + info, err := os.Stat(filePath) + if err != nil { + ctx.Error("Not Found", fasthttp.StatusNotFound) + return + } + + // 如果是目录,尝试索引文件 + if info.IsDir() { + for _, idx := range h.index { + idxPath := filepath.Join(filePath, idx) + if _, err := os.Stat(idxPath); err == nil { + fasthttp.ServeFile(ctx, idxPath) + return + } + } + ctx.Error("Forbidden", fasthttp.StatusForbidden) + return + } + + // 直接返回文件 + fasthttp.ServeFile(ctx, filePath) +} \ No newline at end of file diff --git a/internal/logging/logging.go b/internal/logging/logging.go new file mode 100644 index 0000000..38c4809 --- /dev/null +++ b/internal/logging/logging.go @@ -0,0 +1,49 @@ +package logging + +import ( + "os" + "time" + + "github.com/rs/zerolog" + "github.com/valyala/fasthttp" +) + +var log zerolog.Logger + +// Init 初始化日志系统 +func Init(level string, pretty bool) { + l := parseLevel(level) + if pretty { + log = zerolog.New(zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.RFC3339}).Level(l).With().Timestamp().Logger() + } else { + log = zerolog.New(os.Stdout).Level(l).With().Timestamp().Logger() + } +} + +// LogAccess 记录访问日志 +func LogAccess(ctx *fasthttp.RequestCtx, status int, size int64, duration time.Duration) { + log.Info(). + Str("method", string(ctx.Method())). + Str("path", string(ctx.Path())). + Int("status", status). + Int64("size", size). + Dur("duration", duration). + Str("remote_addr", ctx.RemoteAddr().String()). + Msg("request") +} + +// parseLevel 解析日志级别 +func parseLevel(level string) zerolog.Level { + switch level { + case "debug": + return zerolog.DebugLevel + case "info": + return zerolog.InfoLevel + case "warn": + return zerolog.WarnLevel + case "error": + return zerolog.ErrorLevel + default: + return zerolog.InfoLevel + } +} \ No newline at end of file diff --git a/internal/middleware/middleware.go b/internal/middleware/middleware.go new file mode 100644 index 0000000..ac9454c --- /dev/null +++ b/internal/middleware/middleware.go @@ -0,0 +1,28 @@ +package middleware + +import "github.com/valyala/fasthttp" + +// Middleware 中间件接口 +type Middleware interface { + Name() string + Process(next fasthttp.RequestHandler) fasthttp.RequestHandler +} + +// Chain 中间件链 +type Chain struct { + middlewares []Middleware +} + +// NewChain 创建中间件链 +func NewChain(middlewares ...Middleware) *Chain { + return &Chain{middlewares: middlewares} +} + +// Apply 应用中间件链(逆序包装) +func (c *Chain) Apply(final fasthttp.RequestHandler) fasthttp.RequestHandler { + handler := final + for i := len(c.middlewares) - 1; i >= 0; i-- { + handler = c.middlewares[i].Process(handler) + } + return handler +} \ No newline at end of file diff --git a/internal/server/server.go b/internal/server/server.go new file mode 100644 index 0000000..c8674d4 --- /dev/null +++ b/internal/server/server.go @@ -0,0 +1,74 @@ +package server + +import ( + "time" + + "github.com/valyala/fasthttp" + "rua.plus/lolly/internal/config" + "rua.plus/lolly/internal/handler" + "rua.plus/lolly/internal/logging" + "rua.plus/lolly/internal/middleware" +) + +// Server HTTP 服务器 +type Server struct { + config *config.Config + fastServer *fasthttp.Server + handler fasthttp.RequestHandler + running bool +} + +// New 创建服务器 +func New(cfg *config.Config) *Server { + return &Server{config: cfg} +} + +// Start 启动服务器 +func (s *Server) Start() error { + // 初始化日志 + logging.Init(s.config.Logging.Error.Level, true) + + // 创建路由 + router := handler.NewRouter() + + // 静态文件服务 + staticHandler := handler.NewStaticHandler( + s.config.Server.Static.Root, + s.config.Server.Static.Index, + ) + + // 注册路由 - 处理所有路径 + router.GET("/{filepath:*}", staticHandler.Handle) + router.HEAD("/{filepath:*}", staticHandler.Handle) + + // 应用中间件 + chain := middleware.NewChain() + s.handler = chain.Apply(router.Handler()) + + // 创建 fasthttp 服务器 + s.fastServer = &fasthttp.Server{ + Handler: s.handler, + ReadTimeout: 30 * time.Second, + WriteTimeout: 30 * time.Second, + IdleTimeout: 120 * time.Second, + MaxConnsPerIP: 1000, + MaxRequestsPerConn: 10000, + } + + s.running = true + return s.fastServer.ListenAndServe(s.config.Server.Listen) +} + +// Stop 快速停止服务器 +func (s *Server) Stop() error { + s.running = false + if s.fastServer != nil { + return s.fastServer.Shutdown() + } + return nil +} + +// GracefulStop 优雅停止 +func (s *Server) GracefulStop(timeout time.Duration) error { + return s.Stop() +} diff --git a/internal/server/vhost.go b/internal/server/vhost.go new file mode 100644 index 0000000..88143ee --- /dev/null +++ b/internal/server/vhost.go @@ -0,0 +1,62 @@ +package server + +import ( + "github.com/valyala/fasthttp" +) + +// VHostManager 虚拟主机管理器 +type VHostManager struct { + hosts map[string]*VirtualHost // 按 server_name 索引 + defaultHost *VirtualHost // 默认主机 +} + +// VirtualHost 虚拟主机 +type VirtualHost struct { + name string + handler fasthttp.RequestHandler +} + +// NewVHostManager 创建虚拟主机管理器 +func NewVHostManager() *VHostManager { + return &VHostManager{ + hosts: make(map[string]*VirtualHost), + } +} + +// AddHost 添加虚拟主机 +func (v *VHostManager) AddHost(name string, handler fasthttp.RequestHandler) { + v.hosts[name] = &VirtualHost{ + name: name, + handler: handler, + } +} + +// SetDefault 设置默认主机 +func (v *VHostManager) SetDefault(handler fasthttp.RequestHandler) { + v.defaultHost = &VirtualHost{ + name: "default", + handler: handler, + } +} + +// Handler 返回虚拟主机选择器 +func (v *VHostManager) Handler() fasthttp.RequestHandler { + return func(ctx *fasthttp.RequestCtx) { + host := string(ctx.Host()) + // 去除端口号 + for i := 0; i < len(host); i++ { + if host[i] == ':' { + host = host[:i] + break + } + } + + if vhost, ok := v.hosts[host]; ok { + vhost.handler(ctx) + } else if v.defaultHost != nil { + v.defaultHost.handler(ctx) + } else { + ctx.Error("Host not found", fasthttp.StatusNotFound) + } + } +}