Terms from Haproxy SPOE specification
* SPOE : Stream Processing Offload Engine.
A SPOE is a filter talking to servers managed ba a SPOA to offload the
stream processing. An engine is attached to a proxy. A proxy can have
several engines. Each engine is linked to an agent and only one.
* SPOA : Stream Processing Offload Agent.
A SPOA is a service that will receive info from a SPOE to offload the
stream processing. An agent manages several servers. It uses a backend to
reference all of them. By extension, these servers can also be called
agents.
* SPOP : Stream Processing Offload Protocol, used by SPOEs to talk to SPOA
servers.
This protocol is used by engines to talk to agents. It is an in-house
binary protocol described in this documentation.
This library implements SPOA for Golang applications
go get -u github.com/negasus/haproxy-spoe-go
Example from Section 2.5 SPOE specification describes a simple IP reputation service.
Here is a simple but complete example that sends client-ip address to a ip reputation service. This service can set the variable "ip_score" which is an integer between 0 and 100, indicating its reputation (100 means totally safe and 0 a blacklisted IP with no doubt).
Golang backend application for this example
package main
import (
"github.com/negasus/haproxy-spoe-go/action"
"github.com/negasus/haproxy-spoe-go/agent"
"github.com/negasus/haproxy-spoe-go/request"
"github.com/negasus/haproxy-spoe-go/logger"
"log"
"math/rand"
"net"
"os"
)
func main() {
log.Print("listen 3000")
listener, err := net.Listen("tcp4", "127.0.0.1:3000")
if err != nil {
log.Printf("error create listener, %v", err)
os.Exit(1)
}
defer listener.Close()
a := agent.New(handler, logger.NewDefaultLog())
if err := a.Serve(listener); err != nil {
log.Printf("error agent serve: %+v\n", err)
}
}
func handler(req *request.Request) {
log.Printf("handle request EngineID: '%s', StreamID: '%d', FrameID: '%d' with %d messages\n", req.EngineID, req.StreamID, req.FrameID, req.Messages.Len())
messageName := "get-ip-reputation"
mes, err := req.Messages.GetByName(messageName)
if err != nil {
log.Printf("message %s not found: %v", messageName, err)
return
}
ipValue, ok := mes.KV.Get("ip")
if !ok {
log.Printf("var 'ip' not found in message")
return
}
ip, ok := ipValue.(net.IP)
if !ok {
log.Printf("var 'ip' has wrong type. expect IP addr")
return
}
ipScore := rand.Intn(100)
log.Printf("IP: %s, send score '%d'", ip.String(), ipScore)
req.Actions.SetVar(action.ScopeSession, "ip_score", ipScore)
}
Getting request data is possible through Request.Messages
Returns count of messages in request
count := request.Messages.Len()
Returns message by name and error if not exists
mes, err := request.Messages.GetByName("get-ip-reputation")
Returns message by index and error if not exists
mes, err := request.Messages.GetByIndex(0)
Represents one message, sent by Haproxy
Message has two fields
KV contains key-value data of message
Actions is used for sending a response to Haproxy
Set variable with name
to value
in specific scope
(see bellow)
request.Actions.SetVar(action.ScopeSession, "ip_score", 10)
Unset variable with name
in specific scope
request.Actions.UnsetVar(action.ScopeSession, "ip_score")
Contains message key-value data sent by Haproxy
Returns value by name. If key doesn't exist, last returned value will be set to False
ipValue, ok := message.KV.Get("ip")