Merge pull request #20 from Gouryella/feat/502-error-page

feat(tcp): Improved local service unavailability error response
This commit is contained in:
Gouryella
2026-01-16 20:43:26 +08:00
committed by GitHub
2 changed files with 89 additions and 1 deletions

View File

@@ -117,7 +117,7 @@ func (c *PoolClient) handleHTTPStream(stream net.Conn) {
resp, err := c.httpClient.Do(outReq)
if err != nil {
httputil.WriteProxyError(cc, http.StatusBadGateway, "Local service unavailable")
httputil.WriteLocalServiceUnavailable(cc, c.localPort)
return
}
defer resp.Body.Close()

View File

@@ -64,6 +64,94 @@ func WriteProxyError(w io.Writer, code int, msg string) {
_ = resp.Body.Close()
}
func WriteLocalServiceUnavailable(w io.Writer, localPort int) {
html := fmt.Sprintf(`<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>502 - Local Service Unavailable</title>
<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; }
code {
background: #f6f8fa;
border: 1px solid #d0d7de;
border-radius: 4px;
padding: 2px 6px;
font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;
font-size: 14px;
}
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>Local Service Unavailable</h1>
<p class="desc">The tunnel is active, but the local service is not responding.</p>
</header>
<div class="info-box">
<p>This could happen because:</p>
<ul>
<li>No service is running on port <code>%d</code></li>
<li>The local service has crashed or stopped</li>
<li>The service is still starting up</li>
</ul>
</div>
<p>Please ensure your local service is running on port <code>%d</code> and try again.</p>
<footer>
<a href="https://github.com/Gouryella/drip" target="_blank">GitHub</a>
</footer>
</div>
</body>
</html>`, localPort, localPort)
resp := &http.Response{
StatusCode: http.StatusBadGateway,
Status: "502 Bad Gateway",
Proto: "HTTP/1.1",
ProtoMajor: 1,
ProtoMinor: 1,
Header: make(http.Header),
Body: io.NopCloser(strings.NewReader(html)),
ContentLength: int64(len(html)),
Close: true,
}
resp.Header.Set("Content-Type", "text/html; charset=utf-8")
resp.Header.Set("Content-Length", fmt.Sprintf("%d", len(html)))
_ = resp.Write(w)
_ = resp.Body.Close()
}
// IsWebSocketUpgrade checks if the request is a WebSocket upgrade request.
func IsWebSocketUpgrade(req *http.Request) bool {
return strings.EqualFold(req.Header.Get("Upgrade"), "websocket") &&