diff --git a/internal/config/config.go b/internal/config/config.go index 3ee9d0b9783fe72dbc9960040b107f9081a1e857..fec1d68771aab1426aac01dda561858701fb52ce 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -10,6 +10,7 @@ type Config struct { Version string DocumentRoot string DevelopmentMode bool + WebpackAddr string Socket string ProxyHeadersTimeout time.Duration APILimit uint diff --git a/internal/upstream/routes.go b/internal/upstream/routes.go index 5c5232afb0c30bc65fa36592bb433e47ce8716dc..be1665fd23c40f2760220dde8deb4514bceed445 100644 --- a/internal/upstream/routes.go +++ b/internal/upstream/routes.go @@ -19,6 +19,7 @@ import ( "gitlab.com/gitlab-org/gitlab-workhorse/internal/staticpages" "gitlab.com/gitlab-org/gitlab-workhorse/internal/terminal" "gitlab.com/gitlab-org/gitlab-workhorse/internal/upload" + "gitlab.com/gitlab-org/gitlab-workhorse/internal/webpack" ) type matcherFunc func(*http.Request) bool @@ -139,6 +140,18 @@ func (u *Upstream) configureRoutes() { route("", apiPattern, proxy), route("", ciAPIPattern, proxy), + // In development mode, proxy /assets/webpack requests to webpack + // dev-server, otherwise serve static files from disk. + route( + "", `^/assets/webpack/`, + webpack.DevServer(u.DevelopmentMode, u.WebpackAddr, + static.ServeExisting( + u.URLPrefix, + staticpages.CacheExpireMax, + nil, + )), + ), + // Serve assets route( "", `^/assets/`, diff --git a/internal/webpack/devserver.go b/internal/webpack/devserver.go new file mode 100644 index 0000000000000000000000000000000000000000..99c3900b50d12652ed8d3bdadd0f3518ed8cbfdf --- /dev/null +++ b/internal/webpack/devserver.go @@ -0,0 +1,38 @@ +package webpack + +import ( + "fmt" + "net/http" + "net/http/httputil" + "net/url" + + "gitlab.com/gitlab-org/gitlab-workhorse/internal/helper" +) + +func DevServer(enabled bool, address string, fallbackHandler http.Handler) http.Handler { + if !enabled { + return fallbackHandler + } + + u, err := buildURL(address) + if err != nil { + panic(err) + } + + return httputil.NewSingleHostReverseProxy(u) +} + +func buildURL(address string) (*url.URL, error) { + u := helper.URLMustParse(address) + if u == nil { + return nil, fmt.Errorf("failed to parse URL in %q", address) + } + + // Hope to support unix:// in the future + if u.Scheme != "tcp" { + return nil, fmt.Errorf("invalid scheme: %v", u) + } + + u.Scheme = "http" + return u, nil +} diff --git a/internal/webpack/devserver_test.go b/internal/webpack/devserver_test.go new file mode 100644 index 0000000000000000000000000000000000000000..2976688807a51865042cf2492752175d2ed8acf0 --- /dev/null +++ b/internal/webpack/devserver_test.go @@ -0,0 +1,33 @@ +package webpack + +import ( + "testing" +) + +func TestBuildURL(t *testing.T) { + examples := []struct { + input string + ok bool + }{ + {"", false}, + {"localhost:5000", false}, + {"tcp://localhost:5000", true}, + } + + for _, ex := range examples { + u, err := buildURL(ex.input) + if ex.ok { + if err != nil { + t.Errorf("example %v: expected no error, got %v", ex, err) + } + expectedScheme := "http" + if u.Scheme != expectedScheme { + t.Errorf("example %v: expected scheme %q, got %q", ex, expectedScheme, u.Scheme) + } + } else { + if err == nil { + t.Errorf("example %v: expected error, got none", ex) + } + } + } +} diff --git a/main.go b/main.go index ce2013039f29fb13561b49901ce628163a0d1d77..b2ea90a4b27465ca0bb14b6444ef737d62369eb0 100644 --- a/main.go +++ b/main.go @@ -51,6 +51,7 @@ var apiQueueLimit = flag.Uint("apiQueueLimit", 0, "Number of API requests allowe var apiQueueTimeout = flag.Duration("apiQueueDuration", queueing.DefaultTimeout, "Maximum queueing duration of requests") var logFile = flag.String("logFile", "", "Log file to be used") var prometheusListenAddr = flag.String("prometheusListenAddr", "", "Prometheus listening address, e.g. ':9100'") +var webpackAddr = flag.String("webpackAddr", "", "(development mode only) Network address of Webpack dev server, e.g. 'tcp://localhost:5000'") func main() { flag.Usage = func() { @@ -115,6 +116,7 @@ func main() { Version: Version, DocumentRoot: *documentRoot, DevelopmentMode: *developmentMode, + WebpackAddr: *webpackAddr, ProxyHeadersTimeout: *proxyHeadersTimeout, APILimit: *apiLimit, APIQueueLimit: *apiQueueLimit,