You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

HAProxy SPOE处理HTTP/2消息时,Agent接收的method与path字段为nil的问题排查请求

HAProxy SPOE处理HTTP/2消息时,Agent接收的method与path字段为nil的问题排查请求

大家好,我最近在配置HAProxy SPOE来分析HTTP/2请求和响应的Protobuf消息时遇到了棘手的问题:我的OpenTRB服务器基于HTTP/2接收Protobuf消息,HAProxy作为反向代理配置了SPOE,目的是把请求/响应数据发送给Golang Agent做分析,但Agent接收到的on_request消息里,method和path字段总是返回nil,只有body能正常获取到。以下是我的相关配置和代码,希望能帮大家定位问题:

HAProxy版本

HAProxy version 3.1.3-929bedf

haproxy.cfg配置

########################################
# global and defaults
########################################
global
    log /dev/log local0 err
    log /dev/log local1 err
    daemon
    maxconn 100000
    tune.h2.initial-window-size 1048576

backend bid_agent
    mode tcp
    balance roundrobin
    timeout connect 5s # greater than hello timeout
    timeout server 3m # greater than idle timeout
    option spop-check
    server agent1 127.0.0.1:3001 check

defaults
    log global
    option httplog
    option dontlognull
    timeout connect 5000ms
    timeout client 50000ms
    timeout server 50000ms
    option http-buffer-request

frontend prometheus
    bind *:8405
    mode http
    http-request use-service prometheus-exporter if { path /metrics }
    no log

frontend http2_front
    bind *:3000 alpn h2,http/1.1
    mode http
    filter spoe engine spoe config /etc/haproxy/analytics-filter.conf
    default_backend http2_back

backend http2_back
    mode http
    balance leastconn
    server 10.100.163.247 10.100.163.247:3000 proto h2 check

listen stats
    mode http
    bind *:8404
    stats enable
    stats uri /stats
    stats realm Haproxy\ Statistics
    stats auth admin:admin123

SPOE配置文件(/etc/haproxy/analytics-filter.conf)

[spoe]
spoe-agent bid_agent
    messages on_request on_response
    option set-on-error err
    option dontlog-normal
    timeout hello 3s
    timeout idle 2m
    timeout processing 10s
    use-backend bid_agent
    log global

spoe-message on_request
    event on-frontend-http-request
    mandatory
    arg method str { req.method }
    arg path str { path }
    arg body bin { req.body }

spoe-message on_response
    event on-http-response
    mandatory
    arg status int { res.status }
    arg body bin { res.body }

Golang Agent代码

package main

import (
    "log"
    "net"

    "github.com/negasus/haproxy-spoe-go/agent"
    "github.com/negasus/haproxy-spoe-go/logger"
    "github.com/negasus/haproxy-spoe-go/request"
)

func main() {
    log.Print("SPOE agent listening on 127.0.0.1:3001")
    listener, err := net.Listen("tcp4", "127.0.0.1:3001")
    if err != nil {
        log.Fatalf("unable to listen: %v", err)
    }
    defer listener.Close()

    // Create and serve the SPOE agent with our handler
    a := agent.New(handler, logger.NewDefaultLog())
    if err := a.Serve(listener); err != nil {
        log.Fatalf("agent serve error: %v", err)
    }
}

func handler(req *request.Request) {
    msgCount := req.Messages.Len()
    log.Printf("Handling request EngineID=%s, StreamID=%d, FrameID=%d with %d messages", req.EngineID, req.StreamID, req.FrameID, msgCount)

    for i := 0; i < msgCount; i++ {
        msg, err := req.Messages.GetByIndex(i)
        if err != nil {
            log.Printf("error retrieving message at index %d: %v", i, err)
            continue
        }

        switch msg.Name {
        case "on_request":
            methodVal, _ := msg.KV.Get("method")
            pathVal, _ := msg.KV.Get("path")
            bodyVal, _ := msg.KV.Get("body")

            // Handle binary (protobuf) data properly
            if bodyVal != nil {
                if body, ok := bodyVal.([]byte); ok {
                    // Here you would unmarshal the protobuf
                    log.Printf("Received protobuf body of length: %d", len(body))
                    // Add your protobuf unmarshaling logic here
                }
            }
            log.Printf("[REQUEST] method=%v path=%v", methodVal, pathVal)
        case "on_response":
            statusVal, _ := msg.KV.Get("status")
            timeVal, _ := msg.KV.Get("time")
            bodyVal, _ := msg.KV.Get("body")

            if bodyVal != nil {
                if body, ok := bodyVal.([]byte); ok {
                    log.Printf("Received response protobuf body of length: %d", len(body))
                }
            }
            log.Printf("[RESPONSE] status=%v duration=%vµs", statusVal, timeVal)
        default:
            log.Printf("unknown message: %s", msg.Name)
        }
    }
}

我自己的排查方向(供参考)

  • 首先怀疑SPOE配置里的变量引用错误:比如path字段我写的是{ path },但HAProxy的HTTP请求路径变量应该是{ req.path }?这会不会是导致path为nil的原因?不过method用了{ req.method }还是返回nil,这点我有点困惑。
  • 确认HTTP/2前端的配置:http2_front已经设置了alpn h2,http/1.1,并且后端也指定了proto h2,理论上应该能正确解析HTTP/2请求的?
  • 检查SPOE事件的触发时机:on-frontend-http-request是不是在请求还没完全解析完成时就触发了,导致method和path还没被提取?

有没有朋友遇到过类似的问题,或者能帮我指出配置里的错误?谢谢大家!

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.08 11:44:33