major refactor
All checks were successful
Build and Publish / Metadata (push) Successful in 4s
Build and Publish / BuildAndDeployAmd64 (push) Successful in 49s
Build and Publish / BuildAndDeployArm64 (push) Successful in 3m49s

This commit is contained in:
2025-10-11 10:22:47 +02:00
parent e48a2590bd
commit 24cd0b6ad7
7 changed files with 278 additions and 573 deletions

View File

@@ -4,7 +4,6 @@ import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"strconv"
@@ -274,7 +273,7 @@ or panic-on-error helper:
id := MustNewCartId()
*/
func CookieCartIdHandler(fn func(w http.ResponseWriter, r *http.Request, cartId CartId) error) func(w http.ResponseWriter, r *http.Request) error {
func CookieCartIdHandler(fn func(cartId CartId, w http.ResponseWriter, r *http.Request) error) func(w http.ResponseWriter, r *http.Request) error {
return func(w http.ResponseWriter, r *http.Request) error {
var id CartId
cookie, err := r.Cookie("cartid")
@@ -308,12 +307,12 @@ func CookieCartIdHandler(fn func(w http.ResponseWriter, r *http.Request, cartId
id = parsed
}
}
if ownershipProxyAfterExtraction != nil {
if handled, err := ownershipProxyAfterExtraction(id, w, r); handled || err != nil {
return err
}
}
return fn(w, r, id)
// if ownershipProxyAfterExtraction != nil {
// if handled, err := ownershipProxyAfterExtraction(id, w, r); handled || err != nil {
// return err
// }
// }
return fn(id, w, r)
}
}
@@ -334,103 +333,113 @@ func (s *PoolServer) RemoveCartCookie(w http.ResponseWriter, r *http.Request, ca
return nil
}
func CartIdHandler(fn func(w http.ResponseWriter, r *http.Request, cartId CartId) error) func(w http.ResponseWriter, r *http.Request) error {
func CartIdHandler(fn func(cartId CartId, w http.ResponseWriter, r *http.Request) error) func(w http.ResponseWriter, r *http.Request) error {
return func(w http.ResponseWriter, r *http.Request) error {
raw := r.PathValue("id")
// If no id supplied, generate a new one
if raw == "" {
id := MustNewCartId()
w.Header().Set("Set-Cart-Id", id.String())
if ownershipProxyAfterExtraction != nil {
if handled, err := ownershipProxyAfterExtraction(id, w, r); handled || err != nil {
return err
}
}
return fn(w, r, id)
return fn(id, w, r)
}
// Parse base62 cart id
id, ok := ParseCartId(raw)
if !ok {
return fmt.Errorf("invalid cart id format")
}
if ownershipProxyAfterExtraction != nil {
if handled, err := ownershipProxyAfterExtraction(id, w, r); handled || err != nil {
return err
}
}
return fn(w, r, id)
return fn(id, w, r)
}
}
var ownershipProxyAfterExtraction func(cartId CartId, w http.ResponseWriter, r *http.Request) (handled bool, err error)
func (s *PoolServer) ProxyHandler(fn func(w http.ResponseWriter, r *http.Request, cartId CartId) error) func(cartId CartId, w http.ResponseWriter, r *http.Request) error {
return func(cartId CartId, w http.ResponseWriter, r *http.Request) error {
if ownerHost, ok := s.pool.OwnerHost(cartId); ok {
ok, err := ownerHost.Proxy(cartId, w, r)
if ok || err != nil {
log.Printf("proxy failed: %v", err)
// todo take ownership!!
} else {
return nil
}
}
// Local ownership or no owner known, proceed with local handling
return fn(w, r, cartId)
}
}
//var ownershipProxyAfterExtraction func(cartId CartId, w http.ResponseWriter, r *http.Request) (handled bool, err error)
func (s *PoolServer) Serve() *http.ServeMux {
// Install ownership proxy hook that runs AFTER id extraction (cookie OR path)
ownershipProxyAfterExtraction = func(cartId CartId, w http.ResponseWriter, r *http.Request) (bool, error) {
if cartId.String() == "" {
return false, nil
}
owner := s.pool.OwnerHost(cartId)
if owner == "" || owner == s.pool.Hostname() {
// Set / refresh cartowner cookie pointing to the local host (claim or already owned).
localHost := owner
if localHost == "" {
localHost = s.pool.Hostname()
}
http.SetCookie(w, &http.Cookie{
Name: "cartowner",
Value: localHost,
Path: "/",
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
})
return false, nil
}
// For remote ownership set cartowner cookie to remote host for sticky sessions.
http.SetCookie(w, &http.Cookie{
Name: "cartowner",
Value: owner,
Path: "/",
HttpOnly: true,
SameSite: http.SameSiteLaxMode,
})
// Proxy logic (simplified): reuse existing request to owning host on same port.
target := "http://" + owner + r.URL.Path
if q := r.URL.RawQuery; q != "" {
target += "?" + q
}
req, err := http.NewRequestWithContext(r.Context(), r.Method, target, r.Body)
if err != nil {
http.Error(w, "proxy build error", http.StatusBadGateway)
return true, err
}
for k, v := range r.Header {
for _, vv := range v {
req.Header.Add(k, vv)
}
}
req.Header.Set("X-Forwarded-Host", r.Host)
req.Header.Set("X-Cart-Id", cartId.String())
req.Header.Set("X-Cart-Owner", owner)
resp, err := http.DefaultClient.Do(req)
if err != nil {
http.Error(w, "proxy upstream error", http.StatusBadGateway)
return true, err
}
defer resp.Body.Close()
for k, v := range resp.Header {
for _, vv := range v {
w.Header().Add(k, vv)
}
}
w.Header().Set("X-Cart-Owner-Routed", "true")
w.WriteHeader(resp.StatusCode)
_, copyErr := io.Copy(w, resp.Body)
if copyErr != nil {
return true, copyErr
}
return true, nil
}
// // Install ownership proxy hook that runs AFTER id extraction (cookie OR path)
// ownershipProxyAfterExtraction = func(cartId CartId, w http.ResponseWriter, r *http.Request) (bool, error) {
// if cartId.String() == "" {
// return false, nil
// }
// owner := s.pool.OwnerHost(cartId)
// if owner == "" || owner == s.pool.Hostname() {
// // Set / refresh cartowner cookie pointing to the local host (claim or already owned).
// localHost := owner
// if localHost == "" {
// localHost = s.pool.Hostname()
// }
// http.SetCookie(w, &http.Cookie{
// Name: "cartowner",
// Value: localHost,
// Path: "/",
// HttpOnly: true,
// SameSite: http.SameSiteLaxMode,
// })
// return false, nil
// }
// // For remote ownership set cartowner cookie to remote host for sticky sessions.
// http.SetCookie(w, &http.Cookie{
// Name: "cartowner",
// Value: owner,
// Path: "/",
// HttpOnly: true,
// SameSite: http.SameSiteLaxMode,
// })
// // Proxy logic (simplified): reuse existing request to owning host on same port.
// target := "http://" + owner + r.URL.Path
// if q := r.URL.RawQuery; q != "" {
// target += "?" + q
// }
// req, err := http.NewRequestWithContext(r.Context(), r.Method, target, r.Body)
// if err != nil {
// http.Error(w, "proxy build error", http.StatusBadGateway)
// return true, err
// }
// for k, v := range r.Header {
// for _, vv := range v {
// req.Header.Add(k, vv)
// }
// }
// req.Header.Set("X-Forwarded-Host", r.Host)
// req.Header.Set("X-Cart-Id", cartId.String())
// req.Header.Set("X-Cart-Owner", owner)
// resp, err := http.DefaultClient.Do(req)
// if err != nil {
// http.Error(w, "proxy upstream error", http.StatusBadGateway)
// return true, err
// }
// defer resp.Body.Close()
// for k, v := range resp.Header {
// for _, vv := range v {
// w.Header().Add(k, vv)
// }
// }
// w.Header().Set("X-Cart-Owner-Routed", "true")
// w.WriteHeader(resp.StatusCode)
// _, copyErr := io.Copy(w, resp.Body)
// if copyErr != nil {
// return true, copyErr
// }
// return true, nil
// }
mux := http.NewServeMux()
mux.HandleFunc("OPTIONS /", func(w http.ResponseWriter, r *http.Request) {
@@ -440,29 +449,29 @@ func (s *PoolServer) Serve() *http.ServeMux {
w.WriteHeader(http.StatusOK)
})
mux.HandleFunc("GET /", ErrorHandler(CookieCartIdHandler(s.HandleGet)))
mux.HandleFunc("GET /add/{sku}", ErrorHandler(CookieCartIdHandler(s.HandleAddSku)))
mux.HandleFunc("POST /", ErrorHandler(CookieCartIdHandler(s.HandleAddRequest)))
mux.HandleFunc("POST /set", ErrorHandler(CookieCartIdHandler(s.HandleSetCartItems)))
mux.HandleFunc("DELETE /{itemId}", ErrorHandler(CookieCartIdHandler(s.HandleDeleteItem)))
mux.HandleFunc("PUT /", ErrorHandler(CookieCartIdHandler(s.HandleQuantityChange)))
mux.HandleFunc("DELETE /", ErrorHandler(CookieCartIdHandler(s.RemoveCartCookie)))
mux.HandleFunc("POST /delivery", ErrorHandler(CookieCartIdHandler(s.HandleSetDelivery)))
mux.HandleFunc("DELETE /delivery/{deliveryId}", ErrorHandler(CookieCartIdHandler(s.HandleRemoveDelivery)))
mux.HandleFunc("PUT /delivery/{deliveryId}/pickupPoint", ErrorHandler(CookieCartIdHandler(s.HandleSetPickupPoint)))
mux.HandleFunc("GET /checkout", ErrorHandler(CookieCartIdHandler(s.HandleCheckout)))
mux.HandleFunc("GET /confirmation/{orderId}", ErrorHandler(CookieCartIdHandler(s.HandleConfirmation)))
mux.HandleFunc("GET /", ErrorHandler(CookieCartIdHandler(s.ProxyHandler(s.HandleGet))))
mux.HandleFunc("GET /add/{sku}", ErrorHandler(CookieCartIdHandler(s.ProxyHandler(s.HandleAddSku))))
mux.HandleFunc("POST /", ErrorHandler(CookieCartIdHandler(s.ProxyHandler(s.HandleAddRequest))))
mux.HandleFunc("POST /set", ErrorHandler(CookieCartIdHandler(s.ProxyHandler(s.HandleSetCartItems))))
mux.HandleFunc("DELETE /{itemId}", ErrorHandler(CookieCartIdHandler(s.ProxyHandler(s.HandleDeleteItem))))
mux.HandleFunc("PUT /", ErrorHandler(CookieCartIdHandler(s.ProxyHandler(s.HandleQuantityChange))))
mux.HandleFunc("DELETE /", ErrorHandler(CookieCartIdHandler(s.ProxyHandler(s.RemoveCartCookie))))
mux.HandleFunc("POST /delivery", ErrorHandler(CookieCartIdHandler(s.ProxyHandler(s.HandleSetDelivery))))
mux.HandleFunc("DELETE /delivery/{deliveryId}", ErrorHandler(CookieCartIdHandler(s.ProxyHandler(s.HandleRemoveDelivery))))
mux.HandleFunc("PUT /delivery/{deliveryId}/pickupPoint", ErrorHandler(CookieCartIdHandler(s.ProxyHandler(s.HandleSetPickupPoint))))
mux.HandleFunc("GET /checkout", ErrorHandler(CookieCartIdHandler(s.ProxyHandler(s.HandleCheckout))))
mux.HandleFunc("GET /confirmation/{orderId}", ErrorHandler(CookieCartIdHandler(s.ProxyHandler(s.HandleConfirmation))))
mux.HandleFunc("GET /byid/{id}", ErrorHandler(CartIdHandler(s.HandleGet)))
mux.HandleFunc("GET /byid/{id}/add/{sku}", ErrorHandler(CartIdHandler(s.HandleAddSku)))
mux.HandleFunc("POST /byid/{id}", ErrorHandler(CartIdHandler(s.HandleAddRequest)))
mux.HandleFunc("DELETE /byid/{id}/{itemId}", ErrorHandler(CartIdHandler(s.HandleDeleteItem)))
mux.HandleFunc("PUT /byid/{id}", ErrorHandler(CartIdHandler(s.HandleQuantityChange)))
mux.HandleFunc("POST /byid/{id}/delivery", ErrorHandler(CartIdHandler(s.HandleSetDelivery)))
mux.HandleFunc("DELETE /byid/{id}/delivery/{deliveryId}", ErrorHandler(CartIdHandler(s.HandleRemoveDelivery)))
mux.HandleFunc("PUT /byid/{id}/delivery/{deliveryId}/pickupPoint", ErrorHandler(CartIdHandler(s.HandleSetPickupPoint)))
mux.HandleFunc("GET /byid/{id}/checkout", ErrorHandler(CartIdHandler(s.HandleCheckout)))
mux.HandleFunc("GET /byid/{id}/confirmation", ErrorHandler(CartIdHandler(s.HandleConfirmation)))
mux.HandleFunc("GET /byid/{id}", ErrorHandler(CartIdHandler(s.ProxyHandler(s.HandleGet))))
mux.HandleFunc("GET /byid/{id}/add/{sku}", ErrorHandler(CartIdHandler(s.ProxyHandler(s.HandleAddSku))))
mux.HandleFunc("POST /byid/{id}", ErrorHandler(CartIdHandler(s.ProxyHandler(s.HandleAddRequest))))
mux.HandleFunc("DELETE /byid/{id}/{itemId}", ErrorHandler(CartIdHandler(s.ProxyHandler(s.HandleDeleteItem))))
mux.HandleFunc("PUT /byid/{id}", ErrorHandler(CartIdHandler(s.ProxyHandler(s.HandleQuantityChange))))
mux.HandleFunc("POST /byid/{id}/delivery", ErrorHandler(CartIdHandler(s.ProxyHandler(s.HandleSetDelivery))))
mux.HandleFunc("DELETE /byid/{id}/delivery/{deliveryId}", ErrorHandler(CartIdHandler(s.ProxyHandler(s.HandleRemoveDelivery))))
mux.HandleFunc("PUT /byid/{id}/delivery/{deliveryId}/pickupPoint", ErrorHandler(CartIdHandler(s.ProxyHandler(s.HandleSetPickupPoint))))
mux.HandleFunc("GET /byid/{id}/checkout", ErrorHandler(CartIdHandler(s.ProxyHandler(s.HandleCheckout))))
mux.HandleFunc("GET /byid/{id}/confirmation", ErrorHandler(CartIdHandler(s.ProxyHandler(s.HandleConfirmation))))
return mux
}