Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit8ba05a9

Browse files
authored
feat: add switch http(s) button to error page (#12942)
1 parent848ea7e commit8ba05a9

File tree

8 files changed

+142
-29
lines changed

8 files changed

+142
-29
lines changed

‎Makefile‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,8 @@ endef
200200
# calling this manually.
201201
$(CODER_ALL_BINARIES): go.mod go.sum\
202202
$(GO_SRC_FILES)\
203-
$(shell find ./examples/templates)
203+
$(shell find ./examples/templates)\
204+
site/static/error.html
204205

205206
$(get-mode-os-arch-ext)
206207
if [[ "$$os" != "windows" ]] && [[ "$$ext" != "" ]]; then

‎coderd/tailnet.go‎

Lines changed: 44 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@ import (
44
"bufio"
55
"context"
66
"crypto/tls"
7+
"errors"
8+
"fmt"
79
"net"
810
"net/http"
911
"net/http/httputil"
1012
"net/netip"
1113
"net/url"
14+
"strings"
1215
"sync"
1316
"sync/atomic"
1417
"time"
@@ -23,6 +26,7 @@ import (
2326
"cdr.dev/slog"
2427
"github.com/coder/coder/v2/coderd/tracing"
2528
"github.com/coder/coder/v2/coderd/workspaceapps"
29+
"github.com/coder/coder/v2/coderd/workspaceapps/appurl"
2630
"github.com/coder/coder/v2/codersdk/workspacesdk"
2731
"github.com/coder/coder/v2/site"
2832
"github.com/coder/coder/v2/tailnet"
@@ -341,7 +345,7 @@ type ServerTailnet struct {
341345
totalConns*prometheus.CounterVec
342346
}
343347

344-
func (s*ServerTailnet)ReverseProxy(targetURL,dashboardURL*url.URL,agentID uuid.UUID)*httputil.ReverseProxy {
348+
func (s*ServerTailnet)ReverseProxy(targetURL,dashboardURL*url.URL,agentID uuid.UUID,app appurl.ApplicationURL,wildcardHostnamestring)*httputil.ReverseProxy {
345349
// Rewrite the targetURL's Host to point to the agent's IP. This is
346350
// necessary because due to TCP connection caching, each agent needs to be
347351
// addressed invidivually. Otherwise, all connections get dialed as
@@ -351,13 +355,46 @@ func (s *ServerTailnet) ReverseProxy(targetURL, dashboardURL *url.URL, agentID u
351355
tgt.Host=net.JoinHostPort(tailnet.IPFromUUID(agentID).String(),port)
352356

353357
proxy:=httputil.NewSingleHostReverseProxy(&tgt)
354-
proxy.ErrorHandler=func(w http.ResponseWriter,r*http.Request,errerror) {
358+
proxy.ErrorHandler=func(w http.ResponseWriter,r*http.Request,theErrerror) {
359+
var (
360+
desc="Failed to proxy request to application: "+theErr.Error()
361+
additionalInfo=""
362+
additionalButtonLink=""
363+
additionalButtonText=""
364+
)
365+
366+
vartlsError tls.RecordHeaderError
367+
if (errors.As(theErr,&tlsError)&&tlsError.Msg=="first record does not look like a TLS handshake")||
368+
errors.Is(theErr,http.ErrSchemeMismatch) {
369+
// If the error is due to an HTTP/HTTPS mismatch, we can provide a
370+
// more helpful error message with redirect buttons.
371+
switchURL:= url.URL{
372+
Scheme:dashboardURL.Scheme,
373+
}
374+
_,protocol,isPort:=app.PortInfo()
375+
ifisPort {
376+
targetProtocol:="https"
377+
ifprotocol=="https" {
378+
targetProtocol="http"
379+
}
380+
app=app.ChangePortProtocol(targetProtocol)
381+
382+
switchURL.Host=fmt.Sprintf("%s%s",app.String(),strings.TrimPrefix(wildcardHostname,"*"))
383+
additionalButtonLink=switchURL.String()
384+
additionalButtonText=fmt.Sprintf("Switch to %s",strings.ToUpper(targetProtocol))
385+
additionalInfo+=fmt.Sprintf("This error seems to be due to an app protocol mismatch, try switching to %s.",strings.ToUpper(targetProtocol))
386+
}
387+
}
388+
355389
site.RenderStaticErrorPage(w,r, site.ErrorPageData{
356-
Status:http.StatusBadGateway,
357-
Title:"Bad Gateway",
358-
Description:"Failed to proxy request to application: "+err.Error(),
359-
RetryEnabled:true,
360-
DashboardURL:dashboardURL.String(),
390+
Status:http.StatusBadGateway,
391+
Title:"Bad Gateway",
392+
Description:desc,
393+
RetryEnabled:true,
394+
DashboardURL:dashboardURL.String(),
395+
AdditionalInfo:additionalInfo,
396+
AdditionalButtonLink:additionalButtonLink,
397+
AdditionalButtonText:additionalButtonText,
361398
})
362399
}
363400
proxy.Director=s.director(agentID,proxy.Director)

‎coderd/tailnet_test.go‎

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
"github.com/coder/coder/v2/agent/agenttest"
2727
"github.com/coder/coder/v2/agent/proto"
2828
"github.com/coder/coder/v2/coderd"
29+
"github.com/coder/coder/v2/coderd/workspaceapps/appurl"
2930
"github.com/coder/coder/v2/codersdk/agentsdk"
3031
"github.com/coder/coder/v2/codersdk/workspacesdk"
3132
"github.com/coder/coder/v2/tailnet"
@@ -81,7 +82,7 @@ func TestServerTailnet_ReverseProxy_ProxyEnv(t *testing.T) {
8182
u,err:=url.Parse(fmt.Sprintf("http://127.0.0.1:%d",workspacesdk.AgentHTTPAPIServerPort))
8283
require.NoError(t,err)
8384

84-
rp:=serverTailnet.ReverseProxy(u,u,a.id)
85+
rp:=serverTailnet.ReverseProxy(u,u,a.id, appurl.ApplicationURL{},"")
8586

8687
rw:=httptest.NewRecorder()
8788
req:=httptest.NewRequest(
@@ -112,7 +113,7 @@ func TestServerTailnet_ReverseProxy(t *testing.T) {
112113
u,err:=url.Parse(fmt.Sprintf("http://127.0.0.1:%d",workspacesdk.AgentHTTPAPIServerPort))
113114
require.NoError(t,err)
114115

115-
rp:=serverTailnet.ReverseProxy(u,u,a.id)
116+
rp:=serverTailnet.ReverseProxy(u,u,a.id, appurl.ApplicationURL{},"")
116117

117118
rw:=httptest.NewRecorder()
118119
req:=httptest.NewRequest(
@@ -143,7 +144,7 @@ func TestServerTailnet_ReverseProxy(t *testing.T) {
143144
u,err:=url.Parse(fmt.Sprintf("http://127.0.0.1:%d",workspacesdk.AgentHTTPAPIServerPort))
144145
require.NoError(t,err)
145146

146-
rp:=serverTailnet.ReverseProxy(u,u,a.id)
147+
rp:=serverTailnet.ReverseProxy(u,u,a.id, appurl.ApplicationURL{},"")
147148

148149
rw:=httptest.NewRecorder()
149150
req:=httptest.NewRequest(
@@ -177,7 +178,7 @@ func TestServerTailnet_ReverseProxy(t *testing.T) {
177178
u,err:=url.Parse(fmt.Sprintf("http://127.0.0.1:%d",workspacesdk.AgentHTTPAPIServerPort))
178179
require.NoError(t,err)
179180

180-
rp:=serverTailnet.ReverseProxy(u,u,a.id)
181+
rp:=serverTailnet.ReverseProxy(u,u,a.id, appurl.ApplicationURL{},"")
181182

182183
req,err:=http.NewRequestWithContext(ctx,http.MethodGet,u.String(),nil)
183184
require.NoError(t,err)
@@ -222,7 +223,7 @@ func TestServerTailnet_ReverseProxy(t *testing.T) {
222223
u,err:=url.Parse("http://127.0.0.1"+port)
223224
require.NoError(t,err)
224225

225-
rp:=serverTailnet.ReverseProxy(u,u,a.id)
226+
rp:=serverTailnet.ReverseProxy(u,u,a.id, appurl.ApplicationURL{},"")
226227

227228
fori:=0;i<5;i++ {
228229
rw:=httptest.NewRecorder()
@@ -279,7 +280,7 @@ func TestServerTailnet_ReverseProxy(t *testing.T) {
279280
require.NoError(t,err)
280281

281282
fori,ag:=rangeagents {
282-
rp:=serverTailnet.ReverseProxy(u,u,ag.id)
283+
rp:=serverTailnet.ReverseProxy(u,u,ag.id, appurl.ApplicationURL{},"")
283284

284285
rw:=httptest.NewRecorder()
285286
req:=httptest.NewRequest(
@@ -317,7 +318,7 @@ func TestServerTailnet_ReverseProxy(t *testing.T) {
317318
uri,err:=url.Parse(s.URL)
318319
require.NoError(t,err)
319320

320-
rp:=serverTailnet.ReverseProxy(uri,uri,a.id)
321+
rp:=serverTailnet.ReverseProxy(uri,uri,a.id, appurl.ApplicationURL{},"")
321322

322323
rw:=httptest.NewRecorder()
323324
req:=httptest.NewRequest(
@@ -347,7 +348,7 @@ func TestServerTailnet_ReverseProxy(t *testing.T) {
347348
u,err:=url.Parse(fmt.Sprintf("http://127.0.0.1:%d",workspacesdk.AgentHTTPAPIServerPort))
348349
require.NoError(t,err)
349350

350-
rp:=serverTailnet.ReverseProxy(u,u,a.id)
351+
rp:=serverTailnet.ReverseProxy(u,u,a.id, appurl.ApplicationURL{},"")
351352

352353
rw:=httptest.NewRecorder()
353354
req:=httptest.NewRequest(

‎coderd/workspaceapps/appurl/appurl.go‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"net"
66
"net/url"
77
"regexp"
8+
"strconv"
89
"strings"
910

1011
"golang.org/x/xerrors"
@@ -83,6 +84,55 @@ func (a ApplicationURL) Path() string {
8384
returnfmt.Sprintf("/@%s/%s.%s/apps/%s",a.Username,a.WorkspaceName,a.AgentName,a.AppSlugOrPort)
8485
}
8586

87+
// PortInfo returns the port, protocol, and whether the AppSlugOrPort is a port or not.
88+
func (aApplicationURL)PortInfo() (uint,string,bool) {
89+
var (
90+
portuint64
91+
protocolstring
92+
isPortbool
93+
errerror
94+
)
95+
96+
ifstrings.HasSuffix(a.AppSlugOrPort,"s") {
97+
trimmed:=strings.TrimSuffix(a.AppSlugOrPort,"s")
98+
port,err=strconv.ParseUint(trimmed,10,16)
99+
iferr==nil {
100+
protocol="https"
101+
isPort=true
102+
}
103+
}else {
104+
port,err=strconv.ParseUint(a.AppSlugOrPort,10,16)
105+
iferr==nil {
106+
protocol="http"
107+
isPort=true
108+
}
109+
}
110+
111+
returnuint(port),protocol,isPort
112+
}
113+
114+
func (a*ApplicationURL)ChangePortProtocol(targetstring)ApplicationURL {
115+
newAppURL:=*a
116+
port,protocol,isPort:=a.PortInfo()
117+
if!isPort {
118+
returnnewAppURL
119+
}
120+
121+
iftarget==protocol {
122+
returnnewAppURL
123+
}
124+
125+
iftarget=="https" {
126+
newAppURL.AppSlugOrPort=fmt.Sprintf("%ds",port)
127+
}
128+
129+
iftarget=="http" {
130+
newAppURL.AppSlugOrPort=fmt.Sprintf("%d",port)
131+
}
132+
133+
returnnewAppURL
134+
}
135+
86136
// ParseSubdomainAppURL parses an ApplicationURL from the given subdomain. If
87137
// the subdomain is not a valid application URL hostname, returns a non-nil
88138
// error. If the hostname is not a subdomain of the given base hostname, returns

‎coderd/workspaceapps/appurl/appurl_test.go‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,16 @@ func TestParseSubdomainAppURL(t *testing.T) {
124124
Username:"user",
125125
},
126126
},
127+
{
128+
Name:"Port--Agent--Workspace--User",
129+
Subdomain:"8080s--agent--workspace--user",
130+
Expected: appurl.ApplicationURL{
131+
AppSlugOrPort:"8080s",
132+
AgentName:"agent",
133+
WorkspaceName:"workspace",
134+
Username:"user",
135+
},
136+
},
127137
{
128138
Name:"HyphenatedNames",
129139
Subdomain:"app-slug--agent-name--workspace-name--user-name",

‎coderd/workspaceapps/proxy.go‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ var nonCanonicalHeaders = map[string]string{
6666
typeAgentProviderinterface {
6767
// ReverseProxy returns an httputil.ReverseProxy for proxying HTTP requests
6868
// to the specified agent.
69-
ReverseProxy(targetURL,dashboardURL*url.URL,agentID uuid.UUID)*httputil.ReverseProxy
69+
ReverseProxy(targetURL,dashboardURL*url.URL,agentID uuid.UUID,app appurl.ApplicationURL,wildcardHoststring)*httputil.ReverseProxy
7070

7171
// AgentConn returns a new connection to the specified agent.
7272
AgentConn(ctx context.Context,agentID uuid.UUID) (_*workspacesdk.AgentConn,releasefunc(),_error)
@@ -314,7 +314,7 @@ func (s *Server) workspaceAppsProxyPath(rw http.ResponseWriter, r *http.Request)
314314
return
315315
}
316316

317-
s.proxyWorkspaceApp(rw,r,*token,chiPath)
317+
s.proxyWorkspaceApp(rw,r,*token,chiPath, appurl.ApplicationURL{})
318318
}
319319

320320
// HandleSubdomain handles subdomain-based application proxy requests (aka.
@@ -417,7 +417,7 @@ func (s *Server) HandleSubdomain(middlewares ...func(http.Handler) http.Handler)
417417
if!ok {
418418
return
419419
}
420-
s.proxyWorkspaceApp(rw,r,*token,r.URL.Path)
420+
s.proxyWorkspaceApp(rw,r,*token,r.URL.Path,app)
421421
})).ServeHTTP(rw,r.WithContext(ctx))
422422
})
423423
}
@@ -476,7 +476,7 @@ func (s *Server) parseHostname(rw http.ResponseWriter, r *http.Request, next htt
476476
returnapp,true
477477
}
478478

479-
func (s*Server)proxyWorkspaceApp(rw http.ResponseWriter,r*http.Request,appTokenSignedToken,pathstring) {
479+
func (s*Server)proxyWorkspaceApp(rw http.ResponseWriter,r*http.Request,appTokenSignedToken,pathstring,app appurl.ApplicationURL) {
480480
ctx:=r.Context()
481481

482482
// Filter IP headers from untrusted origins.
@@ -545,8 +545,12 @@ func (s *Server) proxyWorkspaceApp(rw http.ResponseWriter, r *http.Request, appT
545545

546546
r.URL.Path=path
547547
appURL.RawQuery=""
548+
_,protocol,isPort:=app.PortInfo()
549+
ifisPort {
550+
appURL.Scheme=protocol
551+
}
548552

549-
proxy:=s.AgentProvider.ReverseProxy(appURL,s.DashboardURL,appToken.AgentID)
553+
proxy:=s.AgentProvider.ReverseProxy(appURL,s.DashboardURL,appToken.AgentID,app,s.Hostname)
550554

551555
proxy.ModifyResponse=func(r*http.Response)error {
552556
r.Header.Del(httpmw.AccessControlAllowOriginHeader)

‎site/site.go‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -786,12 +786,15 @@ func extractBin(dest string, r io.Reader) (numExtracted int, err error) {
786786
typeErrorPageDatastruct {
787787
Statusint
788788
// HideStatus will remove the status code from the page.
789-
HideStatusbool
790-
Titlestring
791-
Descriptionstring
792-
RetryEnabledbool
793-
DashboardURLstring
794-
Warnings []string
789+
HideStatusbool
790+
Titlestring
791+
Descriptionstring
792+
RetryEnabledbool
793+
DashboardURLstring
794+
Warnings []string
795+
AdditionalInfostring
796+
AdditionalButtonLinkstring
797+
AdditionalButtonTextstring
795798

796799
RenderDescriptionMarkdownbool
797800
}

‎site/static/error.html‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
.container {
3434
--side-padding:24px;
3535
width:100%;
36-
max-width:calc(320px+var(--side-padding)*2);
36+
max-width:calc(500px+var(--side-padding)*2);
3737
padding:0var(--side-padding);
3838
text-align: center;
3939
}
@@ -170,6 +170,9 @@ <h1>
170170
{{- if .Error.RenderDescriptionMarkdown }} {{ .ErrorDescriptionHTML }} {{
171171
else }}
172172
<p>{{ .Error.Description }}</p>
173+
{{ end }} {{- if .Error.AdditionalInfo }}
174+
<br/>
175+
<p>{{ .Error.AdditionalInfo }}</p>
173176
{{ end }} {{- if .Error.Warnings }}
174177
<divclass="warning">
175178
<divclass="warning-title">
@@ -195,7 +198,11 @@ <h3>Warnings</h3>
195198
</div>
196199
{{ end }}
197200
<divclass="button-group">
198-
{{- if .Error.RetryEnabled }}
201+
{{- if and .Error.AdditionalButtonText .Error.AdditionalButtonLink }}
202+
<ahref="{{ .Error.AdditionalButtonLink }}"
203+
>{{ .Error.AdditionalButtonText }}</a
204+
>
205+
{{ end }} {{- if .Error.RetryEnabled }}
199206
<buttononclick="window.location.reload()">Retry</button>
200207
{{ end }}
201208
<ahref="{{ .Error.DashboardURL }}">Back to site</a>

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp