filter-chain里的这个handler作用是在HTTP响应头Warning
里添加请求处理过程中的一些警告信息。
// WithWarningRecorder attaches a deduplicating k8s.io/apiserver/pkg/warning#WarningRecorder to the request context.
func WithWarningRecorder(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
recorder := &recorder{writer: w}
// 把处理警告信息的recorder 放到请求的Context中.
req = req.WithContext(warning.WithWarningRecorder(req.Context(), recorder))
// 交给下一个handler继续处理
handler.ServeHTTP(w, req)
})
}
// WithWarningRecorder returns a new context that wraps the provided context and contains the provided Recorder implementation.
// The returned context can be passed to AddWarning().
func WithWarningRecorder(ctx context.Context, recorder Recorder) context.Context {
// note: 不展开,已分析
return context.WithValue(ctx, warningRecorderKey, recorder)
}
Recorder
Interface Recorder
表示提供警告信息处理的一类结构,struct recorder
实现了它。
// Recorder provides a method for recording warnings
type Recorder interface {
// AddWarning adds the specified warning to the response.
// agent must be valid UTF-8, and must not contain spaces, quotes, backslashes, or control characters.
// text must be valid UTF-8, and must not contain control characters.
AddWarning(agent, text string)
}
- recorder
这里runes
是一个字符的意思,一个汉字或者英文字母都代表一个字符, runes是utf-8的字符,占用4个字节,也就是int32
type recorder struct {
// lock guards calls to AddWarning from multiple threads
lock sync.Mutex
// recorded tracks whether AddWarning was already called with a given text
recorded map[string]bool // 警告信息去重(dedupe)
// ordered tracks warnings added so they can be replayed and truncated if needed
ordered []recordedWarning // 大部分agent都是空字符串`""`
// written tracks how many runes of text have been added as warning headers
written int // utf-8字符数
// truncating tracks if we have already exceeded truncateAtTotalRunes and are now truncating warning messages as we add them
truncating bool // 信息太长截断
// writer is the response writer to add warning headers to
writer http.ResponseWriter
}
// 一条完整的警告信息
type recordedWarning struct {
agent string
text string
}
下面来分析下如何实现AddWarning(agent, text string)
方法。
func (r *recorder) AddWarning(agent, text string) {
if len(text) == 0 {
return
}
r.lock.Lock()
defer r.lock.Unlock()
// truncateAtTotalRunes是响应头Warning的总长度限制,超长且已经截断了就不再添加了
// if we've already exceeded our limit and are already truncating, return early
if r.written >= truncateAtTotalRunes && r.truncating {
return
}
// init if needed
if r.recorded == nil {
r.recorded = map[string]bool{}
}
// 警告信息去重
// dedupe if already warned
if r.recorded[text] {
return
}
r.recorded[text] = true
// 记录完整警告信息
r.ordered = append(r.ordered, recordedWarning{agent: agent, text: text})
// truncateItemRunes是一条警告信息限制,超长则截断
// truncate on a rune boundary, if needed
textRuneLength := utf8.RuneCountInString(text)
if r.truncating && textRuneLength > truncateItemRunes {
text = string([]rune(text)[:truncateItemRunes]) // 切片语法截断
textRuneLength = truncateItemRunes
}
// 处理text里的字符转义,如果agent="",则agent="-"
// compute the header
header, err := net.NewWarningHeader(299, agent, text)
if err != nil {
return
}
// 如果不超总长限制,则直接追加返回
// if this fits within our limit, or we're already truncating, write and return
if r.written+textRuneLength <= truncateAtTotalRunes || r.truncating {
r.written += textRuneLength
r.writer.Header().Add("Warning", header)
return
}
// 否则,需要重新截断处理
// otherwise, enable truncation, reset, and replay the existing items as truncated warnings
r.truncating = true
r.written = 0
r.writer.Header().Del("Warning")
utilruntime.HandleError(fmt.Errorf("exceeded max warning header size, truncating"))
for _, w := range r.ordered {
agent := w.agent
text := w.text
// 每条警告信息超长则截断
textRuneLength := utf8.RuneCountInString(text)
if textRuneLength > truncateItemRunes {
text = string([]rune(text)[:truncateItemRunes])
textRuneLength = truncateItemRunes
}
if header, err := net.NewWarningHeader(299, agent, text); err == nil {
r.written += textRuneLength
// Add adds the key, value pair to the header.
// It appends to any existing values associated with key.
// The key is case insensitive; it is canonicalized by
// CanonicalHeaderKey.
r.writer.Header().Add("Warning", header)
}
}
}
Client Usage
客户端使用的话,直接调用AddWarning
:
// AddWarning records a warning for the specified agent and text to the Recorder added to the provided context using WithWarningRecorder().
// If no Recorder exists in the provided context, this is a no-op.
// agent must be valid UTF-8, and must not contain spaces, quotes, backslashes, or control characters.
// text must be valid UTF-8, and must not contain control characters.
func AddWarning(ctx context.Context, agent string, text string) {
// 从上下文中拿出来recorder
recorder, ok := warningRecorderFrom(ctx)
if !ok {
return
}
// 刚才分析的recorder.AddWarning
recorder.AddWarning(agent, text)
}
比如:
- k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/dispatcher.go
result, err := webhookrequest.VerifyAdmissionResponse(uid, true, response)
if err != nil {
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
}
for k, v := range result.AuditAnnotations {
key := h.Name + "/" + k
if err := attr.Attributes.AddAnnotation(key, v); err != nil {
klog.Warningf("Failed to set admission audit annotation %s to %s for mutating webhook %s: %v", key, v, h.Name, err)
}
}
for _, w := range result.Warnings {
warning.AddWarning(ctx, "", w)
}