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 服务器、静态文件服务、请求路由、基础日志系统。
|
||||||
|
|
||||||
|
### 技术选型
|
||||||
|
|
||||||
|
**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 中间件框架(前置依赖)
|
#### 2.0 中间件框架(前置依赖)
|
||||||
@ -148,10 +175,12 @@ make build
|
|||||||
```go
|
```go
|
||||||
// internal/middleware/middleware.go
|
// internal/middleware/middleware.go
|
||||||
|
|
||||||
|
import "github.com/valyala/fasthttp"
|
||||||
|
|
||||||
// Middleware 中间件接口
|
// Middleware 中间件接口
|
||||||
type Middleware interface {
|
type Middleware interface {
|
||||||
Name() string
|
Name() string
|
||||||
Process(next http.Handler) http.Handler
|
Process(next fasthttp.RequestHandler) fasthttp.RequestHandler
|
||||||
}
|
}
|
||||||
|
|
||||||
// Chain 中间件链
|
// Chain 中间件链
|
||||||
@ -160,14 +189,20 @@ type Chain struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Apply 应用中间件链
|
// 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 等)实现此接口
|
- 定义统一的中间件接口,所有中间件实现 `RequestHandler` 函数签名
|
||||||
- 支持链式组合,按注册顺序执行
|
- 支持链式组合,按注册顺序逆序包装(从后往前)
|
||||||
- Phase 1 建立框架,后续阶段填充具体中间件实现
|
- Phase 2 建立框架,后续阶段填充具体中间件实现
|
||||||
|
|
||||||
#### 2.1 基础 HTTP 服务器
|
#### 2.1 基础 HTTP 服务器
|
||||||
|
|
||||||
@ -176,31 +211,46 @@ func (c *Chain) Apply(final http.Handler) http.Handler
|
|||||||
```go
|
```go
|
||||||
// internal/server/server.go
|
// internal/server/server.go
|
||||||
|
|
||||||
|
import "github.com/valyala/fasthttp"
|
||||||
|
|
||||||
// Server HTTP 服务器
|
// Server HTTP 服务器
|
||||||
type Server struct {
|
type Server struct {
|
||||||
config *config.Config
|
config *config.Config
|
||||||
handler *handler.Handler
|
fastServer *fasthttp.Server
|
||||||
listeners map[string]*net.Listener
|
handler fasthttp.RequestHandler
|
||||||
running bool
|
running bool
|
||||||
stopChan chan struct{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start 启动服务器
|
// 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 停止服务器
|
// Stop 快速停止服务器
|
||||||
func (s *Server) Stop() error
|
func (s *Server) Stop() error {
|
||||||
|
return s.fastServer.Shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
// GracefulStop 优雅停止(等待请求完成)
|
// 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` 标准库为基础
|
- 使用 `fasthttp.Server` 配置超时和连接限制
|
||||||
- 支持 multiple listeners(多端口/多虚拟主机)
|
- 优雅关闭:`Shutdown()` 方法自动等待请求完成
|
||||||
- 优雅关闭:`Shutdown()` 方法
|
- 配置项:`ReadTimeout`、`WriteTimeout`、`IdleTimeout`、`MaxConnsPerIP`
|
||||||
- keep-alive 配置:`ReadTimeout`、`WriteTimeout`、`IdleTimeout`
|
|
||||||
|
|
||||||
#### 2.2 静态文件服务
|
#### 2.2 静态文件服务
|
||||||
|
|
||||||
@ -209,59 +259,106 @@ func (s *Server) GracefulStop(timeout time.Duration) error
|
|||||||
```go
|
```go
|
||||||
// internal/handler/static.go
|
// internal/handler/static.go
|
||||||
|
|
||||||
|
import "github.com/valyala/fasthttp"
|
||||||
|
|
||||||
// StaticHandler 静态文件处理器
|
// StaticHandler 静态文件处理器
|
||||||
type StaticHandler struct {
|
type StaticHandler struct {
|
||||||
root string
|
root string
|
||||||
index []string
|
index []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServeHTTP 处理静态文件请求
|
// Handle 处理静态文件请求
|
||||||
func (h *StaticHandler) ServeHTTP(w http.ResponseWriter, r *http.Request)
|
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)
|
- 索引文件支持(index.html、index.htm)
|
||||||
- 目录列表(可选,默认禁用)
|
- Range 请求支持(fasthttp 内置)
|
||||||
- Range 请求支持(部分下载)
|
|
||||||
- 文件缓存优化(可选)
|
- 文件缓存优化(可选)
|
||||||
|
|
||||||
#### 2.3 请求路由(Location 匹配)
|
#### 2.3 请求路由
|
||||||
|
|
||||||
**路由设计**:
|
**路由库**:使用 [fasthttp/router](https://github.com/fasthttp/router),基于 radix tree 高效匹配。
|
||||||
|
|
||||||
|
**实现**:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
// internal/handler/router.go
|
// internal/handler/router.go
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/valyala/fasthttp"
|
||||||
|
"github.com/fasthttp/router"
|
||||||
|
)
|
||||||
|
|
||||||
// Router 请求路由器
|
// Router 请求路由器
|
||||||
type Router struct {
|
type Router struct {
|
||||||
routes []*Route
|
router *router.Router
|
||||||
}
|
}
|
||||||
|
|
||||||
// Route 路由规则
|
// NewRouter 创建路由器
|
||||||
type Route struct {
|
func NewRouter() *Router {
|
||||||
Path string // 路径模式
|
return &Router{
|
||||||
Type MatchType // 匹配类型:精确/前缀/正则
|
router: router.New(),
|
||||||
Handler http.Handler
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 匹配类型
|
// Register 注册路由
|
||||||
type MatchType int
|
func (r *Router) Register(path string, handler fasthttp.RequestHandler) {
|
||||||
const (
|
r.router.GET(path, handler)
|
||||||
MatchExact MatchType = iota // 精确匹配 (=)
|
r.router.POST(path, handler)
|
||||||
MatchPrefix // 前缀匹配 (^~)
|
r.router.PUT(path, handler)
|
||||||
MatchRegex // 正则匹配 (~)
|
r.router.DELETE(path, handler)
|
||||||
)
|
}
|
||||||
|
|
||||||
|
// Handler 返回路由处理器
|
||||||
|
func (r *Router) Handler() fasthttp.RequestHandler {
|
||||||
|
return r.router.Handler
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**匹配优先级**(同 nginx):
|
**fasthttp/router 匹配类型**:
|
||||||
|
|
||||||
1. 精确匹配 `=`
|
| 类型 | 语法 | 示例 |
|
||||||
2. 前缀匹配(优先)`^~`
|
|------|------|------|
|
||||||
3. 正则匹配 `~`(按顺序)
|
| Named | `{name}` | `/user/{id}` |
|
||||||
4. 最长前缀匹配
|
| 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 多虚拟主机支持
|
#### 2.4 多虚拟主机支持
|
||||||
|
|
||||||
@ -270,17 +367,40 @@ const (
|
|||||||
```go
|
```go
|
||||||
// internal/server/vhost.go
|
// internal/server/vhost.go
|
||||||
|
|
||||||
|
import "github.com/valyala/fasthttp"
|
||||||
|
|
||||||
// VHostManager 虚拟主机管理器
|
// VHostManager 虚拟主机管理器
|
||||||
type VHostManager struct {
|
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` 头选择虚拟主机
|
- 按 `Host` 头选择虚拟主机
|
||||||
- 默认主机fallback
|
- 默认主机 fallback
|
||||||
- SNI 支持(SSL)
|
- SNI 支持(SSL,Phase 4)
|
||||||
|
|
||||||
#### 2.5 基础日志系统(Phase 2 必需)
|
#### 2.5 基础日志系统(Phase 2 必需)
|
||||||
|
|
||||||
|
|||||||
17
go.mod
17
go.mod
@ -2,4 +2,19 @@ module rua.plus/lolly
|
|||||||
|
|
||||||
go 1.26.1
|
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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
@ -10,6 +9,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"rua.plus/lolly/internal/config"
|
"rua.plus/lolly/internal/config"
|
||||||
|
"rua.plus/lolly/internal/server"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 版本信息,通过 -ldflags 注入。
|
// 版本信息,通过 -ldflags 注入。
|
||||||
@ -82,11 +82,10 @@ func startServer(cfgPath string) int {
|
|||||||
fmt.Printf("配置加载成功: %s\n", cfgPath)
|
fmt.Printf("配置加载成功: %s\n", cfgPath)
|
||||||
fmt.Printf("监听地址: %s\n", cfg.Server.Listen)
|
fmt.Printf("监听地址: %s\n", cfg.Server.Listen)
|
||||||
|
|
||||||
// 创建取消上下文,用于优雅停止(Phase 2 使用)。
|
// 创建服务器
|
||||||
_, cancel := context.WithCancel(context.Background())
|
srv := server.New(cfg)
|
||||||
defer cancel()
|
|
||||||
|
|
||||||
// 启动信号监听。
|
// 启动信号监听
|
||||||
sigChan := make(chan os.Signal, 1)
|
sigChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigChan,
|
signal.Notify(sigChan,
|
||||||
syscall.SIGTERM, // 快速停止(kill 或 systemd stop)
|
syscall.SIGTERM, // 快速停止(kill 或 systemd stop)
|
||||||
@ -94,39 +93,38 @@ func startServer(cfgPath string) int {
|
|||||||
syscall.SIGQUIT, // 优雅停止
|
syscall.SIGQUIT, // 优雅停止
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: 启动 HTTP 服务器(Phase 2 实现)
|
// 启动服务器(在 goroutine 中)
|
||||||
// server := server.New(cfg)
|
errChan := make(chan error, 1)
|
||||||
// go server.Start()
|
go func() {
|
||||||
|
fmt.Println("服务器启动中...")
|
||||||
|
if err := srv.Start(); err != nil {
|
||||||
|
errChan <- err
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
fmt.Println("服务器启动中...(HTTP 服务器待 Phase 2 实现)")
|
// 等待信号或启动错误
|
||||||
|
select {
|
||||||
// 等待信号。
|
case err := <-errChan:
|
||||||
sig := waitSignal(sigChan)
|
fmt.Fprintf(os.Stderr, "服务器启动失败: %v\n", err)
|
||||||
|
return 1
|
||||||
// 根据信号类型决定停止方式。
|
case sig := <-sigChan:
|
||||||
switch sig {
|
// 根据信号类型决定停止方式
|
||||||
case syscall.SIGQUIT:
|
switch sig {
|
||||||
// 优雅停止:等待请求完成。
|
case syscall.SIGQUIT:
|
||||||
fmt.Printf("\n收到 SIGQUIT,优雅停止(等待 %v)...\n", shutdownTimeout)
|
// 优雅停止:等待请求完成
|
||||||
// TODO: server.GracefulStop(shutdownTimeout)
|
fmt.Printf("\n收到 SIGQUIT,优雅停止(等待 %v)...\n", shutdownTimeout)
|
||||||
time.Sleep(100 * time.Millisecond) // 模拟等待(Phase 2 实现真实逻辑)
|
srv.GracefulStop(shutdownTimeout)
|
||||||
case syscall.SIGTERM, syscall.SIGINT:
|
case syscall.SIGTERM, syscall.SIGINT:
|
||||||
// 快速停止。
|
// 快速停止
|
||||||
fmt.Printf("\n收到 %v,停止服务器...\n", sigName(sig))
|
fmt.Printf("\n收到 %v,停止服务器...\n", sigName(sig.(syscall.Signal)))
|
||||||
// TODO: server.Stop()
|
srv.Stop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel()
|
|
||||||
fmt.Println("服务器已停止")
|
fmt.Println("服务器已停止")
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// waitSignal 等待信号,返回接收到的信号。
|
|
||||||
func waitSignal(ch <-chan os.Signal) syscall.Signal {
|
|
||||||
sig := <-ch
|
|
||||||
return sig.(syscall.Signal)
|
|
||||||
}
|
|
||||||
|
|
||||||
// sigName 返回信号名称(用于日志输出)。
|
// sigName 返回信号名称(用于日志输出)。
|
||||||
func sigName(sig syscall.Signal) string {
|
func sigName(sig syscall.Signal) string {
|
||||||
switch sig {
|
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