-
Notifications
You must be signed in to change notification settings - Fork 116
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: automatically translate failed requests to localhost to docker.…
…host.internal (#224) Co-authored-by: Dustin Deus <deusdustin@gmail.com>
- Loading branch information
Showing
8 changed files
with
298 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
// Package docker implements helper functions we use while running under Docker. | ||
// This should only be used for development purposes. | ||
package docker | ||
|
||
import ( | ||
"os" | ||
) | ||
|
||
const ( | ||
// dockerInternalHost is the hostname used by docker to access the host machine | ||
// with bridge networking. We use it for automatic fallbacks when requests to localhost fail. | ||
dockerInternalHost = "host.docker.internal" | ||
) | ||
|
||
func Inside() bool { | ||
// Check if we are running inside docker by | ||
// testing by checking if /.dockerenv exists | ||
// | ||
// This is not documented by Docker themselves, but it's the only | ||
// method that has been working reliably for several years. | ||
st, err := os.Stat("/.dockerenv") | ||
return err == nil && !st.IsDir() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
package docker | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"net" | ||
"net/http" | ||
"net/http/httptest" | ||
"strings" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func findLocalNonLocalhostInterface() (net.IP, error) { | ||
ifaces, err := net.Interfaces() | ||
if err != nil { | ||
return nil, fmt.Errorf("could not list network interfaces: %w", err) | ||
} | ||
for _, iface := range ifaces { | ||
addrs, err := iface.Addrs() | ||
if err != nil { | ||
continue | ||
} | ||
for _, addr := range addrs { | ||
switch x := addr.(type) { | ||
case *net.IPNet: | ||
if x.IP.IsPrivate() && !x.IP.IsLoopback() { | ||
return x.IP, nil | ||
} | ||
} | ||
} | ||
} | ||
return nil, errors.New("could not find a suitable IP address") | ||
} | ||
|
||
func TestLocalhostFallbackRoundTripper(t *testing.T) { | ||
t.Parallel() | ||
|
||
localIP, err := findLocalNonLocalhostInterface() | ||
if err != nil { | ||
// If we can't find a suitable address to run the test, skip it | ||
t.Skip(err) | ||
} | ||
t.Log("using local IP", localIP) | ||
// Find a random free TCP port | ||
l, err := net.Listen("tcp", fmt.Sprintf("[%s]:0", localIP.String())) | ||
require.NoError(t, err) | ||
port := l.Addr().(*net.TCPAddr).Port | ||
|
||
server := httptest.NewUnstartedServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
data, err := io.ReadAll(r.Body) | ||
require.NoError(t, err) | ||
response := map[string]any{ | ||
"method": r.Method, | ||
"host": r.Host, | ||
"path": r.URL.Path, | ||
"body": string(data), | ||
} | ||
resp, err := json.Marshal(response) | ||
require.NoError(t, err) | ||
w.Write(resp) | ||
})) | ||
server.Listener = l | ||
server.Start() | ||
t.Cleanup(server.Close) | ||
|
||
transport := &localhostFallbackRoundTripper{ | ||
transport: http.DefaultTransport, | ||
targetHost: localIP.String(), | ||
} | ||
client := http.Client{ | ||
Transport: transport, | ||
} | ||
|
||
t.Run("GET", func(t *testing.T) { | ||
t.Parallel() | ||
resp, err := client.Get(fmt.Sprintf("http://localhost:%d/hello", port)) | ||
require.NoError(t, err) | ||
defer resp.Body.Close() | ||
data, err := io.ReadAll(resp.Body) | ||
require.NoError(t, err) | ||
var response map[string]any | ||
err = json.Unmarshal(data, &response) | ||
require.NoError(t, err) | ||
assert.Equal(t, "GET", response["method"]) | ||
assert.Equal(t, fmt.Sprintf("%s:%d", localIP.String(), port), response["host"]) | ||
assert.Equal(t, "", response["body"]) | ||
assert.Equal(t, "/hello", response["path"]) | ||
}) | ||
|
||
t.Run("POST", func(t *testing.T) { | ||
t.Parallel() | ||
const hello = "hello world" | ||
resp, err := client.Post(fmt.Sprintf("http://localhost:%d", port), "text/plain", strings.NewReader(hello)) | ||
require.NoError(t, err) | ||
defer resp.Body.Close() | ||
data, err := io.ReadAll(resp.Body) | ||
require.NoError(t, err) | ||
var response map[string]any | ||
err = json.Unmarshal(data, &response) | ||
require.NoError(t, err) | ||
assert.Equal(t, "POST", response["method"]) | ||
assert.Equal(t, hello, response["body"]) | ||
assert.Equal(t, "/", response["path"]) | ||
}) | ||
} |
Oops, something went wrong.