feat(server): 实现基础 HTTP 服务器核心功能
使用 fasthttp 替代 net/http,实现 Phase 2 核心模块: - HTTP 服务器:fasthttp.Server 配置超时和连接限制 - 路由系统:fasthttp/router 基于 radix tree 匹配 - 静态文件服务:安全检查、索引文件支持 - 日志系统:zerolog 结构化日志 - 中间件框架:链式组合接口 - 虚拟主机管理:按 Host 头选择处理器 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
06e8d55ef5
commit
b445bca96a
212
docs/plan.md
212
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 必需)
|
||||
|
||||
|
||||
17
go.mod
17
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
|
||||
)
|
||||
|
||||
23
go.sum
23
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=
|
||||
|
||||
@ -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 {
|
||||
|
||||
48
internal/handler/router.go
Normal file
48
internal/handler/router.go
Normal file
@ -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
|
||||
}
|
||||
57
internal/handler/static.go
Normal file
57
internal/handler/static.go
Normal file
@ -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)
|
||||
}
|
||||
49
internal/logging/logging.go
Normal file
49
internal/logging/logging.go
Normal file
@ -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
|
||||
}
|
||||
}
|
||||
28
internal/middleware/middleware.go
Normal file
28
internal/middleware/middleware.go
Normal file
@ -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
|
||||
}
|
||||
74
internal/server/server.go
Normal file
74
internal/server/server.go
Normal file
@ -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()
|
||||
}
|
||||
62
internal/server/vhost.go
Normal file
62
internal/server/vhost.go
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user