detached 90b5233

feat: Optimize proxy routing and rate limiting configurations

- Adjust rate limiter settings for HTTP and HTTPS servers
- Enhance admin container IP handling with specialized update logic
- Improve URL parsing and proxy request handling for different domain types
- Add more robust IP and header management in reverse proxy
- Implement domain-specific proxy routing with improved logging
commit
90b5233234891b2918079c6c0ed09ba18598b608
author
bamen · · 2025-03-10 19:23 UTC
committer
bamen · · 2025-03-10 18:29 UTC
parent
0389b69
signature
unsigned
2 files · +161 -79
M internal/proxy/middleware.go +8 -8
@@ -58,8 +58,8 @@ func (p *Proxy) setupMiddleware() {
5858 // Create a new Starskey rate limiter store for HTTPS server
5959 httpsRateLimiter, err := kv.NewStarskeyRateLimiterStore(
6060 "./data/ratelimiter/https", // Path to store rate limit data
61- 2, // Rate: 2 requests per second (lowered from 10)
62- 10, // Burst: 10 requests (lowered from 30)
61+ 10, // Rate: 10 requests per second (lowered from 10)
62+ 30, // Burst: 30 requests (lowered from 30)
6363 3*time.Minute, // Expires in 3 minutes
6464 )
6565 if err != nil {
@@ -69,8 +69,8 @@ func (p *Proxy) setupMiddleware() {
6969 p.httpsServer.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{
7070 Store: middleware.NewRateLimiterMemoryStoreWithConfig(
7171 middleware.RateLimiterMemoryStoreConfig{
72- Rate: rate.Limit(2), // 2 requests per second (lowered from 10)
73- Burst: 10, // Burst of 10 requests (lowered from 30)
72+ Rate: rate.Limit(10), // 10 requests per second (lowered from 10)
73+ Burst: 30, // Burst of 30 requests (lowered from 30)
7474 ExpiresIn: 3 * time.Minute, // Store expiration
7575 },
7676 ),
@@ -137,8 +137,8 @@ func (p *Proxy) setupMiddleware() {
137137 // Create a new Starskey rate limiter store for HTTP server
138138 httpRateLimiter, err := kv.NewStarskeyRateLimiterStore(
139139 "./data/ratelimiter/http", // Path to store rate limit data
140- 10, // Rate: 10 requests per second (lowered from 10)
141- 30, // Burst: 30 requests (lowered from 30)
140+ 2, // Rate: 2 requests per second (lowered from 10)
141+ 10, // Burst: 10 requests (lowered from 30)
142142 3*time.Minute, // Expires in 3 minutes
143143 )
144144 if err != nil {
@@ -148,8 +148,8 @@ func (p *Proxy) setupMiddleware() {
148148 p.httpServer.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{
149149 Store: middleware.NewRateLimiterMemoryStoreWithConfig(
150150 middleware.RateLimiterMemoryStoreConfig{
151- Rate: rate.Limit(10), // 10 requests per second (lowered from 10)
152- Burst: 30, // Burst of 30 requests (lowered from 30)
151+ Rate: rate.Limit(2), // 2 requests per second (lowered from 10)
152+ Burst: 10, // Burst of 10 requests (lowered from 30)
153153 ExpiresIn: 3 * time.Minute, // Store expiration
154154 },
155155 ),
@@ -58,8 +58,8 @@ func (p *Proxy) setupMiddleware() {
58 // Create a new Starskey rate limiter store for HTTPS server58 // Create a new Starskey rate limiter store for HTTPS server
59 httpsRateLimiter, err := kv.NewStarskeyRateLimiterStore(59 httpsRateLimiter, err := kv.NewStarskeyRateLimiterStore(
60 "./data/ratelimiter/https", // Path to store rate limit data60 "./data/ratelimiter/https", // Path to store rate limit data
61- 2, // Rate: 2 requests per second (lowered from 10)61+ 10, // Rate: 10 requests per second (lowered from 10)
62- 10, // Burst: 10 requests (lowered from 30)62+ 30, // Burst: 30 requests (lowered from 30)
63 3*time.Minute, // Expires in 3 minutes63 3*time.Minute, // Expires in 3 minutes
64 )64 )
65 if err != nil {65 if err != nil {
@@ -69,8 +69,8 @@ func (p *Proxy) setupMiddleware() {
69 p.httpsServer.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{69 p.httpsServer.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{
70 Store: middleware.NewRateLimiterMemoryStoreWithConfig(70 Store: middleware.NewRateLimiterMemoryStoreWithConfig(
71 middleware.RateLimiterMemoryStoreConfig{71 middleware.RateLimiterMemoryStoreConfig{
72- Rate: rate.Limit(2), // 2 requests per second (lowered from 10)72+ Rate: rate.Limit(10), // 10 requests per second (lowered from 10)
73- Burst: 10, // Burst of 10 requests (lowered from 30)73+ Burst: 30, // Burst of 30 requests (lowered from 30)
74 ExpiresIn: 3 * time.Minute, // Store expiration74 ExpiresIn: 3 * time.Minute, // Store expiration
75 },75 },
76 ),76 ),
@@ -137,8 +137,8 @@ func (p *Proxy) setupMiddleware() {
137 // Create a new Starskey rate limiter store for HTTP server137 // Create a new Starskey rate limiter store for HTTP server
138 httpRateLimiter, err := kv.NewStarskeyRateLimiterStore(138 httpRateLimiter, err := kv.NewStarskeyRateLimiterStore(
139 "./data/ratelimiter/http", // Path to store rate limit data139 "./data/ratelimiter/http", // Path to store rate limit data
140- 10, // Rate: 10 requests per second (lowered from 10)140+ 2, // Rate: 2 requests per second (lowered from 10)
141- 30, // Burst: 30 requests (lowered from 30)141+ 10, // Burst: 10 requests (lowered from 30)
142 3*time.Minute, // Expires in 3 minutes142 3*time.Minute, // Expires in 3 minutes
143 )143 )
144 if err != nil {144 if err != nil {
@@ -148,8 +148,8 @@ func (p *Proxy) setupMiddleware() {
148 p.httpServer.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{148 p.httpServer.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{
149 Store: middleware.NewRateLimiterMemoryStoreWithConfig(149 Store: middleware.NewRateLimiterMemoryStoreWithConfig(
150 middleware.RateLimiterMemoryStoreConfig{150 middleware.RateLimiterMemoryStoreConfig{
151- Rate: rate.Limit(10), // 10 requests per second (lowered from 10)151+ Rate: rate.Limit(2), // 2 requests per second (lowered from 10)
152- Burst: 30, // Burst of 30 requests (lowered from 30)152+ Burst: 10, // Burst of 10 requests (lowered from 30)
153 ExpiresIn: 3 * time.Minute, // Store expiration153 ExpiresIn: 3 * time.Minute, // Store expiration
154 },154 },
155 ),155 ),
M internal/proxy/routes.go +153 -71
@@ -1104,6 +1104,17 @@ func (p *Proxy) StartContainerEventListener() error {
11041104 "container_name", containerName,
11051105 "container_ip", containerIP)
11061106
1107+ // Check if this is the Gordon container (admin domain)
1108+ isAdminContainer := false
1109+ adminDomain := p.app.GetConfig().Http.FullDomain()
1110+
1111+ p.mu.RLock()
1112+ if route, exists := p.routes[adminDomain]; exists && route.ContainerID == containerID {
1113+ isAdminContainer = true
1114+ logger.Debug("Admin container event detected", "container_id", containerID)
1115+ }
1116+ p.mu.RUnlock()
1117+
11071118 // First, check if there are any existing routes for this container ID
11081119 var existingRoutes = make(map[string]*ProxyRouteInfo)
11091120 p.mu.RLock()
@@ -1134,7 +1145,67 @@ func (p *Proxy) StartContainerEventListener() error {
11341145
11351146 // Check and update each route if IP has changed
11361147 for domain, route := range existingRoutes {
1137- if route.ContainerIP != containerIP {
1148+ // Handle admin domain specially to avoid path issues
1149+ if domain == adminDomain && isAdminContainer {
1150+ // For admin domain, only update the IP if it has changed
1151+ if route.ContainerIP != containerIP {
1152+ logger.Info("Admin container restarted with new IP - updating route",
1153+ "container_id", containerID,
1154+ "container_name", containerName,
1155+ "domain", domain,
1156+ "old_ip", route.ContainerIP,
1157+ "new_ip", containerIP)
1158+
1159+ // Update IP in memory only first
1160+ p.mu.Lock()
1161+ if r, ok := p.routes[domain]; ok {
1162+ r.ContainerIP = containerIP
1163+ r.Active = true
1164+ }
1165+ p.mu.Unlock()
1166+
1167+ // Update DB in background
1168+ go func(domain, containerID, containerIP string) {
1169+ // Get the domain ID
1170+ var domainID string
1171+ rows, err := p.dbQueryWithRetry("SELECT id FROM domain WHERE name = ?", domain)
1172+ if err != nil {
1173+ logger.Error("Failed to query domain for admin IP update", "domain", domain, "error", err)
1174+ return
1175+ }
1176+
1177+ if !rows.Next() {
1178+ rows.Close()
1179+ logger.Error("Admin domain not found in database", "domain", domain)
1180+ return
1181+ }
1182+
1183+ if err := rows.Scan(&domainID); err != nil {
1184+ rows.Close()
1185+ logger.Error("Failed to scan admin domain ID", "domain", domain, "error", err)
1186+ return
1187+ }
1188+ rows.Close()
1189+
1190+ // Update the IP in the database directly
1191+ now := time.Now().Format(time.RFC3339)
1192+ _, err = p.dbExecWithRetry(`
1193+ UPDATE proxy_route SET container_ip = ?, active = 1, updated_at = ?
1194+ WHERE domain_id = ? AND container_id = ?`,
1195+ containerIP, now, domainID, containerID)
1196+
1197+ if err != nil {
1198+ logger.Error("Failed to update admin container IP in database", "domain", domain, "error", err)
1199+ } else {
1200+ logger.Info("Updated admin container IP in database",
1201+ "domain", domain,
1202+ "container_id", containerID,
1203+ "new_ip", containerIP)
1204+ }
1205+ }(domain, containerID, containerIP)
1206+ }
1207+ } else if route.ContainerIP != containerIP {
1208+ // For non-admin domains, use the standard route update
11381209 logger.Info("Container restarted with new IP - updating route",
11391210 "container_id", containerID,
11401211 "container_name", containerName,
@@ -1702,68 +1773,67 @@ func (p *Proxy) proxyRequest(c echo.Context, route *ProxyRouteInfo) error {
17021773 containerPort := route.ContainerPort
17031774 targetProtocol := route.Protocol
17041775
1705- // Always verify the container IP is current before proxying
1706- networkName := p.app.GetConfig().ContainerEngine.Network
1776+ // Check if this is the admin domain
1777+ isAdminDomain := host == p.app.GetConfig().Http.FullDomain()
17071778
1708- // Get the latest container info to check if IP has changed
1709- containerInfo, err := docker.GetContainerInfo(containerID)
1710- if err == nil && containerInfo.NetworkSettings != nil {
1711- // Check if container has a different IP
1712- if networkSettings, exists := containerInfo.NetworkSettings.Networks[networkName]; exists &&
1713- networkSettings.IPAddress != "" && networkSettings.IPAddress != containerIP {
1714- // Found container with different IP, update the route and use new IP for this request
1715- logger.Warn("Container IP mismatch detected, using live IP instead of cached value",
1716- "domain", host,
1717- "cached_ip", containerIP,
1718- "live_ip", networkSettings.IPAddress)
1779+ // For non-admin domains, verify the container IP is current before proxying
1780+ if !isAdminDomain {
1781+ networkName := p.app.GetConfig().ContainerEngine.Network
17191782
1720- // Use the new IP for this request
1721- containerIP = networkSettings.IPAddress
1783+ // Get the latest container info to check if IP has changed
1784+ containerInfo, err := docker.GetContainerInfo(containerID)
1785+ if err == nil && containerInfo.NetworkSettings != nil {
1786+ // Check if container has a different IP
1787+ if networkSettings, exists := containerInfo.NetworkSettings.Networks[networkName]; exists &&
1788+ networkSettings.IPAddress != "" && networkSettings.IPAddress != containerIP {
1789+ // Found container with different IP, update the route and use new IP for this request
1790+ logger.Warn("Container IP mismatch detected, using live IP instead of cached value",
1791+ "domain", host,
1792+ "cached_ip", containerIP,
1793+ "live_ip", networkSettings.IPAddress)
17221794
1723- // Update routes in the background
1724- go func(domain, containerID, containerIP, containerPort string) {
1725- err := p.ForceUpdateRouteIP(domain, containerIP)
1726- if err != nil {
1727- logger.Error("Failed to update IP after mismatch detection",
1728- "domain", domain,
1729- "error", err)
1730- }
1731- }(host, containerID, containerIP, containerPort)
1795+ // Use the new IP for this request
1796+ containerIP = networkSettings.IPAddress
1797+
1798+ // Update routes in the background
1799+ go func(domain, containerID, containerIP, containerPort string) {
1800+ err := p.ForceUpdateRouteIP(domain, containerIP)
1801+ if err != nil {
1802+ logger.Error("Failed to update IP after mismatch detection",
1803+ "domain", domain,
1804+ "error", err)
1805+ }
1806+ }(host, containerID, containerIP, containerPort)
1807+ }
17321808 }
17331809 }
17341810
1735- // Construct the target URL
1736- targetURL := fmt.Sprintf("%s://%s:%s", targetProtocol, containerIP, containerPort)
1737-
1738- // Add the path from the original request
1739- if c.Request().URL.Path != "" {
1740- targetURL += c.Request().URL.Path
1811+ // Create the target URL
1812+ targetURL := &url.URL{
1813+ Scheme: targetProtocol,
17411814 }
17421815
1743- // Add query string if present
1744- if c.Request().URL.RawQuery != "" {
1745- targetURL += "?" + c.Request().URL.RawQuery
1816+ // Format host properly for both IPv4 and IPv6
1817+ if strings.Contains(containerIP, ":") {
1818+ // IPv6 address needs to be wrapped in brackets
1819+ containerIP = "[" + containerIP + "]"
17461820 }
1821+ targetURL.Host = fmt.Sprintf("%s:%s", containerIP, containerPort)
17471822
1748- // Parse the target URL
1749- target, err := url.Parse(targetURL)
1750- if err != nil {
1751- logger.Error("Failed to parse target URL",
1752- "url", targetURL,
1753- "error", err)
1754- return echo.NewHTTPError(http.StatusInternalServerError, "Failed to parse target URL")
1755- }
1823+ // Set the path - keeping it simple to avoid duplication issues
1824+ targetURL.Path = c.Request().URL.Path
1825+ targetURL.RawQuery = c.Request().URL.RawQuery
17561826
1757- // Special logging for admin domain
1758- if host == p.app.GetConfig().Http.FullDomain() {
1827+ // Special logging based on domain type
1828+ if isAdminDomain {
17591829 logger.Debug("Proxying request to admin domain",
17601830 "domain", host,
1761- "target", targetURL,
1831+ "target", targetURL.String(),
17621832 "path", c.Request().URL.Path)
17631833 } else {
1764- logger.Debug("Proxying request host",
1834+ logger.Debug("Proxying request to container",
17651835 "host", host,
1766- "target", targetURL,
1836+ "target", targetURL.String(),
17671837 "path", c.Request().URL.Path,
17681838 "clientIP", c.RealIP(),
17691839 "remoteAddr", c.Request().RemoteAddr,
@@ -1771,7 +1841,7 @@ func (p *Proxy) proxyRequest(c echo.Context, route *ProxyRouteInfo) error {
17711841 }
17721842
17731843 // Create a reverse proxy
1774- proxy := httputil.NewSingleHostReverseProxy(target)
1844+ proxy := httputil.NewSingleHostReverseProxy(targetURL)
17751845
17761846 // Use our custom transport with longer timeouts
17771847 if p.reverseProxyClient != nil {
@@ -1784,10 +1854,16 @@ func (p *Proxy) proxyRequest(c echo.Context, route *ProxyRouteInfo) error {
17841854 // Call the original director first
17851855 originalDirector(req)
17861856
1787- // Update the Host header to the target host
1788- req.Host = target.Host
1857+ // Handle the Host header differently based on domain type
1858+ if isAdminDomain {
1859+ // For admin domain, use the target host for internal routing
1860+ req.Host = targetURL.Host
1861+ } else {
1862+ // For other domains, keep the original host
1863+ req.Host = host
1864+ }
17891865
1790- // Set X-Forwarded-For
1866+ // Set X-Forwarded headers to preserve client information
17911867 if clientIP := c.RealIP(); clientIP != "" {
17921868 req.Header.Set("X-Forwarded-For", clientIP)
17931869 req.Header.Set("X-Real-IP", clientIP)
@@ -1796,6 +1872,9 @@ func (p *Proxy) proxyRequest(c echo.Context, route *ProxyRouteInfo) error {
17961872 // Set X-Forwarded-Proto to the original protocol
17971873 req.Header.Set("X-Forwarded-Proto", c.Scheme())
17981874
1875+ // Set X-Forwarded-Host to the original host
1876+ req.Header.Set("X-Forwarded-Host", host)
1877+
17991878 // Log request headers for debugging
18001879 logger.Debug("Request headers for proxy",
18011880 "domain", host,
@@ -1807,30 +1886,33 @@ func (p *Proxy) proxyRequest(c echo.Context, route *ProxyRouteInfo) error {
18071886 proxy.ErrorHandler = func(rw http.ResponseWriter, req *http.Request, err error) {
18081887 logger.Error("Proxy error",
18091888 "host", host,
1810- "target", targetURL,
1889+ "target", targetURL.String(),
18111890 "error", err)
18121891
1813- // Check if this is a connection refused error
1814- if strings.Contains(err.Error(), "connection refused") ||
1815- strings.Contains(err.Error(), "no route to host") ||
1816- strings.Contains(err.Error(), "i/o timeout") {
1817- // Container might be down or unreachable - mark the route as inactive
1818- logger.Warn("Container appears to be down, marking route as inactive",
1819- "domain", host,
1820- "container_id", containerID)
1821-
1822- // Find domains for this container and mark them inactive
1823- var domains []string
1824- p.mu.RLock()
1825- for d, r := range p.routes {
1826- if r.ContainerID == containerID {
1827- domains = append(domains, d)
1892+ // Skip container down detection for admin domain
1893+ if !isAdminDomain {
1894+ // Check if this is a connection refused error
1895+ if strings.Contains(err.Error(), "connection refused") ||
1896+ strings.Contains(err.Error(), "no route to host") ||
1897+ strings.Contains(err.Error(), "i/o timeout") {
1898+ // Container might be down or unreachable - mark the route as inactive
1899+ logger.Warn("Container appears to be down, marking route as inactive",
1900+ "domain", host,
1901+ "container_id", containerID)
1902+
1903+ // Find domains for this container and mark them inactive
1904+ var domains []string
1905+ p.mu.RLock()
1906+ for d, r := range p.routes {
1907+ if r.ContainerID == containerID {
1908+ domains = append(domains, d)
1909+ }
18281910 }
1829- }
1830- p.mu.RUnlock()
1911+ p.mu.RUnlock()
18311912
1832- if len(domains) > 0 {
1833- go p.markRoutesInactive(domains)
1913+ if len(domains) > 0 {
1914+ go p.markRoutesInactive(domains)
1915+ }
18341916 }
18351917 }
18361918
@@ -1104,6 +1104,17 @@ func (p *Proxy) StartContainerEventListener() error {
1104 "container_name", containerName,1104 "container_name", containerName,
1105 "container_ip", containerIP)1105 "container_ip", containerIP)
1106 1106
1107+ // Check if this is the Gordon container (admin domain)
1108+ isAdminContainer := false
1109+ adminDomain := p.app.GetConfig().Http.FullDomain()
1110+
1111+ p.mu.RLock()
1112+ if route, exists := p.routes[adminDomain]; exists && route.ContainerID == containerID {
1113+ isAdminContainer = true
1114+ logger.Debug("Admin container event detected", "container_id", containerID)
1115+ }
1116+ p.mu.RUnlock()
1117+
1107 // First, check if there are any existing routes for this container ID1118 // First, check if there are any existing routes for this container ID
1108 var existingRoutes = make(map[string]*ProxyRouteInfo)1119 var existingRoutes = make(map[string]*ProxyRouteInfo)
1109 p.mu.RLock()1120 p.mu.RLock()
@@ -1134,7 +1145,67 @@ func (p *Proxy) StartContainerEventListener() error {
1134 1145
1135 // Check and update each route if IP has changed1146 // Check and update each route if IP has changed
1136 for domain, route := range existingRoutes {1147 for domain, route := range existingRoutes {
1137- if route.ContainerIP != containerIP {1148+ // Handle admin domain specially to avoid path issues
1149+ if domain == adminDomain && isAdminContainer {
1150+ // For admin domain, only update the IP if it has changed
1151+ if route.ContainerIP != containerIP {
1152+ logger.Info("Admin container restarted with new IP - updating route",
1153+ "container_id", containerID,
1154+ "container_name", containerName,
1155+ "domain", domain,
1156+ "old_ip", route.ContainerIP,
1157+ "new_ip", containerIP)
1158+
1159+ // Update IP in memory only first
1160+ p.mu.Lock()
1161+ if r, ok := p.routes[domain]; ok {
1162+ r.ContainerIP = containerIP
1163+ r.Active = true
1164+ }
1165+ p.mu.Unlock()
1166+
1167+ // Update DB in background
1168+ go func(domain, containerID, containerIP string) {
1169+ // Get the domain ID
1170+ var domainID string
1171+ rows, err := p.dbQueryWithRetry("SELECT id FROM domain WHERE name = ?", domain)
1172+ if err != nil {
1173+ logger.Error("Failed to query domain for admin IP update", "domain", domain, "error", err)
1174+ return
1175+ }
1176+
1177+ if !rows.Next() {
1178+ rows.Close()
1179+ logger.Error("Admin domain not found in database", "domain", domain)
1180+ return
1181+ }
1182+
1183+ if err := rows.Scan(&domainID); err != nil {
1184+ rows.Close()
1185+ logger.Error("Failed to scan admin domain ID", "domain", domain, "error", err)
1186+ return
1187+ }
1188+ rows.Close()
1189+
1190+ // Update the IP in the database directly
1191+ now := time.Now().Format(time.RFC3339)
1192+ _, err = p.dbExecWithRetry(`
1193+ UPDATE proxy_route SET container_ip = ?, active = 1, updated_at = ?
1194+ WHERE domain_id = ? AND container_id = ?`,
1195+ containerIP, now, domainID, containerID)
1196+
1197+ if err != nil {
1198+ logger.Error("Failed to update admin container IP in database", "domain", domain, "error", err)
1199+ } else {
1200+ logger.Info("Updated admin container IP in database",
1201+ "domain", domain,
1202+ "container_id", containerID,
1203+ "new_ip", containerIP)
1204+ }
1205+ }(domain, containerID, containerIP)
1206+ }
1207+ } else if route.ContainerIP != containerIP {
1208+ // For non-admin domains, use the standard route update
1138 logger.Info("Container restarted with new IP - updating route",1209 logger.Info("Container restarted with new IP - updating route",
1139 "container_id", containerID,1210 "container_id", containerID,
1140 "container_name", containerName,1211 "container_name", containerName,
@@ -1702,68 +1773,67 @@ func (p *Proxy) proxyRequest(c echo.Context, route *ProxyRouteInfo) error {
1702 containerPort := route.ContainerPort1773 containerPort := route.ContainerPort
1703 targetProtocol := route.Protocol1774 targetProtocol := route.Protocol
1704 1775
1705- // Always verify the container IP is current before proxying1776+ // Check if this is the admin domain
1706- networkName := p.app.GetConfig().ContainerEngine.Network1777+ isAdminDomain := host == p.app.GetConfig().Http.FullDomain()
1707 1778
1708- // Get the latest container info to check if IP has changed1779+ // For non-admin domains, verify the container IP is current before proxying
1709- containerInfo, err := docker.GetContainerInfo(containerID)1780+ if !isAdminDomain {
1710- if err == nil && containerInfo.NetworkSettings != nil {1781+ networkName := p.app.GetConfig().ContainerEngine.Network
1711- // Check if container has a different IP
1712- if networkSettings, exists := containerInfo.NetworkSettings.Networks[networkName]; exists &&
1713- networkSettings.IPAddress != "" && networkSettings.IPAddress != containerIP {
1714- // Found container with different IP, update the route and use new IP for this request
1715- logger.Warn("Container IP mismatch detected, using live IP instead of cached value",
1716- "domain", host,
1717- "cached_ip", containerIP,
1718- "live_ip", networkSettings.IPAddress)
1719 1782
1720- // Use the new IP for this request1783+ // Get the latest container info to check if IP has changed
1721- containerIP = networkSettings.IPAddress1784+ containerInfo, err := docker.GetContainerInfo(containerID)
1785+ if err == nil && containerInfo.NetworkSettings != nil {
1786+ // Check if container has a different IP
1787+ if networkSettings, exists := containerInfo.NetworkSettings.Networks[networkName]; exists &&
1788+ networkSettings.IPAddress != "" && networkSettings.IPAddress != containerIP {
1789+ // Found container with different IP, update the route and use new IP for this request
1790+ logger.Warn("Container IP mismatch detected, using live IP instead of cached value",
1791+ "domain", host,
1792+ "cached_ip", containerIP,
1793+ "live_ip", networkSettings.IPAddress)
1722 1794
1723- // Update routes in the background1795+ // Use the new IP for this request
1724- go func(domain, containerID, containerIP, containerPort string) {1796+ containerIP = networkSettings.IPAddress
1725- err := p.ForceUpdateRouteIP(domain, containerIP)1797+
1726- if err != nil {1798+ // Update routes in the background
1727- logger.Error("Failed to update IP after mismatch detection",1799+ go func(domain, containerID, containerIP, containerPort string) {
1728- "domain", domain,1800+ err := p.ForceUpdateRouteIP(domain, containerIP)
1729- "error", err)1801+ if err != nil {
1730- }1802+ logger.Error("Failed to update IP after mismatch detection",
1731- }(host, containerID, containerIP, containerPort)1803+ "domain", domain,
1804+ "error", err)
1805+ }
1806+ }(host, containerID, containerIP, containerPort)
1807+ }
1732 }1808 }
1733 }1809 }
1734 1810
1735- // Construct the target URL1811+ // Create the target URL
1736- targetURL := fmt.Sprintf("%s://%s:%s", targetProtocol, containerIP, containerPort)1812+ targetURL := &url.URL{
1737-1813+ Scheme: targetProtocol,
1738- // Add the path from the original request
1739- if c.Request().URL.Path != "" {
1740- targetURL += c.Request().URL.Path
1741 }1814 }
1742 1815
1743- // Add query string if present1816+ // Format host properly for both IPv4 and IPv6
1744- if c.Request().URL.RawQuery != "" {1817+ if strings.Contains(containerIP, ":") {
1745- targetURL += "?" + c.Request().URL.RawQuery1818+ // IPv6 address needs to be wrapped in brackets
1819+ containerIP = "[" + containerIP + "]"
1746 }1820 }
1821+ targetURL.Host = fmt.Sprintf("%s:%s", containerIP, containerPort)
1747 1822
1748- // Parse the target URL1823+ // Set the path - keeping it simple to avoid duplication issues
1749- target, err := url.Parse(targetURL)1824+ targetURL.Path = c.Request().URL.Path
1750- if err != nil {1825+ targetURL.RawQuery = c.Request().URL.RawQuery
1751- logger.Error("Failed to parse target URL",
1752- "url", targetURL,
1753- "error", err)
1754- return echo.NewHTTPError(http.StatusInternalServerError, "Failed to parse target URL")
1755- }
1756 1826
1757- // Special logging for admin domain1827+ // Special logging based on domain type
1758- if host == p.app.GetConfig().Http.FullDomain() {1828+ if isAdminDomain {
1759 logger.Debug("Proxying request to admin domain",1829 logger.Debug("Proxying request to admin domain",
1760 "domain", host,1830 "domain", host,
1761- "target", targetURL,1831+ "target", targetURL.String(),
1762 "path", c.Request().URL.Path)1832 "path", c.Request().URL.Path)
1763 } else {1833 } else {
1764- logger.Debug("Proxying request host",1834+ logger.Debug("Proxying request to container",
1765 "host", host,1835 "host", host,
1766- "target", targetURL,1836+ "target", targetURL.String(),
1767 "path", c.Request().URL.Path,1837 "path", c.Request().URL.Path,
1768 "clientIP", c.RealIP(),1838 "clientIP", c.RealIP(),
1769 "remoteAddr", c.Request().RemoteAddr,1839 "remoteAddr", c.Request().RemoteAddr,
@@ -1771,7 +1841,7 @@ func (p *Proxy) proxyRequest(c echo.Context, route *ProxyRouteInfo) error {
1771 }1841 }
1772 1842
1773 // Create a reverse proxy1843 // Create a reverse proxy
1774- proxy := httputil.NewSingleHostReverseProxy(target)1844+ proxy := httputil.NewSingleHostReverseProxy(targetURL)
1775 1845
1776 // Use our custom transport with longer timeouts1846 // Use our custom transport with longer timeouts
1777 if p.reverseProxyClient != nil {1847 if p.reverseProxyClient != nil {
@@ -1784,10 +1854,16 @@ func (p *Proxy) proxyRequest(c echo.Context, route *ProxyRouteInfo) error {
1784 // Call the original director first1854 // Call the original director first
1785 originalDirector(req)1855 originalDirector(req)
1786 1856
1787- // Update the Host header to the target host1857+ // Handle the Host header differently based on domain type
1788- req.Host = target.Host1858+ if isAdminDomain {
1859+ // For admin domain, use the target host for internal routing
1860+ req.Host = targetURL.Host
1861+ } else {
1862+ // For other domains, keep the original host
1863+ req.Host = host
1864+ }
1789 1865
1790- // Set X-Forwarded-For1866+ // Set X-Forwarded headers to preserve client information
1791 if clientIP := c.RealIP(); clientIP != "" {1867 if clientIP := c.RealIP(); clientIP != "" {
1792 req.Header.Set("X-Forwarded-For", clientIP)1868 req.Header.Set("X-Forwarded-For", clientIP)
1793 req.Header.Set("X-Real-IP", clientIP)1869 req.Header.Set("X-Real-IP", clientIP)
@@ -1796,6 +1872,9 @@ func (p *Proxy) proxyRequest(c echo.Context, route *ProxyRouteInfo) error {
1796 // Set X-Forwarded-Proto to the original protocol1872 // Set X-Forwarded-Proto to the original protocol
1797 req.Header.Set("X-Forwarded-Proto", c.Scheme())1873 req.Header.Set("X-Forwarded-Proto", c.Scheme())
1798 1874
1875+ // Set X-Forwarded-Host to the original host
1876+ req.Header.Set("X-Forwarded-Host", host)
1877+
1799 // Log request headers for debugging1878 // Log request headers for debugging
1800 logger.Debug("Request headers for proxy",1879 logger.Debug("Request headers for proxy",
1801 "domain", host,1880 "domain", host,
@@ -1807,30 +1886,33 @@ func (p *Proxy) proxyRequest(c echo.Context, route *ProxyRouteInfo) error {
1807 proxy.ErrorHandler = func(rw http.ResponseWriter, req *http.Request, err error) {1886 proxy.ErrorHandler = func(rw http.ResponseWriter, req *http.Request, err error) {
1808 logger.Error("Proxy error",1887 logger.Error("Proxy error",
1809 "host", host,1888 "host", host,
1810- "target", targetURL,1889+ "target", targetURL.String(),
1811 "error", err)1890 "error", err)
1812 1891
1813- // Check if this is a connection refused error1892+ // Skip container down detection for admin domain
1814- if strings.Contains(err.Error(), "connection refused") ||1893+ if !isAdminDomain {
1815- strings.Contains(err.Error(), "no route to host") ||1894+ // Check if this is a connection refused error
1816- strings.Contains(err.Error(), "i/o timeout") {1895+ if strings.Contains(err.Error(), "connection refused") ||
1817- // Container might be down or unreachable - mark the route as inactive1896+ strings.Contains(err.Error(), "no route to host") ||
1818- logger.Warn("Container appears to be down, marking route as inactive",1897+ strings.Contains(err.Error(), "i/o timeout") {
1819- "domain", host,1898+ // Container might be down or unreachable - mark the route as inactive
1820- "container_id", containerID)1899+ logger.Warn("Container appears to be down, marking route as inactive",
1821-1900+ "domain", host,
1822- // Find domains for this container and mark them inactive1901+ "container_id", containerID)
1823- var domains []string1902+
1824- p.mu.RLock()1903+ // Find domains for this container and mark them inactive
1825- for d, r := range p.routes {1904+ var domains []string
1826- if r.ContainerID == containerID {1905+ p.mu.RLock()
1827- domains = append(domains, d)1906+ for d, r := range p.routes {
1907+ if r.ContainerID == containerID {
1908+ domains = append(domains, d)
1909+ }
1828 }1910 }
1829- }1911+ p.mu.RUnlock()
1830- p.mu.RUnlock()
1831 1912
1832- if len(domains) > 0 {1913+ if len(domains) > 0 {
1833- go p.markRoutesInactive(domains)1914+ go p.markRoutesInactive(domains)
1915+ }
1834 }1916 }
1835 }1917 }
1836 1918

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 7

Tags 106

↑↓ moveenter openesc close