feat: use new properties to override values from headers
This commit is contained in:
parent
a978d4921d
commit
07ef234cac
4 changed files with 69 additions and 136 deletions
|
|
@ -1,80 +0,0 @@
|
||||||
package traefik_umami_feeder
|
|
||||||
|
|
||||||
// Copied source from
|
|
||||||
// https://github.com/kzmake/traefik-plugin-forward-request/blob/master/util.go
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
xForwardedProto = "x-forwarded-proto"
|
|
||||||
xForwardedFor = "x-forwarded-for"
|
|
||||||
xForwardedHost = "x-forwarded-host"
|
|
||||||
xForwardedPort = "x-forwarded-port"
|
|
||||||
xForwardedURI = "x-forwarded-uri"
|
|
||||||
xForwardedMethod = "x-forwarded-method"
|
|
||||||
)
|
|
||||||
|
|
||||||
func copyHeaders(dst, src http.Header, headersToCopy []string) {
|
|
||||||
for _, key := range headersToCopy {
|
|
||||||
if values := src.Values(key); len(values) > 0 {
|
|
||||||
dst[key] = values
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func writeXForwardedHeaders(dst http.Header, req *http.Request) {
|
|
||||||
if clientIP, _, err := net.SplitHostPort(req.RemoteAddr); err == nil {
|
|
||||||
if values := req.Header.Values(xForwardedFor); len(values) > 0 {
|
|
||||||
clientIP = strings.Join(values, ", ") + ", " + clientIP
|
|
||||||
}
|
|
||||||
dst.Set(xForwardedFor, clientIP)
|
|
||||||
}
|
|
||||||
|
|
||||||
xfm := req.Header.Get(xForwardedMethod)
|
|
||||||
switch {
|
|
||||||
case xfm != "":
|
|
||||||
dst.Set(xForwardedMethod, xfm)
|
|
||||||
case req.Method != "":
|
|
||||||
dst.Set(xForwardedMethod, req.Method)
|
|
||||||
default:
|
|
||||||
dst.Del(xForwardedMethod)
|
|
||||||
}
|
|
||||||
|
|
||||||
xfp := req.Header.Get(xForwardedProto)
|
|
||||||
switch {
|
|
||||||
case xfp != "":
|
|
||||||
dst.Set(xForwardedProto, xfp)
|
|
||||||
case req.TLS != nil:
|
|
||||||
dst.Set(xForwardedProto, "https")
|
|
||||||
default:
|
|
||||||
dst.Set(xForwardedProto, "http")
|
|
||||||
}
|
|
||||||
|
|
||||||
if xfp := req.Header.Get(xForwardedPort); xfp != "" {
|
|
||||||
dst.Set(xForwardedPort, xfp)
|
|
||||||
}
|
|
||||||
|
|
||||||
xfh := req.Header.Get(xForwardedHost)
|
|
||||||
switch {
|
|
||||||
case xfh != "":
|
|
||||||
dst.Set(xForwardedHost, xfh)
|
|
||||||
case req.Host != "":
|
|
||||||
dst.Set(xForwardedHost, req.Host)
|
|
||||||
default:
|
|
||||||
dst.Del(xForwardedHost)
|
|
||||||
}
|
|
||||||
|
|
||||||
xfu := req.Header.Get(xForwardedURI)
|
|
||||||
switch {
|
|
||||||
case xfu != "":
|
|
||||||
dst.Set(xForwardedURI, xfu)
|
|
||||||
case req.URL.RequestURI() != "":
|
|
||||||
dst.Set(xForwardedURI, req.URL.RequestURI())
|
|
||||||
default:
|
|
||||||
dst.Del(xForwardedURI)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
4
umami.go
4
umami.go
|
|
@ -91,7 +91,7 @@ type UmamiFeeder struct {
|
||||||
isDebug bool
|
isDebug bool
|
||||||
isDisabled bool
|
isDisabled bool
|
||||||
logHandler *log.Logger
|
logHandler *log.Logger
|
||||||
queue chan *UmamiPayload
|
queue chan *UmamiEvent
|
||||||
|
|
||||||
umamiHost string
|
umamiHost string
|
||||||
umamiToken string
|
umamiToken string
|
||||||
|
|
@ -119,7 +119,7 @@ func New(ctx context.Context, next http.Handler, config *Config, name string) (h
|
||||||
isDisabled: config.Disabled,
|
isDisabled: config.Disabled,
|
||||||
logHandler: log.New(os.Stdout, "", 0),
|
logHandler: log.New(os.Stdout, "", 0),
|
||||||
// Umami API does not support batching https://github.com/umami-software/umami/discussions/1473
|
// Umami API does not support batching https://github.com/umami-software/umami/discussions/1473
|
||||||
queue: make(chan *UmamiPayload, config.QueueSize),
|
queue: make(chan *UmamiEvent, config.QueueSize),
|
||||||
|
|
||||||
umamiHost: config.UmamiHost,
|
umamiHost: config.UmamiHost,
|
||||||
umamiToken: config.UmamiToken,
|
umamiToken: config.UmamiToken,
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -83,7 +84,6 @@ func sendRequestAndParse(ctx context.Context, url string, body interface{}, head
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// opts the port from the host.
|
|
||||||
func parseDomainFromHost(host string) string {
|
func parseDomainFromHost(host string) string {
|
||||||
// check if the host has a port
|
// check if the host has a port
|
||||||
if strings.Contains(host, ":") {
|
if strings.Contains(host, ":") {
|
||||||
|
|
@ -103,3 +103,36 @@ func parseAcceptLanguage(acceptLanguage string) string {
|
||||||
}
|
}
|
||||||
return matches[0][1]
|
return matches[0][1]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func extractRemoteIP(req *http.Request) string {
|
||||||
|
if ip := req.Header.Get("CF-Connecting-IP"); ip != "" {
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|
||||||
|
if ip := req.Header.Get("x-vercel-ip"); ip != "" {
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
|
||||||
|
// Standard proxy headers
|
||||||
|
if xff := req.Header.Get("X-Forwarded-For"); xff != "" {
|
||||||
|
ips := strings.Split(xff, ",")
|
||||||
|
if len(ips) > 0 {
|
||||||
|
return strings.TrimSpace(ips[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if xrip := req.Header.Get("X-Real-IP"); xrip != "" {
|
||||||
|
return xrip
|
||||||
|
}
|
||||||
|
|
||||||
|
// Direct connection
|
||||||
|
if req.RemoteAddr != "" {
|
||||||
|
ip, _, err := net.SplitHostPort(req.RemoteAddr)
|
||||||
|
if err == nil {
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
return req.RemoteAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,67 +4,42 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SendPayload struct {
|
type UmamiEvent struct {
|
||||||
Website string `json:"website"`
|
Website string `json:"website"` // Website ID
|
||||||
Hostname string `json:"hostname"`
|
Hostname string `json:"hostname"` // Name of host
|
||||||
Language string `json:"language,omitempty"`
|
Language string `json:"language,omitempty"` // Language of visitor (ex. "en-US")
|
||||||
Referrer string `json:"referrer,omitempty"`
|
Referrer string `json:"referrer,omitempty"` // Referrer URL
|
||||||
Url string `json:"url"`
|
Url string `json:"url"` // Page URL
|
||||||
//Ip string `json:"ip,omitempty"`
|
Ip string `json:"ip,omitempty"` // IP address
|
||||||
|
UserAgent string `json:"userAgent,omitempty"` // User agent
|
||||||
|
Timestamp int64 `json:"timestamp,omitempty"` // UNIX timestamp in seconds
|
||||||
//Data map[string]interface{} `json:"data,omitempty"` // Additional data for the event
|
//Data map[string]interface{} `json:"data,omitempty"` // Additional data for the event
|
||||||
//Name string `json:"name,omitempty"` // Event name (for custom events)
|
//Name string `json:"name,omitempty"` // Event name (for custom events)
|
||||||
//Screen string `json:"screen,omitempty"` // Screen resolution (ex. "1920x1080")
|
//Screen string `json:"screen,omitempty"` // Screen resolution (ex. "1920x1080")
|
||||||
//Tag string `json:"tag,omitempty"`
|
|
||||||
//Title string `json:"title,omitempty"` // Page title
|
//Title string `json:"title,omitempty"` // Page title
|
||||||
}
|
}
|
||||||
|
|
||||||
type SendBody struct {
|
type SendBody struct {
|
||||||
Payload SendPayload `json:"payload"`
|
Payload *UmamiEvent `json:"payload"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UmamiPayload struct {
|
|
||||||
body SendBody
|
|
||||||
headers http.Header
|
|
||||||
}
|
|
||||||
|
|
||||||
var headersToCopy = []string{
|
|
||||||
"User-Agent",
|
|
||||||
"X-Real-Op",
|
|
||||||
"X-Forwarded-For",
|
|
||||||
"cf-ipcountry",
|
|
||||||
"cf-region-code",
|
|
||||||
"cf-ipcity",
|
|
||||||
"cf-connecting-ip",
|
|
||||||
"x-vercel-ip-country",
|
|
||||||
"x-vercel-ip-country-region",
|
|
||||||
"x-vercel-ip-city",
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copied and adapted from https://github.com/safing/plausiblefeeder/blob/master/event.go
|
|
||||||
// Licensed as MIT license
|
|
||||||
|
|
||||||
func (h *UmamiFeeder) submitToFeed(req *http.Request, code int) {
|
func (h *UmamiFeeder) submitToFeed(req *http.Request, code int) {
|
||||||
body := SendBody{
|
event := &UmamiEvent{
|
||||||
Payload: SendPayload{
|
|
||||||
Hostname: parseDomainFromHost(req.Host),
|
Hostname: parseDomainFromHost(req.Host),
|
||||||
Language: parseAcceptLanguage(req.Header.Get("Accept-Language")),
|
Language: parseAcceptLanguage(req.Header.Get("Accept-Language")),
|
||||||
Referrer: req.Referer(),
|
Referrer: req.Referer(),
|
||||||
Url: req.URL.String(),
|
Url: req.URL.String(),
|
||||||
},
|
Ip: extractRemoteIP(req),
|
||||||
Type: "event",
|
UserAgent: req.Header.Get("User-Agent"),
|
||||||
|
Timestamp: time.Now().Unix(),
|
||||||
}
|
}
|
||||||
|
|
||||||
var headers = make(http.Header)
|
|
||||||
copyHeaders(headers, req.Header, headersToCopy)
|
|
||||||
writeXForwardedHeaders(headers, req)
|
|
||||||
|
|
||||||
payload := &UmamiPayload{body: body, headers: headers}
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case h.queue <- payload:
|
case h.queue <- event:
|
||||||
default:
|
default:
|
||||||
h.error("failed to submit event: queue full")
|
h.error("failed to submit event: queue full")
|
||||||
}
|
}
|
||||||
|
|
@ -103,8 +78,8 @@ func (h *UmamiFeeder) umamiEventFeeder(ctx context.Context) (err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *UmamiFeeder) reportEventToUmami(ctx context.Context, event *UmamiPayload) {
|
func (h *UmamiFeeder) reportEventToUmami(ctx context.Context, event *UmamiEvent) {
|
||||||
hostname := event.body.Payload.Hostname
|
hostname := event.Hostname
|
||||||
websiteId, ok := h.websites[hostname]
|
websiteId, ok := h.websites[hostname]
|
||||||
if !ok {
|
if !ok {
|
||||||
website, err := createWebsite(ctx, h.umamiHost, h.umamiToken, h.umamiTeamId, hostname)
|
website, err := createWebsite(ctx, h.umamiHost, h.umamiToken, h.umamiTeamId, hostname)
|
||||||
|
|
@ -121,10 +96,15 @@ func (h *UmamiFeeder) reportEventToUmami(ctx context.Context, event *UmamiPayloa
|
||||||
h.error("skip tracking, websiteId is unknown: " + hostname)
|
h.error("skip tracking, websiteId is unknown: " + hostname)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
event.body.Payload.Website = websiteId
|
event.Website = websiteId
|
||||||
|
|
||||||
h.debug("sending tracking request %v %v", event.body, event.headers)
|
body := SendBody{
|
||||||
resp, err := sendRequest(ctx, h.umamiHost+"/api/send", event.body, event.headers)
|
Payload: event,
|
||||||
|
Type: "event",
|
||||||
|
}
|
||||||
|
|
||||||
|
h.debug("sending tracking request %v", event)
|
||||||
|
resp, err := sendRequest(ctx, h.umamiHost+"/api/send", body, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.error("failed to send tracking: " + err.Error())
|
h.error("failed to send tracking: " + err.Error())
|
||||||
return
|
return
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue