mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
122 lines
3.7 KiB
Go
122 lines
3.7 KiB
Go
package coderd
|
|
|
|
import (
|
|
"database/sql"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httputil"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
|
|
"github.com/coder/coder/coderd/database"
|
|
"github.com/coder/coder/coderd/httpapi"
|
|
"github.com/coder/coder/coderd/httpmw"
|
|
"github.com/coder/coder/coderd/rbac"
|
|
"github.com/coder/coder/coderd/tracing"
|
|
"github.com/coder/coder/codersdk"
|
|
"github.com/coder/coder/site"
|
|
)
|
|
|
|
// workspaceAppsProxyPath proxies requests to a workspace application
|
|
// through a relative URL path.
|
|
func (api *API) workspaceAppsProxyPath(rw http.ResponseWriter, r *http.Request) {
|
|
workspace := httpmw.WorkspaceParam(r)
|
|
agent := httpmw.WorkspaceAgentParam(r)
|
|
|
|
if !api.Authorize(r, rbac.ActionCreate, workspace.ExecutionRBAC()) {
|
|
httpapi.ResourceNotFound(rw)
|
|
return
|
|
}
|
|
|
|
app, err := api.Database.GetWorkspaceAppByAgentIDAndName(r.Context(), database.GetWorkspaceAppByAgentIDAndNameParams{
|
|
AgentID: agent.ID,
|
|
Name: chi.URLParam(r, "workspaceapp"),
|
|
})
|
|
if errors.Is(err, sql.ErrNoRows) {
|
|
httpapi.Write(rw, http.StatusNotFound, codersdk.Response{
|
|
Message: "Application not found.",
|
|
})
|
|
return
|
|
}
|
|
if err != nil {
|
|
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
|
Message: "Internal error fetching workspace application.",
|
|
Detail: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
if !app.Url.Valid {
|
|
httpapi.Write(rw, http.StatusBadRequest, codersdk.Response{
|
|
Message: fmt.Sprintf("Application %s does not have a url.", app.Name),
|
|
})
|
|
return
|
|
}
|
|
|
|
appURL, err := url.Parse(app.Url.String)
|
|
if err != nil {
|
|
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
|
Message: fmt.Sprintf("App url %q must be a valid url.", app.Url.String),
|
|
Detail: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
|
|
proxy := httputil.NewSingleHostReverseProxy(appURL)
|
|
proxy.ErrorHandler = func(w http.ResponseWriter, r *http.Request, err error) {
|
|
// This is a browser-facing route so JSON responses are not viable here.
|
|
// To pass friendly errors to the frontend, special meta tags are overridden
|
|
// in the index.html with the content passed here.
|
|
r = r.WithContext(site.WithAPIResponse(r.Context(), site.APIResponse{
|
|
StatusCode: http.StatusBadGateway,
|
|
Message: err.Error(),
|
|
}))
|
|
api.siteHandler.ServeHTTP(w, r)
|
|
}
|
|
path := chi.URLParam(r, "*")
|
|
if !strings.HasSuffix(r.URL.Path, "/") && path == "" {
|
|
// Web applications typically request paths relative to the
|
|
// root URL. This allows for routing behind a proxy or subpath.
|
|
// See https://github.com/coder/code-server/issues/241 for examples.
|
|
r.URL.Path += "/"
|
|
http.Redirect(rw, r, r.URL.String(), http.StatusTemporaryRedirect)
|
|
return
|
|
}
|
|
if r.URL.RawQuery == "" && appURL.RawQuery != "" {
|
|
// If the application defines a default set of query parameters,
|
|
// we should always respect them. The reverse proxy will merge
|
|
// query parameters for server-side requests, but sometimes
|
|
// client-side applications require the query parameters to render
|
|
// properly. With code-server, this is the "folder" param.
|
|
r.URL.RawQuery = appURL.RawQuery
|
|
http.Redirect(rw, r, r.URL.String(), http.StatusTemporaryRedirect)
|
|
return
|
|
}
|
|
r.URL.Path = path
|
|
|
|
conn, release, err := api.workspaceAgentCache.Acquire(r, agent.ID)
|
|
if err != nil {
|
|
httpapi.Write(rw, http.StatusInternalServerError, codersdk.Response{
|
|
Message: "Failed to dial workspace agent.",
|
|
Detail: err.Error(),
|
|
})
|
|
return
|
|
}
|
|
defer release()
|
|
|
|
// This strips the session token from a workspace app request.
|
|
cookieHeaders := r.Header.Values("Cookie")[:]
|
|
r.Header.Del("Cookie")
|
|
for _, cookieHeader := range cookieHeaders {
|
|
r.Header.Add("Cookie", httpapi.StripCoderCookies(cookieHeader))
|
|
}
|
|
proxy.Transport = conn.HTTPTransport()
|
|
|
|
// end span so we don't get long lived trace data
|
|
tracing.EndHTTPSpan(r, 200)
|
|
|
|
proxy.ServeHTTP(rw, r)
|
|
}
|