Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

make NewRemoteAllocator accept url without devtools/browser/... #817

Merged
merged 5 commits into from May 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 11 additions & 1 deletion allocate.go
Expand Up @@ -478,10 +478,20 @@ func CombinedOutput(w io.Writer) ExecAllocatorOption {
// NewRemoteAllocator creates a new context set up with a RemoteAllocator,
// suitable for use with NewContext. The url should point to the browser's
// websocket address, such as "ws://127.0.0.1:$PORT/devtools/browser/...".
// If the url does not contain "/devtools/browser/", it will try to detect
// the correct one by sending a request to "http://$HOST:$PORT/json/version".
//
// The url with the following formats are accepted:
// * ws://127.0.0.1:9222/
// * http://127.0.0.1:9222/
//
// But "ws://127.0.0.1:9222/devtools/browser/" are not accepted.
// Because it contains "/devtools/browser/" and will be considered
// as a valid websocket debugger URL.
func NewRemoteAllocator(parent context.Context, url string) (context.Context, context.CancelFunc) {
ctx, cancel := context.WithCancel(parent)
c := &Context{Allocator: &RemoteAllocator{
wsURL: url,
wsURL: detectURL(url),
}}
ctx = context.WithValue(ctx, contextKey{}, c)
return ctx, cancel
Expand Down
57 changes: 54 additions & 3 deletions allocate_test.go
Expand Up @@ -5,8 +5,10 @@ import (
"context"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"net/url"
"os"
"os/exec"
"strings"
Expand Down Expand Up @@ -122,6 +124,55 @@ func TestSkipNewContext(t *testing.T) {
func TestRemoteAllocator(t *testing.T) {
t.Parallel()

tests := []struct {
name string
modifyURL func(wsURL string) string
}{
{
name: "original wsURL",
modifyURL: func(wsURL string) string { return wsURL },
},
{
name: "detect from ws",
modifyURL: func(wsURL string) string {
return wsURL[0:strings.Index(wsURL, "devtools")]
},
},
{
name: "detect from http",
modifyURL: func(wsURL string) string {
return "http" + wsURL[2:strings.Index(wsURL, "devtools")]
},
},
{
name: "hostname",
modifyURL: func(wsURL string) string {
h, err := os.Hostname()
if err != nil {
t.Fatal(err)
}
u, err := url.Parse(wsURL)
if err != nil {
t.Fatal(err)
}
_, post, err := net.SplitHostPort(u.Host)
if err != nil {
t.Fatal(err)
}
u.Host = net.JoinHostPort(h, post)
u.Path = "/"
return u.String()
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
testRemoteAllocator(t, tt.modifyURL)
})
}
}

func testRemoteAllocator(t *testing.T, modifyURL func(wsURL string) string) {
tempDir, err := ioutil.TempDir("", "chromedp-runner")
if err != nil {
t.Fatal(err)
Expand All @@ -140,6 +191,7 @@ func TestRemoteAllocator(t *testing.T) {

// TODO: perhaps deduplicate this code with ExecAllocator
"--user-data-dir="+tempDir,
"--remote-debugging-address=0.0.0.0",
"--remote-debugging-port=0",
"about:blank",
)
Expand All @@ -156,7 +208,7 @@ func TestRemoteAllocator(t *testing.T) {
if err != nil {
t.Fatal(err)
}
allocCtx, allocCancel := NewRemoteAllocator(context.Background(), wsURL)
allocCtx, allocCancel := NewRemoteAllocator(context.Background(), modifyURL(wsURL))
defer allocCancel()

taskCtx, taskCancel := NewContext(allocCtx,
Expand Down Expand Up @@ -356,7 +408,7 @@ func TestModifyCmdFunc(t *testing.T) {
allocCtx, cancel := NewExecAllocator(context.Background(),
append([]ExecAllocatorOption{
ModifyCmdFunc(func(cmd *exec.Cmd) {
cmd.Env = append(cmd.Env, "TZ=" + tz)
cmd.Env = append(cmd.Env, "TZ="+tz)
}),
}, allocOpts...)...)
defer cancel()
Expand All @@ -375,4 +427,3 @@ func TestModifyCmdFunc(t *testing.T) {
t.Fatalf("got %s, want %s", ret, tz)
}
}

44 changes: 44 additions & 0 deletions util.go
@@ -1,8 +1,11 @@
package chromedp

import (
"encoding/json"
"net"
"net/http"
"net/url"
"strings"

"github.com/chromedp/cdproto"
"github.com/chromedp/cdproto/cdp"
Expand All @@ -29,6 +32,47 @@ func forceIP(urlstr string) string {
return u.String()
}

// detectURL detects the websocket debugger URL if the provided URL is not a
// valid websocket debugger URL.
//
// A valid websocket debugger URL is something like:
// ws://127.0.0.1:9222/devtools/browser/...
// The original URL with the following formats are accepted:
// * ws://127.0.0.1:9222/
// * http://127.0.0.1:9222/
func detectURL(urlstr string) string {
if strings.Contains(urlstr, "/devtools/browser/") {
return urlstr
}

// replace the scheme and path to construct the URL like:
// http://127.0.0.1:9222/json/version
u, err := url.Parse(urlstr)
if err != nil {
return urlstr
}
u.Scheme = "http"
u.Path = "/json/version"

// to get "webSocketDebuggerUrl" in the response
resp, err := http.Get(forceIP(u.String()))
if err != nil {
return urlstr
}
defer resp.Body.Close()

var result map[string]interface{}
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
return urlstr
}
// the browser will construct the debugger URL using the "host" header of the /json/version request.
// for example, run headless-shell in a container: docker run -d -p 9000:9222 chromedp/headless-shell:latest
// then: curl http://127.0.0.1:9000/json/version
// and the debugger URL will be something like: ws://127.0.0.1:9000/devtools/browser/...
wsURL := result["webSocketDebuggerUrl"].(string)
return wsURL
}

func runListeners(list []cancelableListener, ev interface{}) []cancelableListener {
for i := 0; i < len(list); {
listener := list[i]
Expand Down