mirror of
https://github.com/Gouryella/drip.git
synced 2026-02-23 21:00:44 +00:00
feat(proxy): Support independent configuration for service domain and tunnel domain
- Add serverDomain and tunnelDomain fields to the Handler struct to distinguish between service domain and tunnel domain - Modify the NewHandler function signature to support passing two separate domain parameters - Update the extractSubdomain method to return a subdomain result type with three states: home, found, notFound - Add serveTunnelNotFound method to handle tunnel not found cases, returning a 404 page - Add favicon support to display an icon on the page - Adjust routing logic to display a dedicated 404 page when accessing a tunnel domain but the corresponding tunnel is not found
This commit is contained in:
@@ -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)
|
||||
|
||||
|
||||
5
internal/server/proxy/favicon.go
Normal file
5
internal/server/proxy/favicon.go
Normal 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 + `">`
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user