mkenney/go-chrome

Allow client to run across a network?

Closed this issue ยท 14 comments

Firstly, really happy w/ this library, tried the others and they've all failed me -- clean, great coverage and it works! ๐Ÿ‘

Question/suggestion:

Can we use this client across a networK? It looks like you need to run the client on the same box with the chrome binary so that it can create/open a process directly on it.

Other clients simply open a connection on the remote-debugging address and port...

A use-case for this is for example, having a (docker) container run your chrome install/process and your go-app on another and they're able to communicate across the (docker) network.

This would allow us to isolate the running chrome process and our app etc.

Hi! It's great to hear that the library is working for you! I had the same experience which is why I started this project.

That's not a use-case I've tried but it should work, the drawback being that there isn't a struct in the package for managing those connections (the Chrome struct does that here) so you have to manage it yourself, but I would think you can create a socket connection manually something like this:

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

	"github.com/mkenney/go-chrome/tot/socket"
	chrome "github.com/mkenney/go-chrome/tot"
)

func main() {
	host := "http://chromium:9222"
	tabData := chrome.TabData{}

	// open a new tab
	client := &http.Client{}
	resp, err := client.Do(host+"/json/new?"+url.QueryEscape(uri))

	// store the tab metadata
	respData = []byte{}
	resp.Body.Read(respData)
	resp.Body.Close()
	json.Unmarshal(respData, &tabData)

	// open a socket connection that controls the opened tab
	socURL, _ := url.Parse(tabData.WebSocketDebuggerURL)
	soc := socket.New(socURL)

	// Enable Page events for this tab.
	if enableResult := <-soc.Page().Enable(); nil != enableResult.Err {
		panic(enableResult.Err)
	}
}

I haven't tested that at all, if you have trouble let me know. We can add a struct to make that easier if it works out.

Following up after looking at your other issue, closing it would be

soc.Stop()
resp, err = client.Do(host+"/json/close?"+tabData.ID)

That code wasn't very good :)

This works though:

package main

import (
	"encoding/json"
	"io/ioutil"
	"net/http"
	"net/url"

	log "github.com/bdlm/log"
	chrome "github.com/mkenney/go-chrome/tot"
	"github.com/mkenney/go-chrome/tot/socket"
)

func main() {
	host := "http://0.0.0.0:9222"
	tabData := chrome.TabData{}

	// open a new tab
	client := &http.Client{}
	resp, err := client.Get(host + "/json/new?" + url.QueryEscape("https://www.google.com"))
	if nil != err {
		log.Errorf("%v", err)
	}

	// store the tab metadata
	defer resp.Body.Close()
	respData, err := ioutil.ReadAll(resp.Body)
	if nil != err {
		log.Errorf("%v", err)
	}
	json.Unmarshal(respData, &tabData)

	// open a socket connection that controls the opened tab
	socURL, _ := url.Parse(tabData.WebSocketDebuggerURL)
	soc := socket.New(socURL)

	// Enable Page events for this tab.
	if enableResult := <-soc.Page().Enable(); nil != enableResult.Err {
		log.Error(enableResult.Err)
	}

	// stop the socket and close the tab
	soc.Stop()
	resp, err = client.Get(host + "/json/close?" + tabData.ID)
	if nil != err {
		log.Errorf("%v", err)
	}
}

with this docker-compose:

version: '3.5'
services:
    chrome:
        container_name: chrome
        hostname: chrome
        image: mkenney/chromium-headless:latest
        ports:
            - 9222:9222
        entrypoint: sh
        command:
            - "-cexu"
            - "/usr/bin/google-chrome --addr=localhost --port=9222 --remote-debugging-port=9222 --remote-debugging-address=0.0.0.0 --disable-extensions --disable-gpu --headless --hide-scrollbars --no-first-run --no-sandbox"

An error may show up in the log, it's complaining that there isn't an event handler for the Page.loadEventFired event, that shouldn't be an error (most events won't be handled) and the message should be more helpful. I'll look into updating that.

ERRO[0000] socket #1 - 0000: An unknown error occurred

Thank you so much for the detailed reply and code example!

I'll be giving this a go ๐Ÿ‘

Great, please let me know if that solves your use-case. I think it probably makes sense for the Chrome struct to have a "remote" mode that otherwise behaves the same as if Chrome were running locally. I'll start thinking about how to integrate that.

I'm going to assume this is working for you and close this out. I'll also create a card for adding a "remote" mode to the package.

Hi @ozburo,

So I was playing with this a bit and realized it already does this and I just wasn't paying attention to what I was doing :)

You can setup the Chrome struct the same as usual, but most of the CLI args aren't necessary, just the address info:

	// Define a chrome instance with remote debugging enabled.
	browser := chrome.New(
		&chrome.Flags{}
			"remote-debugging-address": "0.0.0.0",
			"remote-debugging-port": 9222,
			"addr": "0.0.0.0",
			"port": 9222,
		},
		"",
		"",
		"",
		"",
	)

and then just don't call browser.Launch()...

	// Start the chrome process.
	//if err := browser.Launch(); nil != err {
	//	panic(err)
	//}
	tab, err := browser.NewTab("https://www.google.com/")

Let me know if that accomplishes what you need, thanks!

@mkenney You're a diamond! ๐Ÿ’Ž

Yes, that totally works and solves my issue -- IIRC my issue was that I didn't start up my Chrome container with the right entrypoint/commands -- I thought that it was necessary for the client to do that; but I can see by your example that you don't need to use all the extra options for new.Chrome if you fire up your Chrome container independently.

Using your docker-compose example w/ the correct command works great, thanks again! ๐Ÿ‘

Great!

vkorn commented

@mkenney

I think you need to use addr flag as well, in order to connect to remote instance, otherwise Query goes to localhost. With addr everything works like a charm.

@vkorn thanks! I updated my example.

vkorn commented

@mkenney and me again, lol. Actually you have to specify port as well. Otherwise initial Query goes to default 9222.

@vkorn haha, thanks! I generally just use the defaults so I didn't notice. I updated the example ๐Ÿ˜„