routing
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
appmiddleware "prestaproxy/internal/http/middleware"
|
||||
pscart "prestaproxy/internal/prestashop/cart"
|
||||
pscatalog "prestaproxy/internal/prestashop/catalog"
|
||||
psconfig "prestaproxy/internal/prestashop/config"
|
||||
pscustomer "prestaproxy/internal/prestashop/customer"
|
||||
psroutes "prestaproxy/internal/prestashop/routes"
|
||||
"prestaproxy/internal/render"
|
||||
"prestaproxy/internal/viewmodel"
|
||||
)
|
||||
|
||||
const categorySlugContextKey = "category_slug"
|
||||
const categoryIDContextKey = "category_id"
|
||||
|
||||
type CategoryHandler struct {
|
||||
catalog *pscatalog.Service
|
||||
customers *pscustomer.Service
|
||||
carts *pscart.Service
|
||||
renderer *render.Engine
|
||||
config psconfig.Config
|
||||
products *psroutes.ProductRoute
|
||||
}
|
||||
|
||||
func NewCategoryHandler(catalog *pscatalog.Service, customers *pscustomer.Service, carts *pscart.Service, renderer *render.Engine, cfg psconfig.Config, products *psroutes.ProductRoute) *CategoryHandler {
|
||||
return &CategoryHandler{
|
||||
catalog: catalog,
|
||||
customers: customers,
|
||||
carts: carts,
|
||||
renderer: renderer,
|
||||
config: cfg,
|
||||
products: products,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *CategoryHandler) Show(c echo.Context) error {
|
||||
session := appmiddleware.GetSession(c)
|
||||
if session == nil {
|
||||
session = appmiddleware.GetSession(c)
|
||||
}
|
||||
if h == nil || h.catalog == nil || h.renderer == nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "category handler is not initialized")
|
||||
}
|
||||
|
||||
languageID := int64Default(session.LanguageID, 1)
|
||||
languageID = h.catalog.ResolveLanguageID(c.Request().Context(), c.Request(), languageID)
|
||||
shopID := int64Default(session.ShopID, 1)
|
||||
|
||||
category, err := h.catalog.GetCategoryPage(c.Request().Context(), pscatalog.CategoryPageRequest{
|
||||
ID: categoryID(c),
|
||||
Slug: categorySlug(c),
|
||||
LanguageID: languageID,
|
||||
ShopID: shopID,
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return echo.NewHTTPError(http.StatusNotFound, "category not found")
|
||||
}
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "category query failed: "+err.Error())
|
||||
}
|
||||
|
||||
var profile *pscustomer.Profile
|
||||
if session.CustomerID != nil && h.customers != nil {
|
||||
profile, err = h.customers.GetByID(c.Request().Context(), *session.CustomerID)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "customer query failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
var cartSummary *pscart.Summary
|
||||
if session.CartID != nil && h.carts != nil {
|
||||
cartSummary, err = h.carts.SummaryByID(c.Request().Context(), *session.CartID)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "cart query failed: "+err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
page := viewmodel.CategoryPageData{
|
||||
Category: *category,
|
||||
Session: session,
|
||||
Customer: profile,
|
||||
CartSummary: cartSummary,
|
||||
ShopBaseURL: h.config.PrestaShopBaseURL,
|
||||
}
|
||||
assignCategoryProductLinks(c.Request(), h.products, &page)
|
||||
|
||||
if err := h.renderer.Category(c.Response(), c.Request(), page); err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "category render failed: "+err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetCategorySlug(c echo.Context, slug string) {
|
||||
c.Set(categorySlugContextKey, slug)
|
||||
}
|
||||
|
||||
func SetCategoryID(c echo.Context, id int64) {
|
||||
c.Set(categoryIDContextKey, id)
|
||||
}
|
||||
|
||||
func categorySlug(c echo.Context) string {
|
||||
if value := c.Get(categorySlugContextKey); value != nil {
|
||||
if slug, ok := value.(string); ok && slug != "" {
|
||||
return slug
|
||||
}
|
||||
}
|
||||
return strings.TrimSpace(c.Param("slug"))
|
||||
}
|
||||
|
||||
func categoryID(c echo.Context) int64 {
|
||||
if value := c.Get(categoryIDContextKey); value != nil {
|
||||
if id, ok := value.(int64); ok {
|
||||
return id
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func assignCategoryProductLinks(req *http.Request, route *psroutes.ProductRoute, page *viewmodel.CategoryPageData) {
|
||||
if page == nil {
|
||||
return
|
||||
}
|
||||
langPrefix := requestLanguagePrefix(req)
|
||||
categoryPath := page.Category.Slug
|
||||
for i := range page.Category.Products {
|
||||
product := &page.Category.Products[i]
|
||||
product.URL = route.BuildPath(psroutes.ProductURLData{
|
||||
ID: product.ID,
|
||||
Slug: product.Slug,
|
||||
CategoryPath: categoryPath,
|
||||
EAN13: product.EAN13,
|
||||
LanguagePrefix: langPrefix,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func requestLanguagePrefix(req *http.Request) string {
|
||||
if req == nil || req.URL == nil {
|
||||
return ""
|
||||
}
|
||||
path := strings.Trim(req.URL.Path, "/")
|
||||
if path == "" {
|
||||
return ""
|
||||
}
|
||||
first := path
|
||||
if idx := strings.IndexByte(path, '/'); idx >= 0 {
|
||||
first = path[:idx]
|
||||
}
|
||||
first = strings.TrimSpace(first)
|
||||
if len(first) < 2 || len(first) > 5 {
|
||||
return ""
|
||||
}
|
||||
return "/" + first
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func Healthz() echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
return c.JSON(http.StatusOK, map[string]string{"status": "ok"})
|
||||
}
|
||||
}
|
||||
|
||||
func Readyz(appDB, prestaDB *gorm.DB, proxyTarget string) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
ctx, cancel := context.WithTimeout(c.Request().Context(), 2*time.Second)
|
||||
defer cancel()
|
||||
|
||||
if err := pingDB(ctx, appDB); err != nil {
|
||||
return echo.NewHTTPError(http.StatusServiceUnavailable, "app db unavailable")
|
||||
}
|
||||
if err := pingDB(ctx, prestaDB); err != nil {
|
||||
return echo.NewHTTPError(http.StatusServiceUnavailable, "prestashop db unavailable")
|
||||
}
|
||||
if proxyTarget == "" {
|
||||
return echo.NewHTTPError(http.StatusServiceUnavailable, "prestashop proxy target unavailable")
|
||||
}
|
||||
|
||||
return c.JSON(http.StatusOK, map[string]string{"status": "ready"})
|
||||
}
|
||||
}
|
||||
|
||||
func pingDB(ctx context.Context, db *gorm.DB) error {
|
||||
sqlDB, err := db.DB()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sqlDB.PingContext(ctx)
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
"gorm.io/gorm"
|
||||
|
||||
appmiddleware "prestaproxy/internal/http/middleware"
|
||||
pscart "prestaproxy/internal/prestashop/cart"
|
||||
pscatalog "prestaproxy/internal/prestashop/catalog"
|
||||
psconfig "prestaproxy/internal/prestashop/config"
|
||||
pscustomer "prestaproxy/internal/prestashop/customer"
|
||||
psroutes "prestaproxy/internal/prestashop/routes"
|
||||
"prestaproxy/internal/render"
|
||||
"prestaproxy/internal/viewmodel"
|
||||
)
|
||||
|
||||
const productSlugContextKey = "product_slug"
|
||||
const productIDContextKey = "product_id"
|
||||
|
||||
type ProductHandler struct {
|
||||
products *pscatalog.Service
|
||||
customers *pscustomer.Service
|
||||
carts *pscart.Service
|
||||
renderer *render.Engine
|
||||
config psconfig.Config
|
||||
categories *psroutes.CategoryRoute
|
||||
}
|
||||
|
||||
func NewProductHandler(products *pscatalog.Service, customers *pscustomer.Service, carts *pscart.Service, renderer *render.Engine, cfg psconfig.Config, categories *psroutes.CategoryRoute) *ProductHandler {
|
||||
return &ProductHandler{
|
||||
products: products,
|
||||
customers: customers,
|
||||
carts: carts,
|
||||
renderer: renderer,
|
||||
config: cfg,
|
||||
categories: categories,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *ProductHandler) Show(c echo.Context) error {
|
||||
session := appmiddleware.GetSession(c)
|
||||
if session == nil {
|
||||
session = appmiddleware.GetSession(c)
|
||||
}
|
||||
if h == nil || h.products == nil || h.renderer == nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "product handler is not initialized")
|
||||
}
|
||||
|
||||
languageID := int64Default(session.LanguageID, 1)
|
||||
languageID = h.products.ResolveLanguageID(c.Request().Context(), c.Request(), languageID)
|
||||
shopID := int64Default(session.ShopID, 1)
|
||||
|
||||
product, err := h.products.GetProductPage(c.Request().Context(), pscatalog.ProductPageRequest{
|
||||
ID: productID(c),
|
||||
Slug: productSlug(c),
|
||||
LanguageID: languageID,
|
||||
ShopID: shopID,
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return echo.NewHTTPError(http.StatusNotFound, "product not found")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
var profile *pscustomer.Profile
|
||||
if session.CustomerID != nil && h.customers != nil {
|
||||
profile, err = h.customers.GetByID(c.Request().Context(), *session.CustomerID)
|
||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var cartSummary *pscart.Summary
|
||||
if session.CartID != nil && h.carts != nil {
|
||||
cartSummary, err = h.carts.SummaryByID(c.Request().Context(), *session.CartID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
page := viewmodel.ProductPageData{
|
||||
Product: *product,
|
||||
CategoryURL: productCategoryURL(c.Request(), h.categories, product),
|
||||
Session: session,
|
||||
Customer: profile,
|
||||
CartSummary: cartSummary,
|
||||
ShopBaseURL: h.config.PrestaShopBaseURL,
|
||||
}
|
||||
|
||||
return h.renderer.Product(c.Response(), c.Request(), page)
|
||||
}
|
||||
|
||||
func int64Default(value *int64, fallback int64) int64 {
|
||||
if value == nil || *value == 0 {
|
||||
return fallback
|
||||
}
|
||||
return *value
|
||||
}
|
||||
|
||||
func SetProductSlug(c echo.Context, slug string) {
|
||||
c.Set(productSlugContextKey, slug)
|
||||
}
|
||||
|
||||
func SetProductID(c echo.Context, id int64) {
|
||||
c.Set(productIDContextKey, id)
|
||||
}
|
||||
|
||||
func productSlug(c echo.Context) string {
|
||||
if value := c.Get(productSlugContextKey); value != nil {
|
||||
if slug, ok := value.(string); ok && slug != "" {
|
||||
return slug
|
||||
}
|
||||
}
|
||||
return strings.TrimSpace(c.Param("slug"))
|
||||
}
|
||||
|
||||
func productID(c echo.Context) int64 {
|
||||
if value := c.Get(productIDContextKey); value != nil {
|
||||
if id, ok := value.(int64); ok {
|
||||
return id
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func productCategoryURL(req *http.Request, route *psroutes.CategoryRoute, product *pscatalog.ProductPageData) string {
|
||||
if route == nil || product == nil || product.CategoryID == 0 {
|
||||
return ""
|
||||
}
|
||||
return route.BuildPath(psroutes.CategoryURLData{
|
||||
ID: product.CategoryID,
|
||||
Slug: product.CategorySlug,
|
||||
LanguagePrefix: requestLanguagePrefix(req),
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"prestaproxy/internal/prestashop/cookie"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
const sessionContextKey = "prestashop_session"
|
||||
|
||||
func SetSession(c echo.Context, session *cookie.SessionContext) {
|
||||
if session == nil {
|
||||
session = defaultSession()
|
||||
}
|
||||
c.Set(sessionContextKey, session)
|
||||
}
|
||||
|
||||
func GetSession(c echo.Context) *cookie.SessionContext {
|
||||
if value := c.Get(sessionContextKey); value != nil {
|
||||
if session, ok := value.(*cookie.SessionContext); ok {
|
||||
if session != nil {
|
||||
return session
|
||||
}
|
||||
}
|
||||
}
|
||||
return defaultSession()
|
||||
}
|
||||
|
||||
func defaultSession() *cookie.SessionContext {
|
||||
return &cookie.SessionContext{
|
||||
Values: map[string]string{},
|
||||
ParseStatus: cookie.ParseStatusAnonymous,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
func RequestID() echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
id := c.Request().Header.Get(echo.HeaderXRequestID)
|
||||
if id == "" {
|
||||
id = newRequestID()
|
||||
}
|
||||
c.Response().Header().Set(echo.HeaderXRequestID, id)
|
||||
c.Set(echo.HeaderXRequestID, id)
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newRequestID() string {
|
||||
var buf [16]byte
|
||||
if _, err := rand.Read(buf[:]); err != nil {
|
||||
return fmt.Sprintf("req-%d", time.Now().UnixNano())
|
||||
}
|
||||
return hex.EncodeToString(buf[:])
|
||||
}
|
||||
|
||||
func RealIP() echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if forwarded := c.Request().Header.Get("X-Forwarded-For"); forwarded != "" {
|
||||
c.Set("real_ip", forwarded)
|
||||
} else if host, _, err := net.SplitHostPort(c.Request().RemoteAddr); err == nil {
|
||||
c.Set("real_ip", host)
|
||||
}
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func AccessLog(logger *slog.Logger) echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
start := time.Now()
|
||||
err := next(c)
|
||||
session := GetSession(c)
|
||||
customerID := int64Value(session.CustomerID)
|
||||
cartID := int64Value(session.CartID)
|
||||
|
||||
logger.Info("request complete",
|
||||
"method", c.Request().Method,
|
||||
"path", c.Request().URL.Path,
|
||||
"status", c.Response().Status,
|
||||
"latency_ms", time.Since(start).Milliseconds(),
|
||||
"request_id", c.Response().Header().Get(echo.HeaderXRequestID),
|
||||
"parse_status", session.ParseStatus,
|
||||
"customer_id", customerID,
|
||||
"cart_id", cartID,
|
||||
)
|
||||
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Recover(logger *slog.Logger) echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
defer func() {
|
||||
if recovered := recover(); recovered != nil {
|
||||
logger.Error("panic recovered", "error", recovered, "stack", string(debug.Stack()))
|
||||
_ = c.JSON(http.StatusInternalServerError, map[string]string{"error": "internal server error"})
|
||||
}
|
||||
}()
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func HTTPErrorHandler(logger *slog.Logger) echo.HTTPErrorHandler {
|
||||
return func(err error, c echo.Context) {
|
||||
if c.Response().Committed {
|
||||
return
|
||||
}
|
||||
|
||||
var httpErr *echo.HTTPError
|
||||
code := http.StatusInternalServerError
|
||||
message := map[string]string{"error": "internal server error"}
|
||||
|
||||
if errors.As(err, &httpErr) {
|
||||
code = httpErr.Code
|
||||
if msg, ok := httpErr.Message.(string); ok {
|
||||
message = map[string]string{"error": msg}
|
||||
}
|
||||
}
|
||||
|
||||
logger.Error("request failed", "status", code, "error", err)
|
||||
_ = c.JSON(code, message)
|
||||
}
|
||||
}
|
||||
|
||||
func int64Value(value *int64) any {
|
||||
if value == nil {
|
||||
return nil
|
||||
}
|
||||
return *value
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
|
||||
psconfig "prestaproxy/internal/prestashop/config"
|
||||
pscookie "prestaproxy/internal/prestashop/cookie"
|
||||
)
|
||||
|
||||
type AnonymousSessionInitializer interface {
|
||||
NewAnonymous(ctx context.Context, req *http.Request, cookieName string) (*pscookie.SessionContext, error)
|
||||
}
|
||||
|
||||
type LanguageResolver interface {
|
||||
ResolveLanguageID(ctx context.Context, req *http.Request, fallback int64) int64
|
||||
}
|
||||
|
||||
type ProductRouteMatcher interface {
|
||||
Owns(path string) bool
|
||||
}
|
||||
|
||||
func Session(cfg psconfig.Config, codec pscookie.Codec, initializer AnonymousSessionInitializer, languageResolver LanguageResolver, matcher ProductRouteMatcher) echo.MiddlewareFunc {
|
||||
ownership := cfg.ParseRouteOwnership()
|
||||
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
ownedRoute := ownsProductRoute(ownership.ProductPrefixes, c.Request().URL.Path, matcher)
|
||||
configuredCookieName := cfg.DeriveCookieName(requestCookieHost(c.Request()))
|
||||
cookieName, rawCookie := findPrestaShopCookie(c.Request(), configuredCookieName)
|
||||
if cookieName == "" {
|
||||
cookieName = configuredCookieName
|
||||
}
|
||||
|
||||
session, err := codec.Decode(rawCookie)
|
||||
if err != nil {
|
||||
if ownedRoute {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "prestashop cookie decode failed")
|
||||
}
|
||||
SetSession(c, &pscookie.SessionContext{
|
||||
CookieName: cookieName,
|
||||
RawCookie: rawCookie,
|
||||
Values: map[string]string{},
|
||||
ParseStatus: pscookie.ParseStatusInvalid,
|
||||
})
|
||||
return next(c)
|
||||
}
|
||||
session.CookieName = cookieName
|
||||
if ownedRoute && initializer != nil && shouldBootstrapAnonymousSession(rawCookie, session) {
|
||||
session, err = initializer.NewAnonymous(c.Request().Context(), c.Request(), cookieName)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("prestashop session bootstrap failed: %v", err))
|
||||
}
|
||||
}
|
||||
if ownedRoute {
|
||||
applyRequestLanguage(session, resolveRequestLanguageID(c.Request().Context(), c.Request(), session, languageResolver))
|
||||
}
|
||||
if ownedRoute && shouldSetSessionCookie(rawCookie, session) {
|
||||
encoded, err := codec.Encode(session)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, "prestashop cookie encode failed")
|
||||
}
|
||||
session.RawCookie = encoded
|
||||
setPrestaShopCookie(c.Request(), c.Response(), ownership.ProductPrefixes, cookieName, encoded)
|
||||
}
|
||||
|
||||
SetSession(c, session)
|
||||
return next(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func resolveRequestLanguageID(ctx context.Context, req *http.Request, session *pscookie.SessionContext, resolver LanguageResolver) int64 {
|
||||
if resolver == nil {
|
||||
return 0
|
||||
}
|
||||
return resolver.ResolveLanguageID(ctx, req, sessionLanguageID(session))
|
||||
}
|
||||
|
||||
func findPrestaShopCookie(req *http.Request, configuredName string) (name, value string) {
|
||||
cookies := req.Cookies()
|
||||
for _, cookie := range cookies {
|
||||
if cookie.Name == configuredName {
|
||||
return cookie.Name, cookie.Value
|
||||
}
|
||||
}
|
||||
|
||||
prefix := cookiePrefix(configuredName)
|
||||
if prefix == "" {
|
||||
return "", ""
|
||||
}
|
||||
|
||||
for _, cookie := range cookies {
|
||||
if strings.HasPrefix(cookie.Name, prefix) {
|
||||
return cookie.Name, cookie.Value
|
||||
}
|
||||
}
|
||||
|
||||
return "", ""
|
||||
}
|
||||
|
||||
func cookiePrefix(configuredName string) string {
|
||||
if configuredName == "" {
|
||||
return ""
|
||||
}
|
||||
if strings.HasPrefix(configuredName, "PrestaShop-") {
|
||||
return "PrestaShop-"
|
||||
}
|
||||
if idx := strings.Index(configuredName, "-"); idx >= 0 {
|
||||
return configuredName[:idx+1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func shouldBootstrapAnonymousSession(rawCookie string, session *pscookie.SessionContext) bool {
|
||||
if session == nil {
|
||||
return true
|
||||
}
|
||||
if rawCookie == "" {
|
||||
return true
|
||||
}
|
||||
if session.IsLoggedIn {
|
||||
return false
|
||||
}
|
||||
return session.GuestID == nil ||
|
||||
session.CurrencyID == nil ||
|
||||
session.LanguageID == nil ||
|
||||
session.Values["id_connections"] == "" ||
|
||||
session.Values["iso_code_country"] == ""
|
||||
}
|
||||
|
||||
func shouldSetSessionCookie(rawCookie string, session *pscookie.SessionContext) bool {
|
||||
if session == nil {
|
||||
return false
|
||||
}
|
||||
if rawCookie == "" {
|
||||
return true
|
||||
}
|
||||
return rawCookie != session.RawCookie
|
||||
}
|
||||
|
||||
func applyRequestLanguage(session *pscookie.SessionContext, languageID int64) {
|
||||
if session == nil || languageID == 0 {
|
||||
return
|
||||
}
|
||||
if current := sessionLanguageID(session); current == languageID {
|
||||
return
|
||||
}
|
||||
|
||||
if session.Values == nil {
|
||||
session.Values = map[string]string{}
|
||||
}
|
||||
|
||||
value := strconv.FormatInt(languageID, 10)
|
||||
session.LanguageID = int64Ptr(languageID)
|
||||
session.Values["id_lang"] = value
|
||||
session.Values["id_language"] = value
|
||||
session.OrderedKeys = ensureOrderedKey(session.OrderedKeys, "id_lang", 1)
|
||||
session.OrderedKeys = ensureOrderedKey(session.OrderedKeys, "id_language", 3)
|
||||
|
||||
if !session.IsLoggedIn {
|
||||
if checksum := anonymousSessionChecksum(session, languageID); checksum != "" {
|
||||
session.Values["checksum"] = checksum
|
||||
session.OrderedKeys = ensureOrderedKey(session.OrderedKeys, "checksum", len(session.OrderedKeys))
|
||||
}
|
||||
}
|
||||
|
||||
session.Plaintext = ""
|
||||
session.RawCookie = ""
|
||||
}
|
||||
|
||||
func sessionLanguageID(session *pscookie.SessionContext) int64 {
|
||||
if session == nil || session.LanguageID == nil {
|
||||
return 0
|
||||
}
|
||||
return *session.LanguageID
|
||||
}
|
||||
|
||||
func anonymousSessionChecksum(session *pscookie.SessionContext, languageID int64) string {
|
||||
if session == nil || session.Values == nil {
|
||||
return ""
|
||||
}
|
||||
guestID, _ := strconv.ParseInt(session.Values["id_guest"], 10, 64)
|
||||
connectionID, _ := strconv.ParseInt(session.Values["id_connections"], 10, 64)
|
||||
currencyID, _ := strconv.ParseInt(session.Values["id_currency"], 10, 64)
|
||||
shopID, _ := strconv.ParseInt(session.Values["id_shop"], 10, 64)
|
||||
if guestID == 0 || connectionID == 0 || currencyID == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
buf := make([]byte, 0, 32)
|
||||
for _, value := range []int64{guestID, connectionID, languageID, currencyID, shopID} {
|
||||
buf = strconv.AppendInt(buf, value, 10)
|
||||
buf = append(buf, '|')
|
||||
}
|
||||
return strconv.FormatUint(uint64(crc32.ChecksumIEEE(buf)), 10)
|
||||
}
|
||||
|
||||
func ensureOrderedKey(keys []string, key string, index int) []string {
|
||||
for i, existing := range keys {
|
||||
if existing != key {
|
||||
continue
|
||||
}
|
||||
if i == index || index >= len(keys) {
|
||||
return keys
|
||||
}
|
||||
keys = append(keys[:i], keys[i+1:]...)
|
||||
break
|
||||
}
|
||||
|
||||
if index < 0 {
|
||||
index = 0
|
||||
}
|
||||
if index >= len(keys) {
|
||||
return append(keys, key)
|
||||
}
|
||||
|
||||
keys = append(keys, "")
|
||||
copy(keys[index+1:], keys[index:])
|
||||
keys[index] = key
|
||||
return keys
|
||||
}
|
||||
|
||||
func int64Ptr(value int64) *int64 {
|
||||
if value == 0 {
|
||||
return nil
|
||||
}
|
||||
v := value
|
||||
return &v
|
||||
}
|
||||
|
||||
func setPrestaShopCookie(req *http.Request, res *echo.Response, ownedPrefixes []string, name, value string) {
|
||||
http.SetCookie(res.Writer, &http.Cookie{
|
||||
Name: name,
|
||||
Value: value,
|
||||
Path: requestCookiePath(req.URL.Path, ownedPrefixes),
|
||||
Domain: requestCookieDomain(req),
|
||||
Secure: requestCookieSecure(req),
|
||||
HttpOnly: true,
|
||||
SameSite: http.SameSiteLaxMode,
|
||||
})
|
||||
}
|
||||
|
||||
func requestCookiePath(requestPath string, ownedPrefixes []string) string {
|
||||
for _, prefix := range ownedPrefixes {
|
||||
if prefix == "" || !strings.HasPrefix(requestPath, prefix) {
|
||||
continue
|
||||
}
|
||||
base := path.Clean(strings.TrimSuffix(prefix, "/"))
|
||||
if base == "." || base == "/" {
|
||||
return "/"
|
||||
}
|
||||
parent := path.Dir(base)
|
||||
if parent == "." {
|
||||
return "/"
|
||||
}
|
||||
if !strings.HasSuffix(parent, "/") {
|
||||
parent += "/"
|
||||
}
|
||||
return parent
|
||||
}
|
||||
|
||||
return "/"
|
||||
}
|
||||
|
||||
func requestCookieDomain(req *http.Request) string {
|
||||
host := requestCookieHost(req)
|
||||
if host == "" {
|
||||
return ""
|
||||
}
|
||||
if parsed, err := url.Parse("http://" + host); err == nil {
|
||||
return parsed.Hostname()
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
func requestCookieHost(req *http.Request) string {
|
||||
if req == nil {
|
||||
return ""
|
||||
}
|
||||
host := req.Header.Get("X-Forwarded-Host")
|
||||
if host == "" {
|
||||
host = req.Host
|
||||
}
|
||||
if strings.Contains(host, ",") {
|
||||
host = strings.TrimSpace(strings.Split(host, ",")[0])
|
||||
}
|
||||
return host
|
||||
}
|
||||
|
||||
func requestCookieSecure(req *http.Request) bool {
|
||||
if req.TLS != nil {
|
||||
return true
|
||||
}
|
||||
if forwarded := req.Header.Get("X-Forwarded-Proto"); forwarded != "" {
|
||||
if strings.Contains(forwarded, ",") {
|
||||
forwarded = strings.TrimSpace(strings.Split(forwarded, ",")[0])
|
||||
}
|
||||
return strings.EqualFold(forwarded, "https")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func ownsProductRoute(prefixes []string, path string, matcher ProductRouteMatcher) bool {
|
||||
if matcher != nil {
|
||||
return matcher.Owns(path)
|
||||
}
|
||||
for _, prefix := range prefixes {
|
||||
if prefix != "" && len(path) >= len(prefix) && path[:len(prefix)] == prefix {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package proxy
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
|
||||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
target *url.URL
|
||||
proxy *httputil.ReverseProxy
|
||||
}
|
||||
|
||||
func New(target string) (*Handler, error) {
|
||||
parsed, err := url.Parse(target)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
proxy := httputil.NewSingleHostReverseProxy(parsed)
|
||||
originalDirector := proxy.Director
|
||||
proxy.Director = func(req *http.Request) {
|
||||
originalDirector(req)
|
||||
req.Host = parsed.Host
|
||||
req.Header.Set("X-Forwarded-Host", req.Header.Get("Host"))
|
||||
req.Header.Set("X-Forwarded-Proto", scheme(req))
|
||||
}
|
||||
|
||||
return &Handler{
|
||||
target: parsed,
|
||||
proxy: proxy,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (h *Handler) Handle(c echo.Context) error {
|
||||
h.proxy.ServeHTTP(c.Response(), c.Request())
|
||||
return nil
|
||||
}
|
||||
|
||||
func scheme(req *http.Request) string {
|
||||
if req.TLS != nil {
|
||||
return "https"
|
||||
}
|
||||
if header := req.Header.Get("X-Forwarded-Proto"); header != "" {
|
||||
return header
|
||||
}
|
||||
return "http"
|
||||
}
|
||||
Reference in New Issue
Block a user