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:
xfy 2026-04-02 15:23:54 +08:00
parent 06e8d55ef5
commit b445bca96a
10 changed files with 552 additions and 78 deletions

View File

@ -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 支持SSLPhase 4
#### 2.5 基础日志系统Phase 2 必需)

17
go.mod
View File

@ -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
View File

@ -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=

View File

@ -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 {

View 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
}

View 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)
}

View 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
}
}

View 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
View 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
View 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)
}
}
}