diff --git a/README.md b/README.md index a1609e1..0959df5 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Example with an automatic docker discovery: ## Install - for a binary distribution pick the proper file in the [release section](https://github.com/umputun/reproxy/releases) -- docker container available on [Docker Hub](https://hub.docker.com/r/umputun/reproxy) as well as on [Github Container Registry](ghcr.io/umputun/reproxy). +- docker container available on [Docker Hub](https://hub.docker.com/r/umputun/reproxy) as well as on [Github Container Registry](https://ghcr.io/umputun/reproxy). I.e. `docker pull umputun/reproxy` or `docker pull ghcr.io/umputun/reproxy`. Latest stable version has `:vX.Y.Z` tag (with `:latest` alias) and the current master has `:master` tag. diff --git a/app/discovery/provider/docker.go b/app/discovery/provider/docker.go index a660b41..c52533a 100644 --- a/app/discovery/provider/docker.go +++ b/app/discovery/provider/docker.go @@ -313,16 +313,30 @@ func NewDockerClient(host, network string) DockerClient { } func (d *dockerClient) ListContainers() ([]containerInfo, error) { - // const APIVersion = "v1.41" - // resp, err := d.client.Get(fmt.Sprintf("http://localhost/%s/containers/json", APIVersion)) - resp, err := d.client.Get("http://localhost/containers/json") + // Minimum API version that returns attached networks + // docs.docker.com/engine/api/version-history/#v122-api-changes + const APIVersion = "v1.22" + + resp, err := d.client.Get(fmt.Sprintf("http://localhost/%s/containers/json", APIVersion)) if err != nil { return nil, fmt.Errorf("failed connection to docker socket: %w", err) } defer resp.Body.Close() - var response []struct { + if resp.StatusCode != http.StatusOK { + e := struct { + Message string `json:"message"` + }{} + + if err := json.NewDecoder(resp.Body).Decode(&e); err != nil { + return nil, fmt.Errorf("failed to parse error from docker daemon: %w", err) + } + + return nil, fmt.Errorf("unexpected error from docker daemon: %s", e.Message) + } + + response := []struct { ID string `json:"Id"` Name string State string diff --git a/app/discovery/provider/docker_test.go b/app/discovery/provider/docker_test.go index 7da51cb..e42f474 100644 --- a/app/discovery/provider/docker_test.go +++ b/app/discovery/provider/docker_test.go @@ -174,7 +174,7 @@ func TestDocker_refresh(t *testing.T) { func TestDockerClient(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - require.Equal(t, "/containers/json", r.URL.Path) + require.Equal(t, `/v1.22/containers/json`, r.URL.Path) // obtained using curl --unix-socket /var/run/docker.sock http://localhost/v1.41/containers/json resp, err := ioutil.ReadFile("testdata/containers.json") @@ -201,3 +201,16 @@ func TestDockerClient(t *testing.T) { assert.Empty(t, c[1].IP) } + +func TestDockerClient_error(t *testing.T) { + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, `{"message": "bruh"}`, http.StatusInternalServerError) + })) + + defer srv.Close() + addr := fmt.Sprintf("tcp://%s", strings.TrimPrefix(srv.URL, "http://")) + + client := NewDockerClient(addr, "bridge") + _, err := client.ListContainers() + require.EqualError(t, err, "unexpected error from docker daemon: bruh") +} diff --git a/app/main_test.go b/app/main_test.go index 0ea87b5..aa65140 100644 --- a/app/main_test.go +++ b/app/main_test.go @@ -21,6 +21,7 @@ func Test_Main(t *testing.T) { port := chooseRandomUnusedPort() os.Args = []string{"test", "--static.enabled", "--static.rule=*,/svc1, https://httpbin.org/get,https://feedmaster.umputun.com/ping", + "--static.rule=*,/svc2/(.*), https://echo.umputun.com/$1,https://feedmaster.umputun.com/ping", "--dbg", "--logger.stdout", "--listen=127.0.0.1:" + strconv.Itoa(port), "--signature"} done := make(chan struct{}) @@ -62,11 +63,21 @@ func Test_Main(t *testing.T) { assert.Equal(t, 200, resp.StatusCode) body, err := ioutil.ReadAll(resp.Body) assert.NoError(t, err) - assert.Contains(t, string(body), `"Host": "127.0.0.1"`) + assert.Contains(t, string(body), `"Host": "httpbin.org"`) } { client := http.Client{Timeout: 10 * time.Second} - resp, err := client.Get(fmt.Sprintf("http://127.0.0.1:%d/bas", port)) + resp, err := client.Get(fmt.Sprintf("http://127.0.0.1:%d/svc2/test", port)) + require.NoError(t, err) + defer resp.Body.Close() + assert.Equal(t, 200, resp.StatusCode) + body, err := ioutil.ReadAll(resp.Body) + assert.NoError(t, err) + assert.Contains(t, string(body), `echo echo 123`) + } + { + client := http.Client{Timeout: 10 * time.Second} + resp, err := client.Get(fmt.Sprintf("http://127.0.0.1:%d/bad", port)) require.NoError(t, err) defer resp.Body.Close() assert.Equal(t, http.StatusBadGateway, resp.StatusCode) diff --git a/app/proxy/proxy.go b/app/proxy/proxy.go index 3b97998..5de5f18 100644 --- a/app/proxy/proxy.go +++ b/app/proxy/proxy.go @@ -151,11 +151,12 @@ func (h *Http) proxyHandler() http.HandlerFunc { Director: func(r *http.Request) { ctx := r.Context() uu := ctx.Value(contextKey("url")).(*url.URL) + r.Header.Add("X-Forwarded-Host", uu.Host) + r.Header.Set("X-Origin-Host", r.Host) r.URL.Path = uu.Path r.URL.Host = uu.Host r.URL.Scheme = uu.Scheme - r.Header.Add("X-Forwarded-Host", uu.Host) - r.Header.Add("X-Origin-Host", r.Host) + r.Host = uu.Host h.setXRealIP(r) }, Transport: &http.Transport{ diff --git a/go.mod b/go.mod index 0513c25..1fe7dda 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.16 require ( github.com/BurntSushi/toml v0.3.1 // indirect github.com/go-pkgz/lgr v0.10.4 - github.com/go-pkgz/rest v1.9.1 + github.com/go-pkgz/rest v1.9.2 github.com/gorilla/handlers v1.5.1 github.com/stretchr/testify v1.7.0 github.com/umputun/go-flags v1.5.1 diff --git a/go.sum b/go.sum index c020179..6b8d66d 100644 --- a/go.sum +++ b/go.sum @@ -8,6 +8,8 @@ github.com/go-pkgz/lgr v0.10.4 h1:l7qyFjqEZgwRgaQQSEp6tve4A3OU80VrfzpvtEX8ngw= github.com/go-pkgz/lgr v0.10.4/go.mod h1:CD0s1z6EFpIUplV067gitF77tn25JItzwHNKAPqeCF0= github.com/go-pkgz/rest v1.9.1 h1:JW876BgJJ/MOkAYRnnzpfX7xUqIav+ou1LSVTtQq/Lo= github.com/go-pkgz/rest v1.9.1/go.mod h1:wZ/dGipZUaF9to0vIQl7PwDHgWQDB0jsrFg1xnAKLDw= +github.com/go-pkgz/rest v1.9.2 h1:RyBBRXBYY6eBgTW3UGYOyT4VQPDiBBFh/tesELWsryQ= +github.com/go-pkgz/rest v1.9.2/go.mod h1:wZ/dGipZUaF9to0vIQl7PwDHgWQDB0jsrFg1xnAKLDw= github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4= github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= diff --git a/vendor/github.com/go-pkgz/rest/logger/logger.go b/vendor/github.com/go-pkgz/rest/logger/logger.go index 0efbd9a..a95d592 100644 --- a/vendor/github.com/go-pkgz/rest/logger/logger.go +++ b/vendor/github.com/go-pkgz/rest/logger/logger.go @@ -41,6 +41,7 @@ type logParts struct { remoteIP string statusCode int respSize int + host string prefix string user string @@ -108,10 +109,16 @@ func (l *Middleware) Handler(next http.Handler) http.Handler { remoteIP = l.ipFn(remoteIP) } + server := r.URL.Hostname() + if server == "" { + server = strings.Split(r.Host, ":")[0] + } + p := &logParts{ duration: t2.Sub(t1), rawURL: rawurl, method: r.Method, + host: server, remoteIP: remoteIP, statusCode: ww.status, respSize: ww.size, @@ -135,8 +142,8 @@ func (l *Middleware) formatDefault(r *http.Request, p *logParts) string { _, _ = bld.WriteString(" ") } - _, _ = bld.WriteString(fmt.Sprintf("%s - %s - %s - %d (%d) - %v", - p.method, p.rawURL, p.remoteIP, p.statusCode, p.respSize, p.duration)) + _, _ = bld.WriteString(fmt.Sprintf("%s - %s - %s - %s - %d (%d) - %v", + p.method, p.rawURL, p.host, p.remoteIP, p.statusCode, p.respSize, p.duration)) if p.user != "" { _, _ = bld.WriteString(" - ") diff --git a/vendor/modules.txt b/vendor/modules.txt index b554a01..e28187e 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -7,7 +7,7 @@ github.com/felixge/httpsnoop # github.com/go-pkgz/lgr v0.10.4 ## explicit github.com/go-pkgz/lgr -# github.com/go-pkgz/rest v1.9.1 +# github.com/go-pkgz/rest v1.9.2 ## explicit github.com/go-pkgz/rest github.com/go-pkgz/rest/logger