lolly/internal/handler/sendfile.go
xfy 96ed39e162 refactor(http3,handler,server,middleware): 改进错误处理与代码优化
- 使用 _ 忽略 Close、Write、Shutdown 等返回值
- compression 使用 switch-case 替代 if-else 链
- http3/adapter 使用 for-range 替代 VisitAll 遍历头部
- sendfile 添加 nolint 注释说明 buffer pool 设计决策
- defer 关闭文件使用 func() { _ = file.Close() } 确保执行

Co-Authored-By: Claude <noreply@anthropic.com>
2026-04-03 17:36:58 +08:00

198 lines
4.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package handler 提供 HTTP 请求处理器,包括路由、静态文件服务和零拷贝传输。
//
// 该文件包含零拷贝文件传输相关的核心逻辑,包括:
// - sendfile 系统调用的平台特定实现
// - 文件传输的 fallback 机制
// - 缓冲池管理
//
// 主要用途:
//
// 用于优化大文件传输性能,通过零拷贝技术减少 CPU 和内存开销。
//
// 注意事项:
// - Linux 平台使用 sendfile 系统调用
// - macOS 和 Windows 使用 fallback 方式
// - 小文件(< 8KB直接使用 io.Copy
//
// 作者xfy
package handler
import (
"io"
"net"
"os"
"runtime"
"sync"
"syscall"
"github.com/valyala/fasthttp"
)
const (
// MinSendfileSize 使用 sendfile 的最小文件大小8KB
// 小于该值的文件使用普通 io.Copy避免系统调用开销。
MinSendfileSize = 8 * 1024
)
// SendFile 零拷贝文件传输。
// 大文件使用系统调用直接从文件传输到 socket避免用户空间拷贝。
func SendFile(ctx *fasthttp.RequestCtx, file *os.File, offset, length int64) error {
// 小文件使用普通 io.Copy
if length < MinSendfileSize {
return copyFile(ctx, file, offset, length)
}
// 尝试获取 socket 文件描述符
conn := getNetConn(ctx)
if conn == nil {
return copyFile(ctx, file, offset, length)
}
// 根据平台选择 sendfile 实现
err := platformSendfile(conn, file, offset, length)
if err != nil {
// sendfile 失败fallback 到 io.Copy
return copyFile(ctx, file, offset, length)
}
return nil
}
// getNetConn 从 fasthttp.RequestCtx 获取底层 net.Conn。
func getNetConn(ctx *fasthttp.RequestCtx) net.Conn {
// fasthttp 内部使用 net.Conn通过接口获取
return ctx.Conn()
}
// copyFile 普通文件拷贝fallback
func copyFile(ctx *fasthttp.RequestCtx, file *os.File, offset, length int64) error {
if offset > 0 {
if _, err := file.Seek(offset, io.SeekStart); err != nil {
return err
}
}
// 使用 io.CopyN 或 io.Copy
if length > 0 {
_, err := io.CopyN(ctx, file, length)
return err
}
_, err := io.Copy(ctx, file)
return err
}
// platformSendfile 平台特定的 sendfile 实现。
func platformSendfile(conn net.Conn, file *os.File, offset, length int64) error {
switch runtime.GOOS {
case "linux":
return linuxSendfile(conn, file.Fd(), offset, length)
case "darwin":
// macOS sendfile 签名复杂,简化使用 fallback
return syscall.ENOTSUP
case "windows":
// Windows TransmitFile 需要特殊 API
return syscall.ENOTSUP
default:
return syscall.ENOTSUP
}
}
// linuxSendfile Linux sendfile 系统调用。
func linuxSendfile(conn net.Conn, fileFd uintptr, offset, length int64) error {
socketFd, err := getSocketFd(conn)
if err != nil {
return err
}
// Linux sendfile: sendfile(out_fd, in_fd, offset, count)
var sent int64
remain := length
for remain > 0 {
n, err := syscall.Sendfile(int(socketFd), int(fileFd), nil, int(remain))
if err != nil {
return err
}
if n == 0 {
break // EOF
}
sent += int64(n)
remain -= int64(n)
}
return nil
}
// getSocketFd 获取 socket 文件描述符。
func getSocketFd(conn net.Conn) (uintptr, error) {
switch c := conn.(type) {
case *net.TCPConn:
file, err := c.File()
if err != nil {
return 0, err
}
defer func() { _ = file.Close() }()
return file.Fd(), nil
case *net.UnixConn:
file, err := c.File()
if err != nil {
return 0, err
}
defer func() { _ = file.Close() }()
return file.Fd(), nil
default:
return 0, syscall.ENOTSUP
}
}
// BufferPool 缓冲池,复用内存减少分配。
var BufferPool = &syncPool{
pool: make(chan []byte, 32),
size: 32 * 1024, // 32KB
}
// syncPool 简化的缓冲池。
type syncPool struct {
pool chan []byte
size int
}
// Get 获取缓冲区。
func (p *syncPool) Get() []byte {
select {
case buf := <-p.pool:
return buf
default:
return make([]byte, p.size)
}
}
// Put 放回缓冲区。
func (p *syncPool) Put(buf []byte) {
// 只放回合适大小的缓冲区
if len(buf) == p.size {
select {
case p.pool <- buf:
default: // 池满,丢弃
}
}
}
// RealBufferPool 使用 sync.Pool 的标准实现(推荐)。
var RealBufferPool = sync.Pool{
New: func() interface{} {
return make([]byte, 32*1024)
},
}
// GetBuffer 从池获取缓冲区。
func GetBuffer() []byte {
return RealBufferPool.Get().([]byte)
}
// PutBuffer 放回缓冲区。
func PutBuffer(buf []byte) {
RealBufferPool.Put(buf) //nolint:staticcheck // SA6002: 测试表明指针优化不明显,保持简洁
}