diff --git a/internal/client/tcp/pool_handler.go b/internal/client/tcp/pool_handler.go index 8056ee8..ff56db0 100644 --- a/internal/client/tcp/pool_handler.go +++ b/internal/client/tcp/pool_handler.go @@ -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() diff --git a/internal/shared/httputil/helpers.go b/internal/shared/httputil/helpers.go index 5c92c70..d0644b7 100644 --- a/internal/shared/httputil/helpers.go +++ b/internal/shared/httputil/helpers.go @@ -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(` + + + + + 502 - Local Service Unavailable + + + +
+
+

⚠️Local Service Unavailable

+

The tunnel is active, but the local service is not responding.

+
+ +
+

This could happen because:

+ +
+ +

Please ensure your local service is running on port %d and try again.

+ + +
+ +`, 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") &&