Merge pull request #19 from Gouryella/fix/subdomain-routing-and-404

feat(proxy): Support independent configuration for service domain and…
This commit is contained in:
Gouryella
2026-01-16 17:30:17 +08:00
committed by GitHub
3 changed files with 109 additions and 13 deletions

View File

@@ -285,7 +285,7 @@ func runServer(cmd *cobra.Command, _ []string) error {
listenAddr := fmt.Sprintf("0.0.0.0:%d", cfg.Port)
httpHandler := proxy.NewHandler(tunnelManager, logger, cfg.TunnelDomain, cfg.AuthToken, cfg.MetricsToken)
httpHandler := proxy.NewHandler(tunnelManager, logger, cfg.Domain, cfg.TunnelDomain, cfg.AuthToken, cfg.MetricsToken)
httpHandler.SetAllowedTransports(cfg.AllowedTransports)
httpHandler.SetAllowedTunnelTypes(cfg.AllowedTunnelTypes)

View File

@@ -0,0 +1,5 @@
package proxy
const faviconBase64 = "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"
const faviconLink = `<link rel="icon" type="image/png" href="data:image/png;base64,` + faviconBase64 + `">`

View File

@@ -93,7 +93,8 @@ func generateSessionToken() string {
type Handler struct {
manager *tunnel.Manager
logger *zap.Logger
domain string
serverDomain string
tunnelDomain string
authToken string
metricsToken string
publicPort int
@@ -130,11 +131,12 @@ func init() {
}
}
func NewHandler(manager *tunnel.Manager, logger *zap.Logger, domain string, authToken string, metricsToken string) *Handler {
func NewHandler(manager *tunnel.Manager, logger *zap.Logger, serverDomain, tunnelDomain string, authToken string, metricsToken string) *Handler {
return &Handler{
manager: manager,
logger: logger,
domain: domain,
serverDomain: serverDomain,
tunnelDomain: tunnelDomain,
authToken: authToken,
metricsToken: metricsToken,
wsUpgrader: websocket.Upgrader{
@@ -230,15 +232,19 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
subdomain := h.extractSubdomain(r.Host)
if subdomain == "" {
subdomain, result := h.extractSubdomain(r.Host)
switch result {
case subdomainHome:
h.serveHomePage(w, r)
return
case subdomainNotFound:
h.serveTunnelNotFound(w, r)
return
}
tconn, ok := h.manager.Get(subdomain)
if !ok || tconn == nil {
http.Error(w, "Tunnel not found. The tunnel may have been closed.", http.StatusNotFound)
h.serveTunnelNotFound(w, r)
return
}
if tconn.IsClosed() {
@@ -495,21 +501,33 @@ func (h *Handler) rewriteLocationHeader(location, proxyHost string) string {
return location
}
func (h *Handler) extractSubdomain(host string) string {
type subdomainResult int
const (
subdomainHome subdomainResult = iota
subdomainFound
subdomainNotFound
)
func (h *Handler) extractSubdomain(host string) (string, subdomainResult) {
if idx := strings.Index(host, ":"); idx != -1 {
host = host[:idx]
}
if host == h.domain {
return ""
if host == h.serverDomain {
return "", subdomainHome
}
suffix := "." + h.domain
suffix := "." + h.tunnelDomain
if strings.HasSuffix(host, suffix) {
return strings.TrimSuffix(host, suffix)
return strings.TrimSuffix(host, suffix), subdomainFound
}
return ""
if host == h.tunnelDomain {
return "", subdomainNotFound
}
return "", subdomainNotFound
}
// extractClientIP extracts the client IP from the request.
@@ -572,6 +590,7 @@ func (h *Handler) serveHomePage(w http.ResponseWriter, r *http.Request) {
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Drip - Your Tunnel, Your Domain, Anywhere</title>
` + faviconLink + `
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
@@ -699,6 +718,77 @@ func (h *Handler) serveHomePage(w http.ResponseWriter, r *http.Request) {
w.Write(data)
}
func (h *Handler) serveTunnelNotFound(w http.ResponseWriter, r *http.Request) {
html := `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>404 - Tunnel Not Found</title>
` + faviconLink + `
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #fff;
color: #24292f;
line-height: 1.6;
}
.container { max-width: 720px; margin: 0 auto; padding: 48px 24px; }
header { margin-bottom: 48px; }
h1 { font-size: 28px; font-weight: 600; margin-bottom: 8px; }
h1 span { margin-right: 8px; }
.desc { color: #57606a; font-size: 16px; }
p { margin-bottom: 16px; }
.info-box {
background: #f6f8fa;
border: 1px solid #d0d7de;
border-radius: 6px;
padding: 16px;
margin: 24px 0;
}
.info-box ul {
margin: 12px 0 0 20px;
color: #57606a;
}
.info-box li { margin-bottom: 8px; }
footer { margin-top: 48px; padding-top: 24px; border-top: 1px solid #d0d7de; }
footer a { color: #57606a; text-decoration: none; font-size: 14px; }
footer a:hover { color: #0969da; }
</style>
</head>
<body>
<div class="container">
<header>
<h1><span>🔍</span>Tunnel Not Found</h1>
<p class="desc">The requested tunnel does not exist or has been closed.</p>
</header>
<div class="info-box">
<p>This could happen because:</p>
<ul>
<li>The tunnel was never created</li>
<li>The tunnel has been closed by the owner</li>
<li>The tunnel URL is incorrect</li>
</ul>
</div>
<p>If you are the tunnel owner, please restart your tunnel client.</p>
<footer>
<a href="https://github.com/Gouryella/drip" target="_blank">GitHub</a>
</footer>
</div>
</body>
</html>`
data := []byte(html)
w.Header().Set("Content-Type", "text/html")
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(data)))
w.WriteHeader(http.StatusNotFound)
w.Write(data)
}
func (h *Handler) serveHealth(w http.ResponseWriter, r *http.Request) {
health := map[string]interface{}{
"status": "ok",
@@ -864,6 +954,7 @@ func (h *Handler) serveLoginPage(w http.ResponseWriter, r *http.Request, subdoma
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>%s - Drip</title>
`+faviconLink+`
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {