sesamy-go/pkg/collect/collect.go
2025-06-06 11:21:08 +02:00

197 lines
4.7 KiB
Go

package collect
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"github.com/foomo/sesamy-go/pkg/encoding/gtag"
"github.com/foomo/sesamy-go/pkg/encoding/mpv2"
gtaghttp "github.com/foomo/sesamy-go/pkg/http/gtag"
mpv2http "github.com/foomo/sesamy-go/pkg/http/mpv2"
"github.com/pkg/errors"
"go.uber.org/zap"
)
type (
Collect struct {
l *zap.Logger
taggingURL string
taggingClient *http.Client
gtagHTTPMiddlewares []gtaghttp.Middleware
mpv2HTTPMiddlewares []mpv2http.Middleware
}
Option func(*Collect) error
)
// ------------------------------------------------------------------------------------------------
// ~ Options
// ------------------------------------------------------------------------------------------------
func WithTagging(v string) Option {
return func(c *Collect) error {
c.taggingURL = v
return nil
}
}
func WithTaggingClient(v *http.Client) Option {
return func(c *Collect) error {
c.taggingClient = v
return nil
}
}
func WithGTagHTTPMiddlewares(v ...gtaghttp.Middleware) Option {
return func(c *Collect) error {
c.gtagHTTPMiddlewares = append(c.gtagHTTPMiddlewares, v...)
return nil
}
}
func WithMPv2HTTPMiddlewares(v ...mpv2http.Middleware) Option {
return func(c *Collect) error {
c.mpv2HTTPMiddlewares = append(c.mpv2HTTPMiddlewares, v...)
return nil
}
}
// ------------------------------------------------------------------------------------------------
// ~ Constructor
// ------------------------------------------------------------------------------------------------
func New(l *zap.Logger, opts ...Option) (*Collect, error) {
inst := &Collect{
l: l,
taggingClient: http.DefaultClient,
}
for _, opt := range opts {
if opt != nil {
if err := opt(inst); err != nil {
return nil, err
}
}
}
return inst, nil
}
// ------------------------------------------------------------------------------------------------
// ~ Public methods
// ------------------------------------------------------------------------------------------------
func (c *Collect) GTagHTTPHandler(w http.ResponseWriter, r *http.Request) {
// retrieve payload
payload := gtaghttp.Handler(w, r)
if payload == nil {
http.Error(w, "failed to decode payload", http.StatusBadRequest)
return
}
// compose middlewares
next := c.gtagHandler
for _, middleware := range c.gtagHTTPMiddlewares {
next = middleware(next)
}
// run handler
if err := next(c.l, w, r, payload); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func (c *Collect) MPv2HTTPHandler(w http.ResponseWriter, r *http.Request) {
// retrieve payload
payload := mpv2http.Handler(w, r)
// compose middlewares
next := c.mpv2Handler
for _, middleware := range c.mpv2HTTPMiddlewares {
next = middleware(next)
}
// run handler
if err := next(c.l, w, r, payload); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
// ------------------------------------------------------------------------------------------------
// ~ Private methods
// ------------------------------------------------------------------------------------------------
func (c *Collect) gtagHandler(l *zap.Logger, w http.ResponseWriter, r *http.Request, payload *gtag.Payload) error {
values, body, err := gtag.Encode(payload)
if err != nil {
return err
}
req, err := http.NewRequestWithContext(r.Context(), http.MethodPost, fmt.Sprintf("%s%s?%s", c.taggingURL, "/g/collect", gtag.EncodeValues(values)), body)
if err != nil {
return errors.Wrap(err, "failed to create request")
}
// copy headers
req.Header = r.Header.Clone()
resp, err := c.taggingClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
// copy headers
for key, values := range resp.Header {
for _, value := range values {
w.Header().Add(key, value)
}
}
if _, err := io.Copy(w, resp.Body); err != nil {
return err
}
return nil
}
func (c *Collect) mpv2Handler(l *zap.Logger, w http.ResponseWriter, r *http.Request, payload *mpv2.Payload[any]) error {
body, err := json.Marshal(payload)
if err != nil {
return err
}
req, err := http.NewRequestWithContext(r.Context(), http.MethodPost, fmt.Sprintf("%s%s", c.taggingURL, "/mp/collect"), bytes.NewReader(body))
if err != nil {
return errors.Wrap(err, "failed to create request")
}
// copy headers
req.Header = r.Header.Clone()
// copy raw query
req.URL.RawQuery = r.URL.RawQuery
resp, err := c.taggingClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
// copy headers
for key, values := range resp.Header {
for _, value := range values {
w.Header().Add(key, value)
}
}
if _, err := io.Copy(w, resp.Body); err != nil {
return err
}
return nil
}