server

package
v0.8.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 11, 2026 License: AGPL-3.0 Imports: 25 Imported by: 0

Documentation

Overview

Package server 把 relay、channel 与 plugin 装配成一个可监听的 HTTP 服务, 对外暴露四种方言的端点。

装配规则只有一条:client 方言与渠道方言相同时按字节透传,不同时经 relay 转换。 插件在四个时机介入,插件失败不影响转发可用性。

Index

Constants

View Source
const SessionHeaderName = "X-TokenMP-Session"

SessionHeaderName 是客户端声明会话标识的请求头。

同一段会话的连续请求带上同一个值,core 会把它们粘到同一个上游渠道上,让上游 的提示缓存有机会命中。这个头只在本进程内使用,不会转发给上游。

Variables

This section is empty.

Functions

func BuildPlugins

func BuildPlugins(cfg *config.Config, logger *slog.Logger) (*plugin.Store, error)

BuildPlugins 按配置加载插件目录并构造插件集合。

插件目录不存在且没有启用任何插件时视为没有插件;启用了插件却找不到目录则报错, 否则「插件没生效」只能靠读日志发现。

func WithUpstreamHeader added in v0.5.0

func WithUpstreamHeader(ctx context.Context, name, value string) context.Context

WithUpstreamHeader 把一条上游请求头挂到 ctx 上,转发这个请求时随上游请求带出去。

宿主在 Options.Middleware 里调用:中间件装在内核外侧,这里的 ctx 会一路传到 转发路径与 OnRequestDone。同一个名字重复设置时后一次生效,等价于 http.Header.Set。

客户端自带的请求头默认不转发,只有名字出现在渠道 pass_headers 名单里时才会带上, 因此这里明确设置的头总是会上游。

与渠道配置里的同名头按请求覆盖:请求级比渠道级具体,按请求覆盖某个渠道头正是 这个入口的用途。它也比名字出现在渠道 pass_headers 名单里时从客户端转发的头更 具体,同名时宿主赢。覆盖是显式的,渠道配置自己不受影响,宿主没设的同名头照旧 上去,被覆盖的那个只发一条。覆盖是按名字整个替换:宿主看不到渠道配置里的原值, 两个值都想要得自己拼。渠道的鉴权头是例外,它在最后按 Auth 写入,宿主设的同名头 不生效,凭据始终由渠道决定。

一个名字只带一个值:ctx 里存的是名字到取值的映射,重复字段的头(比如两条同名 Cookie)表达不了。

名字不是合法的 HTTP 头名(RFC 7230 的 token),或取值含控制字符时,这条头在转发 时被丢弃并记一行 WARN:让一个写错的头名把整条请求带成 502 代价太大。水平制表符 在取值里是合法的,其他控制字符不是。

Types

type Attempt added in v0.3.0

type Attempt struct {
	Channel  string
	Dialect  dialect.Name
	Upstream string
	// Status 是上游返回的状态码。0 表示没拿到上游响应,原因看 Failure。
	Status int
	// Failure 是没拿到上游响应的原因,Status 非 0 时恒为 FailureNone。
	Failure Failure
	// Retries 是这条候选内部的渠道内重试次数,0 表示没有重试。它回答「上游实际
	// 多收了几次请求」,与路由级的 attempts 不是一回事。
	Retries int
	// Duration 是这次尝试的耗时,从选定候选算到拿到响应头或失败。含这条候选内部
	// 的全部退避等待。
	Duration time.Duration
}

Attempt 是一次候选尝试。

type Failure added in v0.3.0

type Failure string

Failure 是一次尝试没拿到上游响应的原因。

取值封闭:不把上游的错误文字带进来。那是客户端与上游可控的字符串,长度没有上限, 放进公开面就得跟着定一套截断口径。原因文字在 WARN 日志里。

const (
	// FailureNone 表示没有失败,看 Status。
	FailureNone Failure = ""
	// FailureChannel 表示渠道在装配里不存在。
	FailureChannel Failure = "channel"
	// FailureConvert 表示请求转换失败,没发出请求。
	FailureConvert Failure = "convert"
	// FailureConnect 表示连接上游失败,或读上游响应失败。
	FailureConnect Failure = "connect"
)

type Option added in v0.3.0

type Option func(*Options)

Option 覆盖 FromConfig 的装配参数。

func WithMiddleware added in v0.4.0

func WithMiddleware(middleware func(http.Handler) http.Handler) Option

WithMiddleware 设置包住内核处理器的宿主中间件,见 Options.Middleware。

func WithRequestDone added in v0.3.0

func WithRequestDone(onDone func(context.Context, Outcome)) Option

WithRequestDone 设置每次请求结束时调用的回调,见 Options.OnRequestDone。

func WithoutAccessLog added in v0.4.0

func WithoutAccessLog() Option

WithoutAccessLog 关掉内核自己那两行访问日志,见 Options.DisableAccessLog。

type Options

type Options struct {
	// Config 是配置,必填。
	Config *config.Config
	// Channels 是渠道客户端集合,必填。
	Channels *channel.Set
	// Plugins 是插件集合,为 nil 时视为没有插件。
	Plugins *plugin.Store
	// Engine 是转换内核,为 nil 时用零值内核。
	//
	// 零值内核不带任何行为开关。按配置构造内核是 FromConfigWith 的事:它把
	// defaults 段的默认值翻成 relay.Options 再注入这里。这个字段留给直接调 New
	// 的宿主自己注入内核,New 不读配置。
	Engine *relay.Engine
	// Logger 是日志入口,为 nil 时丢弃日志。
	Logger *slog.Logger
	// Now 返回当前时间,为 nil 时用 time.Now。
	Now func() time.Time
	// MaxBodyBytes 是单个请求体的上限,为 0 时用 config.DefaultMaxBodyBytes。
	// FromConfigWith 会先把它填成配置里 defaults.max_body_bytes 的生效值。
	MaxBodyBytes int64
	// OnRequestDone 在每次请求结束时同步调用,交出这次请求的原始量。
	//
	// ctx 是本次请求一直沿用下来的那个:宿主在 Options.Middleware 里加进 context 的
	// 值在这里能读到,用来把请求级的记账状态跟结束时这份原始量对上。
	//
	// 为 nil 时什么也不做,也不构造 Outcome。
	OnRequestDone func(context.Context, Outcome)
	// Middleware 包住内核的整个处理器,包括访问日志。宿主在这里记自己的日志、
	// 测首字节、加响应头、接 tracing,也可以换掉请求体(内核从 request.Body 读,
	// 换过之后参与路由的就是新内容)或根本不调用 next。
	//
	// 装在外侧:宿主加进 context 的值会一路传下去,OnRequestDone 的 ctx 里有它。
	//
	// 改请求头默认不会传到上游:只有渠道 pass_headers 名单里的头会跟随,其余请求头
	// 与 Auth 都由内核按渠道配置决定。要按请求加上游头,在中间件里调
	// WithUpstreamHeader 往 ctx 上挂。
	//
	// 包装 http.ResponseWriter 时必须把 Flush 转发下去,否则流式响应会静默退化成
	// 不刷新。
	//
	// 为 nil 时什么都不装。
	Middleware func(http.Handler) http.Handler
	// DisableAccessLog 关掉内核自己那两行访问日志。
	//
	// 关掉的是「请求进入」与「请求完成」,包括失败请求的 WARN 完成行;request_id 仍然
	// 生成、其余 WARN 行照旧带着它,OnRequestDone 也不受影响。
	// 进入行里的 client 与 user_agent 会一起没有,需要的话从 Outcome 的同名字段自己记。
	// 默认为 false,即照常记录。
	DisableAccessLog bool
}

Options 是服务的装配参数。

type Outcome added in v0.3.0

type Outcome struct {
	// RequestID 是这次请求的 id,与访问日志里的 request_id 相同。
	RequestID string
	Method    string
	Path      string
	// Client 是去掉端口之后的客户端地址,UserAgent 是请求头里的 User-Agent。
	Client    string
	UserAgent string

	// Status 是写回客户端的状态码。处理器没有显式写响应头时按 200 记,与访问日志一致。
	Status int
	// Duration 是从请求进入中间件到处理器返回的时长,含渠道内重试的退避、换候选与
	// 等待上游的全部时间。不拆成几段:宿主算服务时长要的就是客户端实际等了多久,
	// 拆开之后还得自己加回去,反而多一处会算错的地方。
	Duration time.Duration
	// FirstByte 是从请求进入中间件到首次写出响应体的时长,也就是 core 能测到的 TTFT。
	// 整个请求没有写出过响应体时为 0。
	//
	// 只算响应体,不算响应头:流式路径拿到上游响应头就写响应头,而第一个分片可能过很久
	// 才到,按响应头记会让 TTFT 系统性偏小。非流式响应是一次写完的,这个值因此约等于
	// Duration,看不出 TTFT——该不该算由宿主按 Streaming 自己判断。
	FirstByte time.Duration
	// BytesWritten 是写回客户端的响应体字节数,不含响应头。
	BytesWritten int64

	// Model 是插件改写之后参与匹配的模型名。Channel 是实际服务的那条渠道,降级换过
	// 渠道时是真正服务的那个;没有一条候选服务成功时为空串。
	Model   string
	Channel string
	// Streaming 报告客户端是否要求流式,取自请求内容,不是上游实际按流式回的。
	Streaming bool

	// Attempts 按顺序记下每条候选尝试的耗时与状态,长度与访问日志的 attempts 相同。
	Attempts []Attempt

	// Usage 是上游给出的用量,上游没给时为 nil。
	Usage *Usage
}

Outcome 是一次请求结束时交给宿主的原始量。

只交原始量,不算指标。指标口径随宿主变:TTFT 是从请求进入算还是从上游首字节算, TPS 的分母取总时长还是生成时长,core 算出来的一定不合宿主的意。宿主拿这些量自己算。

字段是每次请求都会填的,没有「有时有有时没有」的项:上游没给用量时 Usage 为 nil, 这是唯一的可选项。

type Server

type Server struct {
	// contains filtered or unexported fields
}

Server 是一个转发服务。

func FromConfig

func FromConfig(cfg *config.Config, logger *slog.Logger) (*Server, error)

FromConfig 按配置装配渠道与插件,再创建服务。

配置里启用但目录里不存在的插件会直接报错:静默降级会让「插件没生效」变成 一个只能靠读日志发现的问题。

func FromConfigWith added in v0.3.0

func FromConfigWith(cfg *config.Config, logger *slog.Logger, opts ...Option) (*Server, error)

FromConfigWith 与 FromConfig 相同,另外可以用 opts 覆盖装配参数。

做成单独一个函数而不是给 FromConfig 加可变参数:可变参数会让它不再能赋给 func(*config.Config, *slog.Logger) (*Server, error) 类型的变量,按仓库的兼容 承诺算破坏性变更。要完全掌控装配就直接用 New。

func New

func New(opts Options) (*Server, error)

New 创建服务。

func (*Server) Handler

func (s *Server) Handler() http.Handler

Handler 返回 HTTP 处理器,便于嵌入其它服务或直接做测试。

func (*Server) Listen

func (s *Server) Listen(ctx context.Context) (net.Listener, error)

Listen 按配置创建监听器。ctx 只影响监听建立阶段。

func (*Server) Run

func (s *Server) Run(ctx context.Context) error

Run 监听并服务,直到 ctx 取消。ctx 取消后停止接收新连接并等待在途请求结束。

func (*Server) Serve

func (s *Server) Serve(ctx context.Context, listener net.Listener) error

Serve 在给定监听器上服务,直到 ctx 取消。

type Usage added in v0.3.0

type Usage struct {
	PromptTokens     int
	CompletionTokens int
	TotalTokens      int
	// CachedTokens 是命中提示缓存的那部分提示词 token,上游没报时为 0。
	CachedTokens int
	// CacheWriteTokens 是写进提示缓存的那部分提示词 token,上游没报时为 0。
	// Claude 与 OpenAI 各用一种字段名报这个数,这里交出转换层归一之后的一个值,
	// 宿主不必再去分辨上游用的是哪一种写法。
	CacheWriteTokens int
}

Usage 是上游给出的用量。字段与访问日志一致,够算 TPS 与账目。

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL