refactor(handler): separate sendfile common code

Create sendfile_common.go for shared constants and functions:
- MinSendfileSize constant
- getNetConn helper
- copyFile fallback function

Platform-specific files now only contain platform implementations.
Eliminates ~50 lines of duplicate code between Linux and non-Linux.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
xfy 2026-04-29 18:24:15 +08:00
parent ecb020fed9
commit a365cd2033
3 changed files with 63 additions and 99 deletions

View File

@ -8,20 +8,12 @@
package handler
import (
"io"
"net"
"os"
"syscall"
"github.com/valyala/fasthttp"
)
const (
// MinSendfileSize 使用 sendfile 的最小文件大小8KB
// 小于该值的文件使用普通 io.Copy避免系统调用开销。
MinSendfileSize = 8 * 1024
)
// SendFile 零拷贝文件传输。
//
// 大文件使用系统调用直接从文件传输到 socket避免用户空间拷贝
@ -68,47 +60,6 @@ func SendFile(ctx *fasthttp.RequestCtx, file *os.File, offset, length int64) err
return nil
}
// getNetConn 从 fasthttp.RequestCtx 获取底层 net.Conn。
//
// 参数:
// - ctx: fasthttp 请求上下文
//
// 返回值:
// - net.Conn: 底层网络连接,如果无法获取则返回 nil
func getNetConn(ctx *fasthttp.RequestCtx) net.Conn {
// fasthttp 内部使用 net.Conn通过接口获取
return ctx.Conn()
}
// copyFile 普通文件拷贝fallback
//
// 使用 io.Copy 进行文件传输,适用于不支持 sendfile 的平台或小文件。
//
// 参数:
// - ctx: fasthttp 请求上下文,作为写入目标
// - file: 源文件对象
// - offset: 文件起始偏移量
// - length: 传输长度0 表示拷贝到文件末尾
//
// 返回值:
// - error: 拷贝过程中的错误
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 非 Linux 平台的 sendfile 实现。
//
// macOS 和 Windows 不支持 sendfile 系统调用,返回 ENOTSUP 触发 fallback。
@ -121,8 +72,8 @@ func copyFile(ctx *fasthttp.RequestCtx, file *os.File, offset, length int64) err
//
// 返回值:
// - error: 始终返回 ENOTSUP表示不支持
func platformSendfile(conn net.Conn, file *os.File, offset, length int64) error {
func platformSendfile(conn any, file *os.File, offset, length int64) error {
// macOS sendfile 签名复杂,简化使用 fallback
// Windows TransmitFile 需要特殊 API
return syscall.ENOTSUP
}
}

View File

@ -0,0 +1,59 @@
// Package handler 提供 HTTP 请求处理器,包括路由、静态文件服务和零拷贝传输。
//
// 该文件包含 sendfile 的公共代码,供所有平台使用。
//
// 作者xfy
package handler
import (
"io"
"net"
"os"
"github.com/valyala/fasthttp"
)
const (
// MinSendfileSize 使用 sendfile 的最小文件大小8KB
// 小于该值的文件使用普通 io.Copy避免系统调用开销。
MinSendfileSize = 8 * 1024
)
// getNetConn 从 fasthttp.RequestCtx 获取底层 net.Conn。
//
// 参数:
// - ctx: fasthttp 请求上下文
//
// 返回值:
// - net.Conn: 底层网络连接,如果无法获取则返回 nil
func getNetConn(ctx *fasthttp.RequestCtx) net.Conn {
return ctx.Conn()
}
// copyFile 普通文件拷贝fallback
//
// 使用 io.Copy 进行文件传输,适用于不支持 sendfile 的平台或小文件。
//
// 参数:
// - ctx: fasthttp 请求上下文,作为写入目标
// - file: 源文件对象
// - offset: 文件起始偏移量
// - length: 传输长度0 表示拷贝到文件末尾
//
// 返回值:
// - error: 拷贝过程中的错误
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
}
}
if length > 0 {
_, err := io.CopyN(ctx, file, length)
return err
}
_, err := io.Copy(ctx, file)
return err
}

View File

@ -2,13 +2,12 @@
// Package handler 提供 HTTP 请求处理器,包括路由、静态文件服务和零拷贝传输。
//
// 该文件包含 Linux 平台完整的 sendfile 实现(零拷贝 + 公共函数)。
// 该文件包含 Linux 平台完整的 sendfile 实现(零拷贝)。
//
// 作者xfy
package handler
import (
"io"
"net"
"os"
"syscall"
@ -18,10 +17,6 @@ import (
)
const (
// MinSendfileSize 使用 sendfile 的最小文件大小8KB
// 小于该值的文件使用普通 io.Copy避免系统调用开销。
MinSendfileSize = 8 * 1024
// sendfileMaxRetries sendfile 系统调用最大重试次数。
// 用于处理 EAGAIN/EWOULDBLOCK 等临时性错误。
sendfileMaxRetries = 100
@ -71,47 +66,6 @@ func SendFile(ctx *fasthttp.RequestCtx, file *os.File, offset, length int64) err
return nil
}
// getNetConn 从 fasthttp.RequestCtx 获取底层 net.Conn。
//
// 用于获取网络连接以便提取 socket 文件描述符。
//
// 参数:
// - ctx: fasthttp 请求上下文
//
// 返回值:
// - net.Conn: 底层网络连接,如果无法获取则返回 nil
func getNetConn(ctx *fasthttp.RequestCtx) net.Conn {
return ctx.Conn()
}
// copyFile 普通文件拷贝fallback
//
// 使用 io.Copy 进行文件传输,适用于不支持 sendfile 的场景或小文件。
//
// 参数:
// - ctx: fasthttp 请求上下文,作为写入目标
// - file: 源文件对象
// - offset: 文件起始偏移量
// - length: 传输长度0 表示拷贝到文件末尾
//
// 返回值:
// - error: 拷贝过程中的错误
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
}
}
if length > 0 {
_, err := io.CopyN(ctx, file, length)
return err
}
_, err := io.Copy(ctx, file)
return err
}
// linuxSendfile Linux sendfile 系统调用。
//
// 使用 Linux 特有的 sendfile 系统调用实现零拷贝传输。
@ -213,4 +167,4 @@ func getSocketFd(conn net.Conn) (uintptr, error) {
default:
return 0, syscall.ENOTSUP
}
}
}