Skip to content

Commit

Permalink
Merge pull request #270 from vania-pooh/master
Browse files Browse the repository at this point in the history
Added ability to proxy /devtools/ API (fixes #268)
  • Loading branch information
aandryashin authored Feb 18, 2019
2 parents dd319cc + 3885f11 commit 8d36ed3
Show file tree
Hide file tree
Showing 6 changed files with 77 additions and 13 deletions.
6 changes: 6 additions & 0 deletions docs/devtools.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
== Proxying Browser Developer Tools

Similarly to proxying VNC traffic Ggr is able to proxy browser developer tools traffic in **running** Selenium session. To access developer tools - just use the following web socket URL:

ws://ggr-host.example.com:4444/devtools/<session-id>

1 change: 1 addition & 0 deletions docs/index.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ include::quota-reload.adoc[leveloffset=+1]
include::video.adoc[leveloffset=+1]
include::logs.adoc[leveloffset=+1]
include::download.adoc[leveloffset=+1]
include::devtools.adoc[leveloffset=+1]
include::tls.adoc[leveloffset=+1]
include::how-it-works.adoc[leveloffset=+1]
include::multiple-instances.adoc[leveloffset=+1]
Expand Down
7 changes: 4 additions & 3 deletions docs/log-files.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,16 @@ The following statuses are available:
| SESSION_FAILED | Session attempt on specified host failed
| SESSION_NOT_CREATED | Attempts to create a new session on all hosts failed. An error was returned to user.
| SHUTTING_DOWN | Server is shutting down and waiting graceful shutdown timeout for currently proxied requests to finish
| UNKNOWN_DEVTOOLS_HOST | Requested to proxy devtools to host not present in quota
| UNKNOWN_DOWNLOAD_HOST | Requested to proxy downloaded file to host not present in quota
| UNKNOWN_LOG_HOST | Requested to proxy log to host not present in quota
| UNKNOWN_VNC_HOST | Requested to proxy VNC to host not present in quota
| UNKNOWN_VIDEO_HOST | Requested to proxy video to host not present in quota
| UNSUPPORTED_BROWSER | Requested browser name and version is not present in quota
| UNSUPPORTED_HOST_VNC_SCHEME | Invalid URL protocol specified for host in quota VNC configuration (should be vnc:// or ws://)
| VNC_CLIENT_DISCONNECTED | Client disconnected from VNC API
| VNC_ERROR | An error occurred when trying to proxy VNC traffic
| VNC_SESSION_CLOSED | Client closed VNC session
| WS_CLIENT_DISCONNECTED | Client disconnected from websocket API
| WS_ERROR | An error occurred when trying to proxy websocket traffic
| WS_SESSION_CLOSED | Client closed websocket session
|===

=== Custom Labels in Log File
Expand Down
2 changes: 1 addition & 1 deletion docs/tls.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ server {
add_header Access-Control-Allow-Credentials "true";
}
location ~ ^/vnc/ {
location ~ ^/(vnc|devtools)/ {
proxy_pass http://ggr;
proxy_http_version 1.1;
proxy_read_timeout 950s;
Expand Down
36 changes: 31 additions & 5 deletions proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"net/http/httputil"
"net/url"
"reflect"
"strconv"
"strings"
"sync"
"sync/atomic"
Expand All @@ -39,7 +40,7 @@ const (
)

var paths = struct {
Ping, Status, Err, Host, Quota, Route, Proxy, VNC, Video, Logs, Download, Clipboard string
Ping, Status, Err, Host, Quota, Route, Proxy, VNC, Video, Logs, Download, Clipboard, Devtools string
}{
Ping: "/ping",
Status: "/wd/hub/status",
Expand All @@ -53,6 +54,7 @@ var paths = struct {
Logs: "/logs/",
Download: "/download/",
Clipboard: "/clipboard/",
Devtools: "/devtools/",
}

var keys = struct {
Expand Down Expand Up @@ -671,20 +673,43 @@ func proxyWebSockets(id uint64, wsconn *websocket.Conn, sessionID string, host s
}

func proxyConn(id uint64, wsconn *websocket.Conn, conn net.Conn, err error, sessionID string, address string) {
log.Printf("[%d] [-] [PROXYING_TO_VNC] [-] [-] [-] [%s] [%s] [-] [-]\n", id, address, sessionID)
log.Printf("[%d] [-] [PROXYING_TO_WS] [-] [-] [-] [%s] [%s] [-] [-]", id, address, sessionID)
if err != nil {
log.Printf("[%d] [-] [VNC_ERROR] [-] [-] [-] [%s] [%s] [-] [%v]\n", id, sessionID, address, err)
log.Printf("[%d] [-] [WS_ERROR] [-] [-] [-] [%s] [%s] [-] [%v]", id, sessionID, address, err)
return
}
defer conn.Close()
wsconn.PayloadType = websocket.BinaryFrame
go func() {
io.Copy(wsconn, conn)
wsconn.Close()
log.Printf("[%d] [-] [VNC_SESSION_CLOSED] [-] [-] [-] [%s] [%s] [-] [-]\n", id, address, sessionID)
log.Printf("[%d] [-] [WS_SESSION_CLOSED] [-] [-] [-] [%s] [%s] [-] [-]", id, address, sessionID)
}()
io.Copy(conn, wsconn)
log.Printf("[%d] [-] [VNC_CLIENT_DISCONNECTED] [-] [-] [-] [%s] [%s] [-] [-]\n", id, address, sessionID)
log.Printf("[%d] [-] [WS_CLIENT_DISCONNECTED] [-] [-] [-] [%s] [%s] [-] [-]", id, address, sessionID)
}

func devtools(wsconn *websocket.Conn) {
defer wsconn.Close()
confLock.RLock()
defer confLock.RUnlock()

id := serial()
head := len(paths.Devtools)
tail := head + md5SumLength
path := wsconn.Request().URL.Path
if len(path) < tail {
log.Printf("[%d] [-] [INVALID_DEVTOOLS_REQUEST_URL] [-] [-] [%s] [-] [-] [-] [-]", id, path)
return
}
sum := path[head:tail]
h, ok := routes[sum]
if ok {
sessionID := strings.Split(path, "/")[2][md5SumLength:]
proxyWebSockets(id, wsconn, sessionID, h.Name, strconv.Itoa(h.Port), "/devtools")
} else {
log.Printf("[%d] [-] [UNKNOWN_DEVTOOLS_HOST] [-] [-] [-] [-] [%s] [-] [-]", id, sum)
}
}

func video(w http.ResponseWriter, r *http.Request) {
Expand Down Expand Up @@ -759,5 +784,6 @@ func mux() http.Handler {
mux.HandleFunc(paths.Logs, WithSuitableAuthentication(authenticator, logs))
mux.HandleFunc(paths.Download, WithSuitableAuthentication(authenticator, download))
mux.HandleFunc(paths.Clipboard, WithSuitableAuthentication(authenticator, clipboard))
mux.Handle(paths.Devtools, websocket.Handler(devtools))
return mux
}
38 changes: 34 additions & 4 deletions proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,14 +249,14 @@ func TestProxyScreenVNCProtocol(t *testing.T) {
}}}}
updateQuota(user, browsers)

testDataReceived(vncHost, testData, t)
testDataReceived(vncHost, "vnc", testData, t)
}

func testDataReceived(host Host, correctData string, t *testing.T) {
func testDataReceived(host Host, api string, correctData string, t *testing.T) {
sessionID := host.Sum() + "123"

origin := "http://localhost/"
u := fmt.Sprintf("ws://%s/vnc/%s", srv.Listener.Addr(), sessionID)
u := fmt.Sprintf("ws://%s/%s/%s", srv.Listener.Addr(), api, sessionID)
ws, err := websocket.Dial(u, "", origin)
AssertThat(t, err, Is{nil})

Expand Down Expand Up @@ -308,10 +308,40 @@ func TestProxyScreenWebSocketsProtocol(t *testing.T) {
}}}}
updateQuota(user, browsers)

testDataReceived(wsHost, testData, t)
testDataReceived(wsHost, "vnc", testData, t)

}

func TestProxyDevtools(t *testing.T) {
test.Lock()
defer test.Unlock()

const testData = "devtools-data"
mux := http.NewServeMux()
mux.Handle("/devtools/123", websocket.Handler(func(wsconn *websocket.Conn) {
wsconn.Write([]byte(testData))
}))

wsServer := httptest.NewServer(mux)
defer wsServer.Close()

h, p, _ := net.SplitHostPort(wsServer.Listener.Addr().String())
intPort, _ := strconv.Atoi(p)
wsHost := Host{Name: h, Port: intPort, Count: 1}

browsers := Browsers{Browsers: []Browser{
{Name: "browser", DefaultVersion: "1.0", Versions: []Version{
{Number: "1.0", Regions: []Region{
{Hosts: Hosts{
wsHost,
}},
}},
}}}}
updateQuota(user, browsers)

testDataReceived(wsHost, "devtools", testData, t)
}

func TestProxyVideoFileWithoutAuth(t *testing.T) {
rsp, err := http.Get(gridrouter("/video/123"))

Expand Down

0 comments on commit 8d36ed3

Please sign in to comment.