Skip to content
This repository has been archived by the owner on Dec 17, 2024. It is now read-only.

Commit

Permalink
Merge pull request #248 from vania-pooh/master
Browse files Browse the repository at this point in the history
Added ability to proxy clipboard API (fixes #247)
  • Loading branch information
aandryashin authored Oct 19, 2018
2 parents 08e778c + f9a61d1 commit a16380d
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 49 deletions.
2 changes: 1 addition & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func (ss *setImpl) size() int {
}

func sessionURL(h *Host) string {
return h.Route() + routePath
return h.Route() + paths.Route
}

type ggrBrowsers struct {
Expand Down
2 changes: 1 addition & 1 deletion docs/multiple-instances.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ $ curl -s http://example.com:4444/ping
.Result
[source,javascript]
----
{"uptime":"2m46.854829503s","lastReloadTime":"2017-05-12 12:33:06.322038542 +0300 MSK","numRequests":42, "version": "1.4.0"}
{"uptime":"2m46.854829503s","lastReloadTime":"2017-05-12 12:33:06.322038542 +0300 MSK","numRequests":42, "numSessions":19, "version": "1.6.3"}
----

It returns `200 OK` when Ggr operates normally. Additionally server uptime, last quota reload time and overall number of session requests from service startup are returned in JSON format.
Expand Down
106 changes: 59 additions & 47 deletions proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"net/url"
"strings"
"sync"
"sync/atomic"
"time"

"github.com/abbot/go-http-auth"
Expand All @@ -29,26 +30,30 @@ const (
)

const (
pingPath = "/ping"
statusPath = "/wd/hub/status"
errPath = "/err"
hostPath = "/host/"
quotaPath = "/quota"
routePath = "/wd/hub/session"
proxyPath = routePath + "/"
vncPath = "/vnc/"
videoPath = "/video/"
logsPath = "/logs/"
downloadPath = "/download/"
head = len(proxyPath)
md5SumLength = 32
tail = head + md5SumLength
sessPart = 4 // /wd/hub/session/{various length session}
defaultVNCPort = "5900"
vncScheme = "vnc"
wsScheme = "ws"
)

var paths = struct {
Ping, Status, Err, Host, Quota, Route, Proxy, VNC, Video, Logs, Download, Clipboard string
}{
Ping: "/ping",
Status: "/wd/hub/status",
Err: "/err",
Host: "/host/",
Quota: "/quota",
Route: "/wd/hub/session",
Proxy: "/wd/hub/session/",
VNC: "/vnc/",
Video: "/video/",
Logs: "/logs/",
Download: "/download/",
Clipboard: "/clipboard/",
}

var keys = struct {
desiredCapabilities string
w3cCapabilities string
Expand All @@ -60,16 +65,18 @@ var keys = struct {
}

var (
head = len(paths.Proxy)
tail = head + md5SumLength
httpClient = &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
}
quota = make(map[string]ggrBrowsers)
routes = make(Routes)
num uint64
numLock sync.RWMutex
confLock sync.RWMutex
quota = make(map[string]ggrBrowsers)
routes = make(Routes)
numSessions uint64
numRequests uint64
confLock sync.RWMutex
)

// Routes - an MD5 to host map
Expand Down Expand Up @@ -202,17 +209,7 @@ func reply(w http.ResponseWriter, msg map[string]interface{}, status int) {
}

func serial() uint64 {
numLock.Lock()
defer numLock.Unlock()
id := num
num++
return id
}

func getSerial() uint64 {
numLock.RLock()
defer numLock.RUnlock()
return num
return atomic.AddUint64(&numRequests, 1) - 1
}

func info(r *http.Request) (user, remote string) {
Expand Down Expand Up @@ -339,6 +336,7 @@ loop:
resp["sessionId"] = h.Sum() + sess
}
reply(w, resp, http.StatusOK)
atomic.AddUint64(&numSessions, 1)
log.Printf("[%d] [%.2fs] [SESSION_CREATED] [%s] [%s] [%s] [%s] [%s] [%d] [-]\n", id, secondsSince(start), user, remote, fmtBrowser(browser, version, labels), h.Net(), sess, count)
return
case browserFailed:
Expand Down Expand Up @@ -408,7 +406,7 @@ func proxy(r *http.Request) {
log.Printf("[%d] [-] [INVALID_URL] [-] [%s] [%s] [-] [-] [-] [-]\n", id, remote, r.URL.Path)
}
r.URL.Host = listen
r.URL.Path = errPath
r.URL.Path = paths.Err
}

func ping(w http.ResponseWriter, _ *http.Request) {
Expand All @@ -419,8 +417,15 @@ func ping(w http.ResponseWriter, _ *http.Request) {
Uptime string `json:"uptime"`
LastReloadTime string `json:"lastReloadTime"`
NumRequests uint64 `json:"numRequests"`
NumSessions uint64 `json:"numSessions"`
Version string `json:"version"`
}{time.Since(startTime).String(), lastReloadTime.String(), getSerial(), gitRevision})
}{
time.Since(startTime).String(),
lastReloadTime.String(),
atomic.LoadUint64(&numRequests),
atomic.LoadUint64(&numSessions),
gitRevision,
})
}

func status(w http.ResponseWriter, _ *http.Request) {
Expand All @@ -444,7 +449,7 @@ func host(w http.ResponseWriter, r *http.Request) {

id := serial()
user, remote := info(r)
head := len(hostPath)
head := len(paths.Host)
tail := head + md5SumLength
path := r.URL.Path
if len(path) < tail {
Expand Down Expand Up @@ -603,7 +608,7 @@ func vnc(wsconn *websocket.Conn) {
defer confLock.RUnlock()

id := serial()
head := len(vncPath)
head := len(paths.VNC)
tail := head + md5SumLength
path := wsconn.Request().URL.Path
if len(path) < tail {
Expand Down Expand Up @@ -675,23 +680,29 @@ func proxyConn(id uint64, wsconn *websocket.Conn, conn net.Conn, err error, sess
}

func video(w http.ResponseWriter, r *http.Request) {
proxyStatic(w, r, videoPath, "INVALID_VIDEO_REQUEST_URL", "PROXYING_VIDEO", "UNKNOWN_VIDEO_HOST", func(sessionId string) string {
proxyStatic(w, r, paths.Video, "INVALID_VIDEO_REQUEST_URL", "PROXYING_VIDEO", "UNKNOWN_VIDEO_HOST", func(sessionId string) string {
return fmt.Sprintf("/video/%s.mp4", sessionId)
})
}

func logs(w http.ResponseWriter, r *http.Request) {
proxyStatic(w, r, logsPath, "INVALID_LOG_REQUEST_URL", "PROXYING_LOG", "UNKNOWN_LOG_HOST", func(sessionId string) string {
proxyStatic(w, r, paths.Logs, "INVALID_LOG_REQUEST_URL", "PROXYING_LOG", "UNKNOWN_LOG_HOST", func(sessionId string) string {
return fmt.Sprintf("/logs/%s.log", sessionId)
})
}

func download(w http.ResponseWriter, r *http.Request) {
proxyStatic(w, r, downloadPath, "INVALID_DOWNLOAD_REQUEST_URL", "PROXYING_DOWNLOAD", "UNKNOWN_DOWNLOAD_HOST", func(remainder string) string {
proxyStatic(w, r, paths.Download, "INVALID_DOWNLOAD_REQUEST_URL", "PROXYING_DOWNLOAD", "UNKNOWN_DOWNLOAD_HOST", func(remainder string) string {
return fmt.Sprintf("/download/%s", remainder)
})
}

func clipboard(w http.ResponseWriter, r *http.Request) {
proxyStatic(w, r, paths.Clipboard, "INVALID_CLIPBOARD_REQUEST_URL", "PROXYING_CLIPBOARD", "UNKNOWN_DOWNLOAD_HOST", func(remainder string) string {
return fmt.Sprintf("/clipboard/%s", remainder)
})
}

func proxyStatic(w http.ResponseWriter, r *http.Request, route string, invalidUrlMessage string, proxyingMessage string, unknownHostMessage string, pathProvider func(string) string) {
confLock.RLock()
defer confLock.RUnlock()
Expand Down Expand Up @@ -728,16 +739,17 @@ func mux() http.Handler {
"Selenium Grid Router",
auth.HtpasswdFileProvider(users),
)
mux.HandleFunc(pingPath, ping)
mux.HandleFunc(statusPath, status)
mux.HandleFunc(errPath, err)
mux.HandleFunc(hostPath, WithSuitableAuthentication(authenticator, host))
mux.HandleFunc(quotaPath, WithSuitableAuthentication(authenticator, quotaInfo))
mux.HandleFunc(routePath, withCloseNotifier(WithSuitableAuthentication(authenticator, postOnly(route))))
mux.Handle(proxyPath, &httputil.ReverseProxy{Director: proxy})
mux.Handle(vncPath, websocket.Handler(vnc))
mux.HandleFunc(videoPath, WithSuitableAuthentication(authenticator, video))
mux.HandleFunc(logsPath, WithSuitableAuthentication(authenticator, logs))
mux.HandleFunc(downloadPath, WithSuitableAuthentication(authenticator, download))
mux.HandleFunc(paths.Ping, ping)
mux.HandleFunc(paths.Status, status)
mux.HandleFunc(paths.Err, err)
mux.HandleFunc(paths.Host, WithSuitableAuthentication(authenticator, host))
mux.HandleFunc(paths.Quota, WithSuitableAuthentication(authenticator, quotaInfo))
mux.HandleFunc(paths.Route, withCloseNotifier(WithSuitableAuthentication(authenticator, postOnly(route))))
mux.Handle(paths.Proxy, &httputil.ReverseProxy{Director: proxy})
mux.Handle(paths.VNC, websocket.Handler(vnc))
mux.HandleFunc(paths.Video, WithSuitableAuthentication(authenticator, video))
mux.HandleFunc(paths.Logs, WithSuitableAuthentication(authenticator, logs))
mux.HandleFunc(paths.Download, WithSuitableAuthentication(authenticator, download))
mux.HandleFunc(paths.Clipboard, WithSuitableAuthentication(authenticator, clipboard))
return mux
}
30 changes: 30 additions & 0 deletions proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@ func TestPing(t *testing.T) {
AssertThat(t, hasLastReloadTime, Is{true})
_, hasNumRequests := data["numRequests"]
AssertThat(t, hasNumRequests, Is{true})
_, hasNumSessions := data["numSessions"]
AssertThat(t, hasNumSessions, Is{true})
version, hasVersion := data["version"]
AssertThat(t, hasVersion, Is{true})
AssertThat(t, version, EqualTo{"test-revision"})
Expand Down Expand Up @@ -420,6 +422,34 @@ func TestProxyDownload(t *testing.T) {
AssertThat(t, rsp, Code{http.StatusNotFound})
}

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

AssertThat(t, err, Is{nil})
AssertThat(t, rsp, Code{http.StatusUnauthorized})
}

func TestProxyClipboard(t *testing.T) {

test.Lock()
defer test.Unlock()

fileServer, sessionID := prepareMockFileServer("/clipboard/123")
defer fileServer.Close()

rsp, err := doBasicHTTPRequest(http.MethodGet, gridrouter(fmt.Sprintf("/clipboard/%s", sessionID)), nil)
AssertThat(t, err, Is{nil})
AssertThat(t, rsp, Code{http.StatusOK})

rsp, err = doBasicHTTPRequest(http.MethodGet, gridrouter("/clipboard/missing-session"), nil)
AssertThat(t, err, Is{nil})
AssertThat(t, rsp, Code{http.StatusNotFound})

rsp, err = doBasicHTTPRequest(http.MethodGet, gridrouter("/clipboard/f7fd94f75c79c36e547c091632da440f_missing-session"), nil)
AssertThat(t, err, Is{nil})
AssertThat(t, rsp, Code{http.StatusNotFound})
}

func TestCreateSessionGet(t *testing.T) {
req, _ := http.NewRequest(http.MethodGet, gridrouter("/wd/hub/session"), nil)
req.SetBasicAuth("test", "test")
Expand Down

0 comments on commit a16380d

Please sign in to comment.